From: Jeremy Harris Date: Sun, 3 Apr 2022 20:37:01 +0000 (+0100) Subject: Support PIPECONNECT with helo_data using the local IP, when interface is known. X-Git-Tag: exim-4.96-RC0~14 X-Git-Url: https://git.exim.org/exim.git/commitdiff_plain/cb45303cf2a8d9922702f13db42b3285c48f6aa7 Support PIPECONNECT with helo_data using the local IP, when interface is known. --- diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index 0ac98d668..7f96768f7 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -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 diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index 93eac06a1..3b1aa2664 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -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 ----------------- diff --git a/src/src/functions.h b/src/src/functions.h index e231ce204..8badd90a4 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -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); diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c index 7b8212477..610e4a068 100644 --- a/src/src/smtp_out.c +++ b/src/src/smtp_out.c @@ -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); } diff --git a/src/src/structs.h b/src/src/structs.h index 8c103caa8..46cc99ff6 100644 --- a/src/src/structs.h +++ b/src/src/structs.h @@ -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 */ diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index d897c691e..4d6135be7 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -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; diff --git a/src/src/transports/smtp_socks.c b/src/src/transports/smtp_socks.c index 5aff62c15..4dd03c1f1 100644 --- a/src/src/transports/smtp_socks.c +++ b/src/src/transports/smtp_socks.c @@ -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; diff --git a/src/src/verify.c b/src/src/verify.c index d1c4af275..ed0898c9b 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -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: diff --git a/test/confs/4050 b/test/confs/4050 index fd3d7db54..2523328f7 100644 --- a/test/confs/4050 +++ b/test/confs/4050 @@ -60,6 +60,9 @@ begin transports smtp: driver = smtp +.ifdef BB + interface = BB +.endif hosts_try_fastopen = : hosts_pipe_connect = CONTROL .ifdef HELO_MSG diff --git a/test/log/4051 b/test/log/4051 index c332087d7..d56d4061a 100644 --- a/test/log/4051 +++ b/test/log/4051 @@ -23,6 +23,11 @@ 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: 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: R=server +1999-03-02 09:44:33 10HmbI-0005vi-00 Completed +1999-03-02 09:44:33 End queue run: pid=pppp diff --git a/test/scripts/4050-pipe-conn/4051 b/test/scripts/4050-pipe-conn/4051 index dd5f4c7a8..e06bf5c7e 100644 --- a/test/scripts/4050-pipe-conn/4051 +++ b/test/scripts/4050-pipe-conn/4051 @@ -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