X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/5800e3234f2594639d82e5063d9c522c6a881d25..HEAD:/src/src/smtp_out.c diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c index 7b8212477..2ea29a7e4 100644 --- a/src/src/smtp_out.c +++ b/src/src/smtp_out.c @@ -2,9 +2,10 @@ * Exim - an Internet mail transport agent * *************************************************/ +/* Copyright (c) The Exim Maintainers 2020 - 2024 */ /* Copyright (c) University of Cambridge 1995 - 2018 */ -/* Copyright (c) The Exim Maintainers 2020 - 2021 */ /* 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. */ @@ -252,44 +253,20 @@ 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) { -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; -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,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. */ -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); @@ -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 */ - 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 +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. */ -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. */ -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) + 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 */ @@ -343,15 +373,16 @@ else # 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 } + expand_level--; #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,30 +405,14 @@ 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); - 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 @@ -408,12 +423,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; } @@ -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 - 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 */ @@ -501,8 +529,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); } @@ -524,10 +551,10 @@ Returns: TRUE if OK, FALSE on error, with errno set 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; +const uschar * where; 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 +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 @@ -555,6 +583,7 @@ else 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; @@ -563,6 +592,7 @@ else } else { + where = US"send"; 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 */ + where = US"cork"; if (!more) setsockopt(cctx->sock, IPPROTO_TCP, TCP_CORK, &off, sizeof(off)); #endif @@ -585,7 +616,8 @@ else 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; } @@ -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 * *************************************************/ @@ -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 - 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 @@ -654,7 +670,6 @@ if (format) 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 " @@ -678,13 +693,13 @@ if (format) if (outblock->authenticating) { - uschar *p = big_buffer; + uschar * p = big_buffer; 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++ = '*'; }