X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/d12746bc15d83ab821be36975da0179672708bc1..f9d167e054b6c63fb114cf96d1f0269f0bfbcefb:/src/src/smtp_out.c diff --git a/src/src/smtp_out.c b/src/src/smtp_out.c index 9bd90c77a..4e8c44869 100644 --- a/src/src/smtp_out.c +++ b/src/src/smtp_out.c @@ -3,6 +3,7 @@ *************************************************/ /* Copyright (c) University of Cambridge 1995 - 2018 */ +/* Copyright (c) The Exim Maintainers 2020 */ /* See the file NOTICE for conditions of use and distribution. */ /* A number of functions for driving outgoing SMTP calls. */ @@ -52,9 +53,18 @@ if (!(expint = expand_string(istring))) return FALSE; } -while (isspace(*expint)) expint++; -if (*expint == 0) return TRUE; +if (is_tainted2(expint, LOG_MAIN|LOG_PANIC, "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); + return FALSE; + } + +Uskip_whitespace(&expint); +if (!*expint) return TRUE; +/* we just tested to ensure no taint, so big_buffer is ok */ while ((iface = string_nextinlist(&expint, &sep, big_buffer, big_buffer_size))) { @@ -144,7 +154,40 @@ return TRUE; static void tfo_out_check(int sock) { -# if defined(TCP_INFO) && defined(EXIM_HAVE_TCPI_UNACKED) +# ifdef __FreeBSD__ +struct tcp_info tinfo; +socklen_t len = sizeof(tinfo); + +/* A getsockopt TCP_FASTOPEN unfortunately returns "was-used" for a TFO/R as +well as a TFO/C. Use what we can of the Linux hack below; reliability issues ditto. */ +switch (tcp_out_fastopen) + { + case TFO_ATTEMPTED_NODATA: + if ( getsockopt(sock, IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0 + && tinfo.tcpi_state == TCPS_SYN_SENT + && tinfo.__tcpi_unacked > 0 + ) + { + DEBUG(D_transport|D_v) + debug_printf("TCP_FASTOPEN tcpi_unacked %d\n", tinfo.__tcpi_unacked); + tcp_out_fastopen = TFO_USED_NODATA; + } + break; + /* + case TFO_ATTEMPTED_DATA: + case TFO_ATTEMPTED_DATA: + if (tinfo.tcpi_options & TCPI_OPT_SYN_DATA) XXX no equvalent as of 12.2 + */ + } + +switch (tcp_out_fastopen) + { + case TFO_ATTEMPTED_DATA: tcp_out_fastopen = TFO_USED_DATA; break; + default: break; /* compiler quietening */ + } + +# else /* Linux & Apple */ +# if defined(TCP_INFO) && defined(EXIM_HAVE_TCPI_UNACKED) struct tcp_info tinfo; socklen_t len = sizeof(tinfo); @@ -191,15 +234,27 @@ switch (tcp_out_fastopen) tcp_out_fastopen = TFO_NOT_USED; } break; + + default: break; /* compiler quietening */ } -# endif +# endif +# endif /* Linux & Apple */ } #endif -/* Arguments as for smtp_connect(), plus - early_data if non-NULL, idenmpotent data to be sent - +/* 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 */ @@ -269,23 +324,45 @@ early-data but no TFO support, send it after connecting. */ else { #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) - fastopen_blob = early_data ? early_data : &tcp_fastopen_nodata; + { + if (!early_data) + fastopen_blob = &tcp_fastopen_nodata; /* TFO, with no data */ + else if (early_data->data) + fastopen_blob = early_data; /* TFO, with data */ +# ifdef TCP_FASTOPEN_CONNECT + else + { /* expecting client data */ + 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 + } #endif if (ip_connect(sock, host_af, host->address, port, timeout, fastopen_blob) < 0) save_errno = errno; else if (early_data && !fastopen_blob && early_data->data && early_data->len) { + /* We had some early-data to send, but couldn't do TFO */ HDEBUG(D_transport|D_acl|D_v) debug_printf("sending %ld nonTFO early-data\n", (long)early_data->len); -#ifdef TCP_QUICKACK +#ifdef TCP_QUICKACK_notdef (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off)); #endif if (send(sock, early_data->data, early_data->len, 0) < 0) save_errno = errno; } +#ifdef TCP_QUICKACK_notdef + /* Under TFO (with openssl & pipe-conn; testcase 4069, as of + 5.10.8-100.fc32.x86_64) this seems to be inop. + Perhaps overwritten when we (client) go -> ESTABLISHED on seeing the 3rd-ACK? + For that case, added at smtp_reap_banner(). */ + (void) setsockopt(sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off)); +#endif } /* Either bind() or connect() failed */ @@ -294,7 +371,7 @@ if (save_errno != 0) { HDEBUG(D_transport|D_acl|D_v) { - debug_printf_indent("failed: %s", CUstrerror(save_errno)); + debug_printf_indent(" failed: %s", CUstrerror(save_errno)); if (save_errno == ETIMEDOUT) debug_printf(" (timeout=%s)", readconf_printtime(timeout)); debug_printf("\n"); @@ -311,7 +388,7 @@ else union sockaddr_46 interface_sock; EXIM_SOCKLEN_T size = sizeof(interface_sock); - HDEBUG(D_transport|D_acl|D_v) debug_printf_indent("connected\n"); + 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 @@ -339,7 +416,7 @@ smtp_port_for_connect(host_item * host, int port) { if (host->port != PORT_NONE) { - HDEBUG(D_transport|D_acl|D_v) + HDEBUG(D_transport|D_acl|D_v) if (port != host->port) debug_printf_indent("Transport port=%d replaced by host-specific port=%d\n", port, host->port); port = host->port; @@ -360,6 +437,9 @@ host->address will always be an IPv4 address. Arguments: sc details for making connection: host, af, interface, transport early_data if non-NULL, 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 */ @@ -389,7 +469,7 @@ if (ob->socks_proxy) { 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) @@ -442,7 +522,7 @@ BOOL more = mode == SCMD_MORE; HDEBUG(D_transport|D_acl) debug_printf_indent("cmd buf flush %d bytes%s\n", n, more ? " (more expected)" : ""); -#ifdef SUPPORT_TLS +#ifndef DISABLE_TLS if (outblock->cctx->tls_ctx) rc = tls_write(outblock->cctx->tls_ctx, outblock->buffer, n, more); else @@ -465,7 +545,7 @@ else rc = n; } else - + { rc = send(outblock->cctx->sock, outblock->buffer, n, #ifdef MSG_MORE more ? MSG_MORE : 0 @@ -473,6 +553,17 @@ else 0 #endif ); + +#if defined(__linux__) + /* This is a workaround for a current linux kernel bug: as of + 5.6.8-200.fc31.x86_64 small (cctx->sock, IPPROTO_TCP, TCP_CORK, &off, sizeof(off)); +#endif + } } if (rc <= 0) @@ -519,8 +610,14 @@ if (format) gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer }; va_list ap; + /* Use taint-unchecked routines for writing into big_buffer, trusting that + we'll never expand the results. Actually, the error-message use - leaving + the results in big_buffer for potential later use - is uncomfortably distant. + XXX Would be better to assume all smtp commands are short, use normal pool + alloc rather than big_buffer, and another global for the data-for-error. */ + va_start(ap, format); - if (!string_vformat(&gs, FALSE, CS format, ap)) + if (!string_vformat(&gs, SVFMT_TAINT_NOCHK, CS format, ap)) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "overlong write_command in outgoing " "SMTP"); va_end(ap); @@ -536,7 +633,7 @@ if (format) if (!flush_buffer(outblock, SCMD_FLUSH)) return -1; } - Ustrncpy(CS outblock->ptr, gs.s, gs.ptr); + Ustrncpy(outblock->ptr, gs.s, gs.ptr); outblock->ptr += gs.ptr; outblock->cmd_count++; gs.ptr -= 2; string_from_gstring(&gs); /* remove \r\n for error message */ @@ -556,10 +653,12 @@ if (format) while (!isspace(*p)) p++; while (isspace(*p)) p++; } - while (*p != 0) *p++ = '*'; + while (*p) *p++ = '*'; } - HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP>> %s\n", big_buffer); + HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP%c> %s\n", + mode == SCMD_BUFFER ? '|' : mode == SCMD_MORE ? '+' : '>', + big_buffer); } if (mode != SCMD_BUFFER) @@ -587,14 +686,14 @@ Arguments: inblock the SMTP input block (contains holding buffer, socket, etc.) buffer where to put the line size space available for the line - timeout the timeout to use when reading a packet + timelimit deadline for reading the lime, seconds past epoch Returns: length of a line that has been put in the buffer - -1 otherwise, with errno set + -1 otherwise, with errno set, and inblock->ptr adjusted */ static int -read_response_line(smtp_inblock *inblock, uschar *buffer, int size, int timeout) +read_response_line(smtp_inblock *inblock, uschar *buffer, int size, time_t timelimit) { uschar *p = buffer; uschar *ptr = inblock->ptr; @@ -631,15 +730,16 @@ for (;;) { *p = 0; /* Leave malformed line for error message */ errno = ERRNO_SMTPFORMAT; + inblock->ptr = ptr; return -1; } } /* Need to read a new input packet. */ - if((rc = ip_recv(cctx, inblock->buffer, inblock->buffersize, timeout)) <= 0) + if((rc = ip_recv(cctx, inblock->buffer, inblock->buffersize, timelimit)) <= 0) { - DEBUG(D_deliver|D_transport|D_acl) + DEBUG(D_deliver|D_transport|D_acl|D_v) debug_printf_indent(errno ? " SMTP(%s)<<\n" : " SMTP(closed)<<\n", strerror(errno)); break; @@ -656,6 +756,7 @@ for (;;) /* Get here if there has been some kind of recv() error; errno is set, but we ensure that the result buffer is empty before returning. */ +inblock->ptr = inblock->ptrend = inblock->buffer; *buffer = 0; return -1; } @@ -688,22 +789,28 @@ Returns: TRUE if a valid, non-error response was received; else FALSE /*XXX could move to smtp transport; no other users */ BOOL -smtp_read_response(void * sx0, uschar *buffer, int size, int okdigit, +smtp_read_response(void * sx0, uschar * buffer, int size, int okdigit, int timeout) { smtp_context * sx = sx0; -uschar *ptr = buffer; +uschar * ptr = buffer; int count = 0; +time_t timelimit = time(NULL) + timeout; errno = 0; /* Ensure errno starts out zero */ -#ifdef EXPERIMENTAL_PIPE_CONNECT +#ifndef DISABLE_PIPE_CONNECT if (sx->pending_BANNER || sx->pending_EHLO) - if (smtp_reap_early_pipe(sx, &count) != OK) + { + int rc; + 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; return FALSE; } + } #endif /* This is a loop to read and concatenate the lines that make up a multi-line @@ -711,7 +818,7 @@ response. */ for (;;) { - if ((count = read_response_line(&sx->inblock, ptr, size, timeout)) < 0) + if ((count = read_response_line(&sx->inblock, ptr, size, timelimit)) < 0) return FALSE; HDEBUG(D_transport|D_acl|D_v)