X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/8e9fdd6369f0a7a81f0ca195e24edd372f7ca3ef..6acb441b40bbcded2e85819c71a068db713e7ca6:/src/src/transports/smtp.c diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 459110b80..301d84c2e 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.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. */ #include "../exim.h" @@ -42,7 +43,7 @@ optionlist smtp_transport_options[] = { { "dane_require_tls_ciphers", opt_stringptr, LOFF(dane_require_tls_ciphers) }, # endif { "data_timeout", opt_time, LOFF(data_timeout) }, - { "delay_after_cutoff", opt_bool, LOFF(delay_after_cutoff) }, + { "delay_after_cutoff", opt_bool, LOFF(delay_after_cutoff) }, #ifndef DISABLE_DKIM { "dkim_canon", opt_stringptr, LOFF(dkim.dkim_canon) }, { "dkim_domain", opt_stringptr, LOFF(dkim.dkim_domain) }, @@ -110,6 +111,7 @@ optionlist smtp_transport_options[] = { { "lmtp_ignore_quota", opt_bool, LOFF(lmtp_ignore_quota) }, { "max_rcpt", opt_int | opt_public, OPT_OFF(transport_instance, max_addresses) }, + { "message_linelength_limit", opt_int, LOFF(message_linelength_limit) }, { "multi_domain", opt_expand_bool | opt_public, OPT_OFF(transport_instance, multi_domain) }, { "port", opt_stringptr, LOFF(port) }, @@ -126,7 +128,7 @@ optionlist smtp_transport_options[] = { { "tls_dh_min_bits", opt_int, LOFF(tls_dh_min_bits) }, { "tls_privatekey", opt_stringptr, LOFF(tls_privatekey) }, { "tls_require_ciphers", opt_stringptr, LOFF(tls_require_ciphers) }, -# ifdef EXPERIMENTAL_TLS_RESUME +# ifndef DISABLE_TLS_RESUME { "tls_resumption_hosts", opt_stringptr, LOFF(tls_resumption_hosts) }, # endif { "tls_sni", opt_stringptr, LOFF(tls_sni) }, @@ -161,23 +163,12 @@ void smtp_transport_closedown(transport_instance *tblock) {} /* Default private options block for the smtp transport. */ smtp_transport_options_block smtp_transport_option_defaults = { - .hosts = NULL, - .fallback_hosts = NULL, - .hostlist = NULL, - .fallback_hostlist = NULL, + /* All non-mentioned elements 0/NULL/FALSE */ .helo_data = US"$primary_hostname", - .interface = NULL, - .port = NULL, .protocol = US"smtp", - .dscp = NULL, - .serialize_hosts = NULL, - .hosts_try_auth = NULL, - .hosts_require_auth = NULL, .hosts_try_chunking = US"*", #ifdef SUPPORT_DANE .hosts_try_dane = US"*", - .hosts_require_dane = NULL, - .dane_require_tls_ciphers = NULL, #endif .hosts_try_fastopen = US"*", #ifndef DISABLE_PRDR @@ -185,19 +176,6 @@ smtp_transport_options_block smtp_transport_option_defaults = { #endif #ifndef DISABLE_OCSP .hosts_request_ocsp = US"*", /* hosts_request_ocsp (except under DANE; tls_client_start()) */ - .hosts_require_ocsp = NULL, -#endif - .hosts_require_tls = NULL, - .hosts_avoid_tls = NULL, - .hosts_verify_avoid_tls = NULL, - .hosts_avoid_pipelining = NULL, -#ifndef DISABLE_PIPE_CONNECT - .hosts_pipe_connect = NULL, -#endif - .hosts_avoid_esmtp = NULL, -#ifndef DISABLE_TLS - .hosts_nopass_tls = NULL, - .hosts_noproxy_tls = NULL, #endif .command_timeout = 5*60, .connect_timeout = 5*60, @@ -206,62 +184,28 @@ smtp_transport_options_block smtp_transport_option_defaults = { .size_addition = 1024, .hosts_max_try = 5, .hosts_max_try_hardlimit = 50, + .message_linelength_limit = 998, .address_retry_include_sender = TRUE, - .allow_localhost = FALSE, - .authenticated_sender_force = FALSE, - .gethostbyname = FALSE, .dns_qualify_single = TRUE, - .dns_search_parents = FALSE, .dnssec = { .request= US"*", .require=NULL }, .delay_after_cutoff = TRUE, - .hosts_override = FALSE, - .hosts_randomize = FALSE, .keepalive = TRUE, - .lmtp_ignore_quota = FALSE, - .expand_retry_include_ip_address = NULL, .retry_include_ip_address = TRUE, -#ifdef SUPPORT_SOCKS - .socks_proxy = NULL, -#endif #ifndef DISABLE_TLS - .tls_certificate = NULL, - .tls_crl = NULL, - .tls_privatekey = NULL, - .tls_require_ciphers = NULL, - .tls_sni = NULL, +# if defined(SUPPORT_SYSDEFAULT_CABUNDLE) || !defined(USE_GNUTLS) .tls_verify_certificates = US"system", +# endif .tls_dh_min_bits = EXIM_CLIENT_DH_DEFAULT_MIN_BITS, .tls_tempfail_tryclear = TRUE, -# ifdef EXPERIMENTAL_TLS_RESUME - .tls_resumption_hosts = NULL, -# endif - .tls_verify_hosts = NULL, .tls_try_verify_hosts = US"*", .tls_verify_cert_hostnames = US"*", #endif #ifdef SUPPORT_I18N - .utf8_downconvert = NULL, + .utf8_downconvert = US"-1", #endif #ifndef DISABLE_DKIM .dkim = - {.dkim_domain = NULL, - .dkim_identity = NULL, - .dkim_private_key = NULL, - .dkim_selector = NULL, - .dkim_canon = NULL, - .dkim_sign_headers = NULL, - .dkim_strict = NULL, - .dkim_hash = US"sha256", - .dkim_timestamps = NULL, - .dot_stuffed = FALSE, - .force_bodyhash = FALSE, -# ifdef EXPERIMENTAL_ARC - .arc_signspec = NULL, -# endif - }, -# ifdef EXPERIMENTAL_ARC - .arc_sign = NULL, -# endif + { .dkim_hash = US"sha256", }, #endif }; @@ -361,10 +305,6 @@ smtp_transport_setup(transport_instance *tblock, address_item *addrlist, { smtp_transport_options_block *ob = SOB tblock->options_block; -errmsg = errmsg; /* Keep picky compilers happy */ -uid = uid; -gid = gid; - /* Pass back options if required. This interface is getting very messy. */ if (tf) @@ -865,7 +805,7 @@ else uschar * ehlo_resp_key = ehlo_cache_key(sx); dbdata_ehlo_resp * er; - if (!(er = dbfn_read(dbm_file, ehlo_resp_key))) + if (!(er = dbfn_read_enforce_length(dbm_file, ehlo_resp_key, sizeof(dbdata_ehlo_resp)))) { DEBUG(D_transport) debug_printf("no ehlo-resp record\n"); } else if (time(NULL) - er->time_stamp > retry_data_expire) { @@ -911,11 +851,9 @@ names = string_copyn(expand_nstring[1], expand_nlength[1]); for (au = auths, authnum = 0; au; au = au->next, authnum++) if (au->client) { const uschar * list = names; - int sep = ' '; - uschar name[32]; - - while (string_nextinlist(&list, &sep, name, sizeof(name))) - if (strcmpic(au->public_name, name) == 0) + uschar * s; + for (int sep = ' '; s = string_nextinlist(&list, &sep, NULL, 0); ) + if (strcmpic(au->public_name, s) == 0) { authbits |= BIT(authnum); break; } } @@ -1550,6 +1488,7 @@ if ( sx->esmtp if (require_auth == OK && !f.smtp_authenticated) { + invalidate_ehlo_cache_entry(sx); set_errno_nohost(sx->addrlist, ERRNO_AUTHFAIL, string_sprintf("authentication required but %s", fail_reason), DEFER, FALSE, &sx->delivery_start); @@ -1620,8 +1559,8 @@ return FALSE; typedef struct smtp_compare_s { - uschar *current_sender_address; - struct transport_instance *tblock; + uschar * current_sender_address; + struct transport_instance * tblock; } smtp_compare_t; @@ -1685,7 +1624,7 @@ current_local_identity = smtp_local_identity(s_compare->current_sender_address, s_compare->tblock); if (!(new_sender_address = deliver_get_sender_address(message_id))) - return 0; + return FALSE; message_local_identity = smtp_local_identity(new_sender_address, s_compare->tblock); @@ -1970,7 +1909,7 @@ tls_out.peerdn = NULL; tls_out.sni = NULL; #endif tls_out.ocsp = OCSP_NOT_REQ; -#ifdef EXPERIMENTAL_TLS_RESUME +#ifndef DISABLE_TLS_RESUME tls_out.resumption = 0; #endif tls_out.ver = NULL; @@ -1989,7 +1928,80 @@ if (sx->smtps) DEFER, FALSE, &sx->delivery_start); return ERROR; } -#endif +#else + +/* If we have a proxied TLS connection, check usability for this message */ + +if (continue_hostname && continue_proxy_cipher) + { + int rc; + const uschar * sni = US""; + +# ifdef SUPPORT_DANE + /* Check if the message will be DANE-verified; if so force its SNI */ + + tls_out.dane_verified = FALSE; + smtp_port_for_connect(sx->conn_args.host, sx->port); + if ( sx->conn_args.host->dnssec == DS_YES + && ( sx->dane_required + || verify_check_given_host(CUSS &ob->hosts_try_dane, sx->conn_args.host) == OK + ) ) + switch (rc = tlsa_lookup(sx->conn_args.host, &sx->conn_args.tlsa_dnsa, sx->dane_required)) + { + case OK: sx->conn_args.dane = TRUE; + ob->tls_tempfail_tryclear = FALSE; /* force TLS */ + ob->tls_sni = sx->first_addr->domain; /* force SNI */ + break; + case FAIL_FORCED: break; + default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER, + string_sprintf("DANE error: tlsa lookup %s", + rc_to_string(rc)), + rc, FALSE, &sx->delivery_start); +# ifndef DISABLE_EVENT + (void) event_raise(sx->conn_args.tblock->event_action, + US"dane:fail", sx->dane_required + ? US"dane-required" : US"dnssec-invalid"); +# endif + return rc; + } +# endif + + /* If the SNI or the DANE status required for the new message differs from the + existing conn drop the connection to force a new one. */ + + if (ob->tls_sni && !(sni = expand_cstring(ob->tls_sni))) + log_write(0, LOG_MAIN|LOG_PANIC, + "<%s>: failed to expand transport's tls_sni value: %s", + sx->addrlist->address, expand_string_message); + +# ifdef SUPPORT_DANE + if ( (continue_proxy_sni ? (Ustrcmp(continue_proxy_sni, sni) == 0) : !*sni) + && continue_proxy_dane == sx->conn_args.dane) + { + tls_out.sni = US sni; + if ((tls_out.dane_verified = continue_proxy_dane)) + sx->conn_args.host->dnssec = DS_YES; + } +# else + if ((continue_proxy_sni ? (Ustrcmp(continue_proxy_sni, sni) == 0) : !*sni)) + tls_out.sni = US sni; +# endif + else + { + DEBUG(D_transport) + debug_printf("Closing proxied-TLS connection due to SNI mismatch\n"); + + HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP>> QUIT\n"); + write(0, "QUIT\r\n", 6); + close(0); + continue_hostname = continue_proxy_cipher = NULL; + f.continue_more = FALSE; + continue_sequence = 1; /* Unfortunately, this process cannot affect success log + which is done by delivery proc. Would have to pass this + back through reporting pipe. */ + } + } +#endif /*!DISABLE_TLS*/ /* Make a connection to the host if this isn't a continued delivery, and handle the initial interaction and HELO/EHLO/LHLO. Connect timeout errors are handled @@ -2019,7 +2031,8 @@ if (!continue_hostname) switch (rc = tlsa_lookup(sx->conn_args.host, &sx->conn_args.tlsa_dnsa, sx->dane_required)) { case OK: sx->conn_args.dane = TRUE; - ob->tls_tempfail_tryclear = FALSE; + ob->tls_tempfail_tryclear = FALSE; /* force TLS */ + ob->tls_sni = sx->first_addr->domain; /* force SNI */ break; case FAIL_FORCED: break; default: set_errno_nohost(sx->addrlist, ERRNO_DNSDEFER, @@ -2463,9 +2476,7 @@ if ( smtp_peer_options & OPTION_TLS /* TLS negotiation failed; give an error. From outside, this function may be called again to try in clear on a new connection, if the options permit it for this host. */ -#ifdef USE_GNUTLS - GNUTLS_CONN_FAILED: -#endif + TLS_CONN_FAILED: DEBUG(D_tls) debug_printf("TLS session fail: %s\n", tls_errstr); # ifdef SUPPORT_DANE @@ -2487,7 +2498,23 @@ if ( smtp_peer_options & OPTION_TLS goto TLS_FAILED; } - /* TLS session is set up */ + /* TLS session is set up. Check the inblock fill level. If there is + content then as we have not yet done a tls read it must have arrived before + the TLS handshake, in-clear. That violates the sync requirement of the + STARTTLS RFC, so fail. */ + + if (sx->inblock.ptr != sx->inblock.ptrend) + { + DEBUG(D_tls) + { + int i = sx->inblock.ptrend - sx->inblock.ptr; + debug_printf("unused data in input buffer after ack for STARTTLS:\n" + "'%.*s'%s\n", + i > 100 ? 100 : i, sx->inblock.ptr, i > 100 ? "..." : ""); + } + tls_errstr = US"synch error before connect"; + goto TLS_CONN_FAILED; + } smtp_peer_options_wrap = smtp_peer_options; for (address_item * addr = sx->addrlist; addr; addr = addr->next) @@ -2592,7 +2619,7 @@ if (tls_out.active.sock >= 0) Can it do that, with all the flexibility we need? */ tls_errstr = US"error on first read"; - goto GNUTLS_CONN_FAILED; + goto TLS_CONN_FAILED; } #else goto RESPONSE_FAILED; @@ -3284,13 +3311,9 @@ int max_fd = MAX(pfd[0], tls_out.active.sock) + 1; int rc, i; close(pfd[1]); -if ((rc = exim_fork(US"tls proxy"))) - { - DEBUG(D_transport) debug_printf("proxy-proc final-pid %d\n", rc); +if ((rc = exim_fork(US"tls-proxy"))) _exit(rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS); - } -testharness_pause_ms(100); /* let parent debug out */ set_process_info("proxying TLS connection for continued transport"); FD_ZERO(&rfds); FD_SET(tls_out.active.sock, &rfds); @@ -3365,7 +3388,7 @@ for (int fd_bits = 3; fd_bits; ) done: testharness_pause_ms(100); /* let logging complete */ - exim_exit(0, US"TLS proxy"); + exim_exit(EXIT_SUCCESS); } #endif @@ -3433,8 +3456,10 @@ BOOL pass_message = FALSE; uschar *message = NULL; uschar new_message_id[MESSAGE_ID_LENGTH + 1]; smtp_context * sx = store_get(sizeof(*sx), TRUE); /* tainted, for the data buffers */ +#ifdef SUPPORT_DANE +BOOL dane_held; +#endif -suppress_tls = suppress_tls; /* stop compiler warning when no TLS support */ *message_defer = FALSE; memset(sx, 0, sizeof(*sx)); @@ -3449,17 +3474,43 @@ sx->conn_args.tblock = tblock; gettimeofday(&sx->delivery_start, NULL); sx->sync_addr = sx->first_addr = addrlist; -/* Get the channel set up ready for a message (MAIL FROM being the next -SMTP command to send */ +#ifdef SUPPORT_DANE +DANE_DOMAINS: +dane_held = FALSE; +#endif + +/* Get the channel set up ready for a message, MAIL FROM being the next +SMTP command to send. */ if ((rc = smtp_setup_conn(sx, suppress_tls)) != OK) { timesince(&addrlist->delivery_time, &sx->delivery_start); - return rc; + yield = rc; + goto TIDYUP; } +#ifdef SUPPORT_DANE +/* If the connection used DANE, ignore for now any addresses with incompatible +domains. The SNI has to be the domain. Arrange a whole new TCP conn later, +just in case only TLS isn't enough. */ + +if (sx->conn_args.dane) + { + const uschar * dane_domain = sx->first_addr->domain; + + for (address_item * a = sx->first_addr->next; a; a = a->next) + if ( a->transport_return == PENDING_DEFER + && Ustrcmp(dane_domain, a->domain) != 0) + { + DEBUG(D_transport) debug_printf("DANE: holding %s for later\n", a->domain); + dane_held = TRUE; + a->transport_return = DANE; + } + } +#endif + /* If there is a filter command specified for this transport, we can now -set it up. This cannot be done until the identify of the host is known. */ +set it up. This cannot be done until the identity of the host is known. */ if (tblock->filter_command) { @@ -4083,8 +4134,15 @@ if (!sx->ok) *message_defer = TRUE; } +#ifdef TIOCOUTQ + DEBUG(D_transport) if (sx->cctx.sock >= 0) + { + int n; + if (ioctl(sx->cctx.sock, TIOCOUTQ, &n) == 0) + debug_printf("%d bytes remain in socket output buffer\n", n); + } +#endif } - /* Otherwise, we have an I/O error or a timeout other than after MAIL or ".", or some other transportation error. We defer all addresses and yield DEFER, except for the case of failed add_headers expansion, or a transport @@ -4152,14 +4210,13 @@ DEBUG(D_transport) if (sx->completed_addr && sx->ok && sx->send_quit) { - BOOL more; smtp_compare_t t_compare; t_compare.tblock = tblock; t_compare.current_sender_address = sender_address; - if ( sx->first_addr != NULL - || f.continue_more + if ( sx->first_addr != NULL /* more addrs for this message */ + || f.continue_more /* more addrs for coninued-host */ || ( #ifndef DISABLE_TLS ( tls_out.active.sock < 0 && !continue_proxy_cipher @@ -4168,7 +4225,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit) && #endif transport_check_waiting(tblock->name, host->name, - tblock->connection_max_messages, new_message_id, &more, + tblock->connection_max_messages, new_message_id, (oicf)smtp_are_same_identities, (void*)&t_compare) ) ) { @@ -4206,7 +4263,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit) if (sx->first_addr != NULL) /* More addresses still to be sent */ - { /* in this run of the transport */ + { /* for this message */ continue_sequence++; /* Causes * in logging */ pipelining_active = sx->pipelining_used; /* was cleared at DATA */ goto SEND_MESSAGE; @@ -4230,6 +4287,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit) tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_WAIT); sx->cctx.tls_ctx = NULL; + tls_out.active.sock = -1; smtp_peer_options = smtp_peer_options_wrap; sx->ok = !sx->smtps && smtp_write_command(sx, SCMD_FLUSH, "EHLO %s\r\n", sx->helo_data) @@ -4238,7 +4296,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit) '2', ob->command_timeout); if (sx->ok && f.continue_more) - return yield; /* More addresses for another run */ + goto TIDYUP; /* More addresses for another run */ } else { @@ -4258,7 +4316,7 @@ if (sx->completed_addr && sx->ok && sx->send_quit) else #endif if (f.continue_more) - return yield; /* More addresses for another run */ + goto TIDYUP; /* More addresses for another run */ /* If the socket is successfully passed, we mustn't send QUIT (or indeed anything!) from here. */ @@ -4279,10 +4337,9 @@ propagate it from the initial #ifndef DISABLE_TLS if (tls_out.active.sock >= 0) { - int pid = exim_fork(US"tls proxy interproc"); + int pid = exim_fork(US"tls-proxy-interproc"); if (pid == 0) /* child; fork again to disconnect totally */ { - testharness_pause_ms(100); /* let parent debug out */ /* does not return */ smtp_proxy_tls(sx->cctx.tls_ctx, sx->buffer, sizeof(sx->buffer), pfd, ob->command_timeout); @@ -4290,7 +4347,6 @@ propagate it from the initial if (pid > 0) /* parent */ { - DEBUG(D_transport) debug_printf("proxy-proc inter-pid %d\n", pid); close(pfd[0]); /* tidy the inter-proc to disconn the proxy proc */ waitpid(pid, NULL, 0); @@ -4300,7 +4356,7 @@ propagate it from the initial sx->cctx.sock = -1; continue_transport = NULL; continue_hostname = NULL; - return yield; + goto TIDYUP; } log_write(0, LOG_PANIC_DIE, "fork failed"); } @@ -4337,10 +4393,13 @@ This change is being made on 31-Jul-98. After over a year of trouble-free operation, the old commented-out code was removed on 17-Sep-99. */ SEND_QUIT: -#ifdef TCP_CORK -(void) setsockopt(sx->cctx.sock, IPPROTO_TCP, TCP_CORK, US &on, sizeof(on)); +if (sx->send_quit) + { +#ifdef EXIM_TCP_CORK + (void) setsockopt(sx->cctx.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on)); #endif -if (sx->send_quit) (void)smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n"); + (void)smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n"); + } END_OFF: @@ -4375,9 +4434,38 @@ if (sx->send_quit) (void) event_raise(tblock->event_action, US"tcp:close", NULL); #endif +#ifdef SUPPORT_DANE +if (dane_held) + { + sx->first_addr = NULL; + for (address_item * a = sx->addrlist->next; a; a = a->next) + if (a->transport_return == DANE) + { + a->transport_return = PENDING_DEFER; + if (!sx->first_addr) + { + /* Remember the new start-point in the addrlist, for smtp_setup_conn() + to get the domain string for SNI */ + + sx->first_addr = a; + DEBUG(D_transport) debug_printf("DANE: go-around for %s\n", a->domain); + } + } + goto DANE_DOMAINS; + } +#endif + continue_transport = NULL; continue_hostname = NULL; return yield; + +TIDYUP: +#ifdef SUPPORT_DANE +if (dane_held) for (address_item * a = sx->addrlist->next; a; a = a->next) + if (a->transport_return == DANE) + a->transport_return = PENDING_DEFER; +#endif +return yield; } @@ -4530,6 +4618,22 @@ DEBUG(D_transport) cutthrough.cctx.sock >= 0 ? cutthrough.cctx.sock : 0); } +/* Check the restrictions on line length */ + +if (max_received_linelength > ob->message_linelength_limit) + { + struct timeval now; + gettimeofday(&now, NULL); + + for (address_item * addr = addrlist; addr; addr = addr->next) + if (addr->transport_return == DEFER) + addr->transport_return = PENDING_DEFER; + + set_errno_nohost(addrlist, ERRNO_SMTPFORMAT, + US"message has lines too long for transport", FAIL, TRUE, &now); + goto END_TRANSPORT; + } + /* Set the flag requesting that these hosts be added to the waiting database if the delivery fails temporarily or if we are running with queue_smtp or a 2-stage queue run. This gets unset for certain @@ -5170,7 +5274,12 @@ retry_non_continued: #ifndef DISABLE_EVENT /* If the last host gave a defer raise a per-message event */ - if (!nexthost && (message_defer || rc == DEFER)) + if ( !( nexthost + && unexpired_hosts_tried < ob->hosts_max_try + && total_hosts_tried < ob->hosts_max_try_hardlimit + ) + && (message_defer || rc == DEFER) + ) deferred_event_raise(first_addr, host, US"msg:defer"); #endif }