Support PIPECONNECT with helo_data using the local IP, when interface is known.
authorJeremy Harris <jgh146exb@wizmail.org>
Sun, 3 Apr 2022 20:37:01 +0000 (21:37 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Mon, 4 Apr 2022 22:33:21 +0000 (23:33 +0100)
doc/doc-docbook/spec.xfpt
doc/doc-txt/ChangeLog
src/src/functions.h
src/src/smtp_out.c
src/src/structs.h
src/src/transports/smtp.c
src/src/transports/smtp_socks.c
src/src/verify.c
test/confs/4050
test/log/4051
test/scripts/4050-pipe-conn/4051

index 0ac98d668ca2a76c6c92ee6abdf8f7eeb1cd55f3..7f96768f7cde6d92b137daf4ce00268ce6483add 100644 (file)
@@ -25582,7 +25582,10 @@ so combines well with TCP Fast Open.
 See also the &%pipelining_connect_advertise_hosts%& main option.
 
 Note:
-When the facility is used, the transport &%helo_data%& option
+.new
+When the facility is used, if the transport &%interface%& option is unset
+the &%helo_data%& option
+.wen
 will be expanded before the &$sending_ip_address$& variable
 is filled in.
 A check is made for the use of that variable, without the
index 93eac06a1427f9e9deb0b756567fbf6540fc0f09..3b1aa2664514f667dc0b989b717eb5266f765f45 100644 (file)
@@ -116,6 +116,11 @@ JH/26 Fix CHUNKING on a continued-transport.  Previously the usabliility of
       the the facility was not passed across execs, and only the first message
       passed over a connection could use BDAT; any further ones using DATA.
 
+JH/27 Support the PIPECONNECT facility in the smtp transport when the helo_data
+      uses $sending_ip_address and an interface is specified.
+      Previously any use of the local address in the EHLO name disabled
+      PIPECONNECT, the common case being to use the rDNS of it.
+
 
 Exim version 4.95
 -----------------
index e231ce204742c94c9be70a85a3ff322f9dc50cb9..8badd90a421aff32f8f6ecd1f1653fc3b1c3e85d 100644 (file)
@@ -485,6 +485,7 @@ extern int     sieve_interpret(const uschar *, int, const uschar *,
                 const uschar *, const uschar *, const uschar *,
                 address_item **, uschar **);
 extern void    sigalrm_handler(int);
+extern int     smtp_boundsock(smtp_connect_args *);
 extern void    smtp_closedown(uschar *);
 extern void    smtp_command_timeout_exit(void) NORETURN;
 extern void    smtp_command_sigterm_exit(void) NORETURN;
@@ -493,8 +494,6 @@ extern void    smtp_data_sigint_exit(void) NORETURN;
 extern void    smtp_deliver_init(void);
 extern uschar *smtp_cmd_hist(void);
 extern int     smtp_connect(smtp_connect_args *, const blob *);
-extern int     smtp_sock_connect(host_item *, int, int, uschar *,
-                transport_instance * tb, int, const blob *);
 extern int     smtp_feof(void);
 extern int     smtp_ferror(void);
 extern uschar *smtp_get_connection_info(void);
@@ -516,6 +515,7 @@ extern void    smtp_notquit_exit(uschar *, uschar *, uschar *, ...);
 extern void    smtp_port_for_connect(host_item *, int);
 extern void    smtp_send_prohibition_message(int, uschar *);
 extern int     smtp_setup_msg(void);
+extern int     smtp_sock_connect(smtp_connect_args *, int, const blob *);
 extern BOOL    smtp_start_session(void);
 extern int     smtp_ungetc(int);
 extern BOOL    smtp_verify_helo(void);
index 7b8212477af67f305d4c1f67774891159d8f88c0..610e4a068c500ba1bea7ea60ea27cbff093b313d 100644 (file)
@@ -252,44 +252,21 @@ switch (tcp_out_fastopen)
 #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
-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;
-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. */
 
@@ -300,7 +277,7 @@ 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. */
 
-if (dscp && dscp_lookup(dscp, host_af, &dscp_level, &dscp_option, &dscp_value))
+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);
@@ -309,7 +286,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 */
-  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));
   }
@@ -317,24 +294,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. */
 
-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", 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. */
 
-else
+if (!save_errno)
   {
 #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 */
@@ -351,7 +380,7 @@ else
     }
 #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)
     {
@@ -374,29 +403,13 @@ else
 #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);
 
+  /* 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);
@@ -408,12 +421,25 @@ else
     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;
   }
+
+/* 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;
 }
 
 
@@ -501,8 +527,7 @@ if (ob->socks_proxy)
   }
 #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);
 }
 
 
index 8c103caa82c988096286f8d4d1dfe27d5ace8bfb..46cc99ff681de9aef611de8442e43129804cb33f 100644 (file)
@@ -830,6 +830,7 @@ typedef struct {
   host_item *           host;
   int                   host_af;
   uschar *              interface;
+  int                  sock;   /* used for a bound but not connected socket */
 
 #ifdef SUPPORT_DANE
   BOOL dane:1;                 /* connection must do dane */
index d897c691eb2bffc86748f232daacf2ae11303131..4d6135be71f0d024609c7fc4560eac3d03c9aefe 100644 (file)
@@ -2251,6 +2251,7 @@ if (!continue_hostname)
     the helo string might use it avoid doing early-pipelining. */
 
     if (  !sx->helo_data
+       || sx->conn_args.interface
        || !Ustrstr(sx->helo_data, "$sending_ip_address")
        || Ustrstr(sx->helo_data, "def:sending_ip_address")
        )
@@ -2270,7 +2271,10 @@ if (!continue_hostname)
 
 PIPE_CONNECT_RETRY:
   if (sx->early_pipe_active)
+    {
     sx->outblock.conn_args = &sx->conn_args;
+    (void) smtp_boundsock(&sx->conn_args);
+    }
   else
 #endif
     {
@@ -2295,9 +2299,10 @@ PIPE_CONNECT_RETRY:
     }
   /* Expand the greeting message while waiting for the initial response. (Makes
   sense if helo_data contains ${lookup dnsdb ...} stuff). The expansion is
-  delayed till here so that $sending_interface and $sending_port are set. */
-/*XXX early-pipe: they still will not be. Is there any way to find out what they
-will be?  Somehow I doubt it. */
+  delayed till here so that $sending_ip_address and $sending_port are set.
+  Those will be known even for a TFO lazy-connect, having been set by the bind().
+  For early-pipe, we are ok if binding to a local interface; otherwise (if
+  $sending_ip_address is seen in helo_data) we disabled early-pipe above. */
 
   if (sx->helo_data)
     if (!(sx->helo_data = expand_string(sx->helo_data)))
@@ -3716,6 +3721,7 @@ sx->port = defport;
 sx->conn_args.interface = interface;
 sx->helo_data = NULL;
 sx->conn_args.tblock = tblock;
+sx->conn_args.sock = -1;
 gettimeofday(&sx->delivery_start, NULL);
 sx->sync_addr = sx->first_addr = addrlist;
 
index 5aff62c15761eabba25c23d113e78d1ca6d91341..4dd03c1f165f530ccd41e42276c3f05082299f14 100644 (file)
@@ -274,7 +274,7 @@ for(;;)
   {
   int idx;
   host_item proxy;
-  int proxy_af;
+  smtp_connect_args sc = {.sock = -1};
 
   if ((idx = socks_get_proxy(proxies, nproxies)) < 0)
     {
@@ -286,11 +286,16 @@ for(;;)
 
   /* bodge up a host struct for the proxy */
   proxy.address = proxy.name = sob->proxy_host;
-  proxy_af = Ustrchr(sob->proxy_host, ':') ? AF_INET6 : AF_INET;
+  proxy.port = sob->port;
+
+  sc.tblock = tb;
+  sc.ob = ob;
+  sc.host = &proxy;
+  sc.host_af = Ustrchr(sob->proxy_host, ':') ? AF_INET6 : AF_INET;
+  sc.interface = interface;
 
   /*XXX we trust that the method-select command is idempotent */
-  if ((fd = smtp_sock_connect(&proxy, proxy_af, sob->port,
-             interface, tb, sob->timeout, &early_data)) >= 0)
+  if ((fd = smtp_sock_connect(&sc, sob->timeout, &early_data)) >= 0)
     {
     proxy_local_address = string_copy(proxy.address);
     proxy_local_port = sob->port;
index d1c4af275b5f15bfe6538fe0a3a680d4c99a705f..ed0898c9b7316fd09b42c88d02e01774d987a40c 100644 (file)
@@ -661,6 +661,7 @@ coding means skipping this whole loop and doing the append separately.  */
     sx->conn_args.interface = interface;
     sx->helo_data = tf->helo_data;
     sx->conn_args.tblock = addr->transport;
+    sx->conn_args.sock = -1;
     sx->verify = TRUE;
 
 tls_retry_connection:
index fd3d7db54a785cc4e1332982a9ca39317ef35d2d..2523328f7aa5397d643a4727f088fa23e16417c4 100644 (file)
@@ -60,6 +60,9 @@ begin transports
 
 smtp:
   driver =             smtp
+.ifdef BB
+  interface =          BB
+.endif
   hosts_try_fastopen = :
   hosts_pipe_connect = CONTROL
 .ifdef HELO_MSG
index c332087d719bda8e9eff3ae73d51e776b3343c7e..d56d4061a49f6cfd26e17464848b56b7525b9e2d 100644 (file)
 1999-03-02 09:44:33 10HmbF-0005vi-00 => helo_data@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] L C="250 OK id=10HmbG-0005vi-00"
 1999-03-02 09:44:33 10HmbF-0005vi-00 Completed
 1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbH-0005vi-00 <= CALLER@the.local.host.name U=CALLER P=local S=sss for helo_data_bound@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbH-0005vi-00 => helo_data_bound@test.ex R=client T=smtp H=127.0.0.1 [127.0.0.1] L* C="250 OK id=10HmbI-0005vi-00"
+1999-03-02 09:44:33 10HmbH-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
 
 ******** SERVER ********
 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D
@@ -52,3 +57,8 @@
 1999-03-02 09:44:33 10HmbG-0005vi-00 => :blackhole: <helo_data@test.ex> R=server
 1999-03-02 09:44:33 10HmbG-0005vi-00 Completed
 1999-03-02 09:44:33 End queue run: pid=pppp
+1999-03-02 09:44:33 10HmbI-0005vi-00 <= CALLER@the.local.host.name H=(127.0.0.1) [127.0.0.1] P=esmtp L* S=sss id=E10HmbH-0005vi-00@the.local.host.name for helo_data_bound@test.ex
+1999-03-02 09:44:33 Start queue run: pid=pppp
+1999-03-02 09:44:33 10HmbI-0005vi-00 => :blackhole: <helo_data_bound@test.ex> R=server
+1999-03-02 09:44:33 10HmbI-0005vi-00 Completed
+1999-03-02 09:44:33 End queue run: pid=pppp
index dd5f4c7a849b2dacbb69ec8b251b849205da671e..e06bf5c7e59fe900745183b6052542071c2fdaf0 100644 (file)
@@ -64,6 +64,15 @@ exim -DCONTROL=127.0.0.1 -DHELO_MSG='$sending_ip_address' -q
 exim -DNOTDAEMON -DSERVER=server -q
 ****
 #
+# ... unless the connection is bound to a specified interface
+exim helo_data_bound@test.ex
+Subject test 6
+****
+exim -DCONTROL=127.0.0.1 -DHELO_MSG='$sending_ip_address' -DBB=127.0.0.1 -q
+****
+exim -DNOTDAEMON -DSERVER=server -q
+****
+#
 #
 killdaemon
 no_msglog_check