X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=src%2Fsrc%2Ftransports%2Fsmtp.c;h=df94eebde581f3526b6fb4379d18115020ec4df1;hb=6707bfa9fb78858de938a1abca2846c820c5ded7;hp=d00f15fe1768da1a1dbf97253108c698c5824804;hpb=9f6b3bf5187562bac4c96e3ed6a17740d01489fa;p=user%2Fhenk%2Fcode%2Fexim.git diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index d00f15fe1..df94eebde 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -5,6 +5,7 @@ /* Copyright (c) The Exim Maintainers 2020 - 2022 */ /* Copyright (c) University of Cambridge 1995 - 2018 */ /* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ #include "../exim.h" #include "smtp.h" @@ -113,7 +114,7 @@ optionlist smtp_transport_options[] = { { "interface", opt_stringptr, LOFF(interface) }, { "keepalive", opt_bool, LOFF(keepalive) }, { "lmtp_ignore_quota", opt_bool, LOFF(lmtp_ignore_quota) }, - { "max_rcpt", opt_int | opt_public, + { "max_rcpt", opt_stringptr | opt_public, OPT_OFF(transport_instance, max_addresses) }, { "message_linelength_limit", opt_int, LOFF(message_linelength_limit) }, { "multi_domain", opt_expand_bool | opt_public, @@ -275,7 +276,7 @@ struct list for (struct list * l = list; l < list + nelem(list); l++) if (!*l->re) - *l->re = regex_must_compile(l->string, FALSE, TRUE); + *l->re = regex_must_compile(l->string, MCS_NOFLAGS, TRUE); } @@ -625,8 +626,8 @@ if (suffix) else message = string_fmt_append(message, " %s", exim_errstr(basic_errno)); -log_write(0, LOG_MAIN, "%s", string_from_gstring(message)); -deliver_msglog("%s %s\n", tod_stamp(tod_log), message->s); +log_write(0, LOG_MAIN, "%Y", message); +deliver_msglog("%s %.*s\n", tod_stamp(tod_log), message->ptr, message->s); } static void @@ -764,6 +765,28 @@ return TRUE; } +/* Grab a string differentiating server behind a loadbalancer, for TLS +resumption when such servers do not share a session-cache */ + +static void +ehlo_response_lbserver(smtp_context * sx, smtp_transport_options_block * ob) +{ +#if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME) +const uschar * s; +uschar * save_item = iterate_item; + +if (sx->conn_args.have_lbserver) + return; +iterate_item = sx->buffer; +s = expand_cstring(ob->host_name_extract); +iterate_item = save_item; +sx->conn_args.host_lbserver = s && !*s ? NULL : s; +sx->conn_args.have_lbserver = TRUE; +#endif +} + + + /******************************************************************************/ #ifdef EXPERIMENTAL_ESMTP_LIMITS @@ -833,10 +856,12 @@ ehlo_limits_apply(sx, sx->peer_limit_mail, sx->peer_limit_rcpt, static void ehlo_cache_limits_apply(smtp_context * sx) { +# ifndef DISABLE_PIPE_CONNECT ehlo_limits_apply(sx, sx->ehlo_resp.limit_mail, sx->ehlo_resp.limit_rcpt, sx->ehlo_resp.limit_rcptdom); +# endif } -#endif +#endif /*EXPERIMENTAL_ESMTP_LIMITS*/ /******************************************************************************/ @@ -867,11 +892,11 @@ write_ehlo_cache_entry(smtp_context * sx) { open_db dbblock, * dbm_file; -#ifdef EXPERIMENTAL_ESMTP_LIMITS +# ifdef EXPERIMENTAL_ESMTP_LIMITS sx->ehlo_resp.limit_mail = sx->peer_limit_mail; sx->ehlo_resp.limit_rcpt = sx->peer_limit_rcpt; sx->ehlo_resp.limit_rcptdom = sx->peer_limit_rcptdom; -#endif +# endif if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE))) { @@ -879,7 +904,7 @@ if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE))) dbdata_ehlo_resp er = { .data = sx->ehlo_resp }; HDEBUG(D_transport) -#ifdef EXPERIMENTAL_ESMTP_LIMITS +# ifdef EXPERIMENTAL_ESMTP_LIMITS if (sx->ehlo_resp.limit_mail || sx->ehlo_resp.limit_rcpt || sx->ehlo_resp.limit_rcptdom) debug_printf("writing clr %04x/%04x cry %04x/%04x lim %05d/%05d/%05d\n", sx->ehlo_resp.cleartext_features, sx->ehlo_resp.cleartext_auths, @@ -887,7 +912,7 @@ if ((dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE))) sx->ehlo_resp.limit_mail, sx->ehlo_resp.limit_rcpt, sx->ehlo_resp.limit_rcptdom); else -#endif +# endif debug_printf("writing clr %04x/%04x cry %04x/%04x\n", sx->ehlo_resp.cleartext_features, sx->ehlo_resp.cleartext_auths, sx->ehlo_resp.crypted_features, sx->ehlo_resp.crypted_auths); @@ -906,6 +931,16 @@ if ( sx->early_pipe_active && (dbm_file = dbfn_open(US"misc", O_RDWR, &dbblock, TRUE, TRUE))) { uschar * ehlo_resp_key = ehlo_cache_key(sx); + HDEBUG(D_transport) + { + dbdata_ehlo_resp * er; + + if (!(er = dbfn_read_enforce_length(dbm_file, ehlo_resp_key, sizeof(dbdata_ehlo_resp)))) + debug_printf("no ehlo-resp record!\n"); + else + debug_printf("ehlo-resp record is %d seconds old\n", time(NULL) - er->time_stamp); + } + dbfn_delete(dbm_file, ehlo_resp_key); dbfn_close(dbm_file); } @@ -936,7 +971,7 @@ else else { DEBUG(D_transport) -#ifdef EXPERIMENTAL_ESMTP_LIMITS +# ifdef EXPERIMENTAL_ESMTP_LIMITS if (er->data.limit_mail || er->data.limit_rcpt || er->data.limit_rcptdom) debug_printf("EHLO response bits from cache:" " cleartext 0x%04x/0x%04x crypted 0x%04x/0x%04x lim %05d/%05d/%05d\n", @@ -944,16 +979,16 @@ else er->data.crypted_features, er->data.crypted_auths, er->data.limit_mail, er->data.limit_rcpt, er->data.limit_rcptdom); else -#endif +# endif debug_printf("EHLO response bits from cache:" " cleartext 0x%04x/0x%04x crypted 0x%04x/0x%04x\n", er->data.cleartext_features, er->data.cleartext_auths, er->data.crypted_features, er->data.crypted_auths); sx->ehlo_resp = er->data; -#ifdef EXPERIMENTAL_ESMTP_LIMITS +# ifdef EXPERIMENTAL_ESMTP_LIMITS ehlo_cache_limits_apply(sx); -#endif +# endif dbfn_close(dbm_file); return TRUE; } @@ -965,7 +1000,7 @@ return FALSE; /* Return an auths bitmap for the set of AUTH methods offered by the server -which match our authenticators. */ +which match our client-side authenticators. */ static unsigned short study_ehlo_auths(smtp_context * sx) @@ -976,7 +1011,7 @@ uschar authnum; unsigned short authbits = 0; if (!sx->esmtp) return 0; -if (!regex_AUTH) regex_AUTH = regex_must_compile(AUTHS_REGEX, FALSE, TRUE); +if (!regex_AUTH) regex_AUTH = regex_must_compile(AUTHS_REGEX, MCS_NOFLAGS, TRUE); if (!regex_match_and_setup(regex_AUTH, sx->buffer, 0, -1)) return 0; expand_nmax = -1; /* reset */ names = string_copyn(expand_nstring[1], expand_nlength[1]); @@ -991,7 +1026,7 @@ for (au = auths, authnum = 0; au; au = au->next, authnum++) if (au->client) } DEBUG(D_transport) - debug_printf("server offers %s AUTH, methods '%s', bitmap 0x%04x\n", + debug_printf("server offers %s AUTH, methods '%s', usable-bitmap 0x%04x\n", tls_out.active.sock >= 0 ? "crypted" : "plaintext", names, authbits); if (tls_out.active.sock >= 0) @@ -1040,6 +1075,8 @@ if (pending_BANNER) if (tls_out.active.sock >= 0) rc = DEFER; goto fail; } + /*XXX EXPERIMENTAL_ESMTP_LIMITS ? */ + ehlo_response_lbserver(sx, sx->conn_args.ob); } if (pending_EHLO) @@ -1068,11 +1105,10 @@ if (pending_EHLO) | OPTION_CHUNKING | OPTION_PRDR | OPTION_DSN | OPTION_PIPE | OPTION_SIZE | OPTION_UTF8 | OPTION_EARLY_PIPE ); -#ifdef EXPERIMENTAL_ESMTP_LIMITS +# ifdef EXPERIMENTAL_ESMTP_LIMITS if (tls_out.active.sock >= 0 || !(peer_offered & OPTION_TLS)) ehlo_response_limits_read(sx); -#endif -/*XXX RESUMP - EHLO-resp avail here int sx->buffer */ +# endif if ( peer_offered != sx->peer_offered || (authbits = study_ehlo_auths(sx)) != *ap) { @@ -1089,11 +1125,14 @@ if (pending_EHLO) write_ehlo_cache_entry(sx); } else + { invalidate_ehlo_cache_entry(sx); + sx->early_pipe_active = FALSE; /* cancel further early-pipe on this conn */ + } return OK; /* just carry on */ } -#ifdef EXPERIMENTAL_ESMTP_LIMITS +# ifdef EXPERIMENTAL_ESMTP_LIMITS /* If we are handling LIMITS, compare the actual EHLO LIMITS values with the cached values and invalidate cache if different. OK to carry on with connect since values are advisory. */ @@ -1117,7 +1156,7 @@ if (pending_EHLO) invalidate_ehlo_cache_entry(sx); } } -#endif +# endif } return OK; @@ -1451,7 +1490,7 @@ rc = (au->info->clientcode)(au, sx, ob->command_timeout, sx->buffer, sizeof(sx->buffer)); sx->outblock.authenticating = FALSE; driver_srcfile = authenticator_name = NULL; driver_srcline = 0; -DEBUG(D_transport) debug_printf("%s authenticator yielded %d\n", au->name, rc); +DEBUG(D_transport) debug_printf("%s authenticator yielded %s\n", au->name, rc_names[rc]); /* A temporary authentication failure must hold up delivery to this host. After a permanent authentication failure, we carry on @@ -1475,10 +1514,25 @@ switch(rc) /* Failure after reading a response */ case FAIL: + { + uschar * logmsg = NULL; + if (errno != 0 || sx->buffer[0] != '5') return FAIL; - log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s", - au->name, host->name, host->address, sx->buffer); +#ifndef DISABLE_EVENT + { + uschar * save_name = sender_host_authenticated; + sender_host_authenticated = au->name; + if ((logmsg = event_raise(sx->conn_args.tblock->event_action, US"auth:fail", + sx->buffer, NULL))) + log_write(0, LOG_MAIN, "%s", logmsg); + sender_host_authenticated = save_name; + } +#endif + if (!logmsg) + log_write(0, LOG_MAIN, "%s authenticator failed H=%s [%s] %s", + au->name, host->name, host->address, sx->buffer); break; + } /* Failure by some other means. In effect, the authenticator decided it wasn't prepared to handle this case. Typically this @@ -1538,7 +1592,7 @@ f.smtp_authenticated = FALSE; client_authenticator = client_authenticated_id = client_authenticated_sender = NULL; if (!regex_AUTH) - regex_AUTH = regex_must_compile(AUTHS_REGEX, FALSE, TRUE); + regex_AUTH = regex_must_compile(AUTHS_REGEX, MCS_NOFLAGS, TRUE); /* Is the server offering AUTH? */ @@ -1824,85 +1878,63 @@ pcre2_match_data * md = pcre2_match_data_create(1, pcre_gen_ctx); #ifndef DISABLE_TLS if ( checks & OPTION_TLS && pcre2_match(regex_STARTTLS, - (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) + (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0) #endif checks &= ~OPTION_TLS; if ( checks & OPTION_IGNQ && pcre2_match(regex_IGNOREQUOTA, - (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) + (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0) checks &= ~OPTION_IGNQ; if ( checks & OPTION_CHUNKING && pcre2_match(regex_CHUNKING, - (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) + (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0) checks &= ~OPTION_CHUNKING; #ifndef DISABLE_PRDR if ( checks & OPTION_PRDR && pcre2_match(regex_PRDR, - (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) + (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0) #endif checks &= ~OPTION_PRDR; #ifdef SUPPORT_I18N if ( checks & OPTION_UTF8 && pcre2_match(regex_UTF8, - (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) + (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0) #endif checks &= ~OPTION_UTF8; if ( checks & OPTION_DSN && pcre2_match(regex_DSN, - (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) + (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0) checks &= ~OPTION_DSN; if ( checks & OPTION_PIPE && pcre2_match(regex_PIPELINING, - (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) + (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0) checks &= ~OPTION_PIPE; if ( checks & OPTION_SIZE && pcre2_match(regex_SIZE, - (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) + (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0) checks &= ~OPTION_SIZE; #ifndef DISABLE_PIPE_CONNECT if ( checks & OPTION_EARLY_PIPE && pcre2_match(regex_EARLY_PIPE, - (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_mtc_ctx) < 0) + (PCRE2_SPTR)buf, bsize, 0, PCRE_EOPT, md, pcre_gen_mtc_ctx) < 0) #endif checks &= ~OPTION_EARLY_PIPE; -pcre2_match_data_free(md); +/* pcre2_match_data_free(md); gen ctx needs no free */ /* debug_printf("%s: found 0x%04x\n", __FUNCTION__, checks); */ return checks; } -/* Grab a string differentiating server behind a loadbalancer, for TLS -resumption when such servers do not share a session-cache */ - -static const uschar * -ehlo_response_lbserver(uschar * buffer, smtp_transport_options_block * ob) -{ -#if !defined(DISABLE_TLS) && !defined(DISABLE_TLS_RESUME) -/* want to make this a main-section option */ -const uschar * s; -uschar * save_item = iterate_item; - -iterate_item = buffer; -s = expand_cstring(ob->host_name_extract); -iterate_item = save_item; -return s && !*s ? NULL : s; -#else -return NULL; -#endif -} - - - /* Callback for emitting a BDAT data chunk header. If given a nonzero size, first flush any buffered SMTP commands @@ -2102,8 +2134,9 @@ sx->dane_required = verify_check_given_host(CUSS &ob->hosts_require_dane, sx->conn_args.host) == OK; #endif -if ((sx->max_mail = sx->conn_args.tblock->connection_max_messages) == 0) sx->max_mail = 999999; -if ((sx->max_rcpt = sx->conn_args.tblock->max_addresses) == 0) sx->max_rcpt = 999999; +if ((sx->max_mail = sx->conn_args.tblock->connection_max_messages) == 0) + sx->max_mail = UNLIMITED_ADDRS; +sx->max_rcpt = expand_max_rcpt(sx->conn_args.tblock->max_addresses); sx->igquotstr = US""; if (!sx->helo_data) sx->helo_data = ob->helo_data; @@ -2545,8 +2578,6 @@ goto SEND_QUIT; : 0 ) #endif -/*XXX RESUMP - sx->buffer has the EHLO-resp, but only if not early-pipe and not continued-connection */ -/* maybe disable resump on cont? */ ); #ifdef EXPERIMENTAL_ESMTP_LIMITS if (tls_out.active.sock >= 0 || !(sx->peer_offered & OPTION_TLS)) @@ -2563,13 +2594,13 @@ goto SEND_QUIT; if ( (sx->peer_offered & (OPTION_PIPE | OPTION_EARLY_PIPE)) == (OPTION_PIPE | OPTION_EARLY_PIPE)) { - DEBUG(D_transport) debug_printf("PIPE_CONNECT usable in future for this IP\n"); + DEBUG(D_transport) debug_printf("PIPECONNECT usable in future for this IP\n"); sx->ehlo_resp.cleartext_auths = study_ehlo_auths(sx); write_ehlo_cache_entry(sx); } } #endif - sx->conn_args.host_lbserver = ehlo_response_lbserver(sx->buffer, ob); + ehlo_response_lbserver(sx, ob); } /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */ @@ -2672,8 +2703,6 @@ if ( smtp_peer_options & OPTION_TLS sx->early_pipe_active = FALSE; goto PIPE_CONNECT_RETRY; } -/*XXX RESUMP - does this leave the EHLO-resp anywhere? Yes, sx->buffer */ - sx->conn_args.host_lbserver = ehlo_response_lbserver(sx->buffer, ob); } #endif @@ -2703,7 +2732,6 @@ if ( smtp_peer_options & OPTION_TLS TLS_NEGOTIATE: { sx->conn_args.sending_ip_address = sending_ip_address; - /*XXX RESUMP want LB-server info here */ if (!tls_client_start(&sx->cctx, &sx->conn_args, sx->addrlist, &tls_out, &tls_errstr)) { /* TLS negotiation failed; give an error. From outside, this function may @@ -2805,8 +2833,9 @@ if (tls_out.active.sock >= 0) #ifdef EXPERIMMENTAL_ESMTP_LIMITS /* As we are about to send another EHLO, forget any LIMITS received so far. */ sx->peer_limit_mail = sx->peer_limit_rcpt = sx->peer_limit_rcptdom = 0; - if ((sx->max_mail = sx->conn_args.tblock->connection_max_message) == 0) sx->max_mail = 999999; - if ((sx->max_rcpt = sx->conn_args.tblock->max_addresses) == 0) sx->max_rcpt = 999999; + if ((sx->max_mail = sx->conn_args.tblock->connection_max_message) == 0) + sx->max_mail = UNLIMITED_ADDRS; + sx->max_rcpt = expand_max_rcpt(sx->conn_args.tblock->max_addresses); sx->single_rcpt_domain = FALSE; #endif @@ -3804,7 +3833,7 @@ if (tblock->filter_command) yield ERROR. */ if (!transport_set_up_command(&transport_filter_argv, - tblock->filter_command, TRUE, DEFER, addrlist, FALSE, + tblock->filter_command, TSUC_EXPAND_ARGS, DEFER, addrlist, string_sprintf("%.50s transport filter", tblock->name), NULL)) { set_errno_nohost(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER, @@ -3995,7 +4024,7 @@ else if (smtp_peer_options & OPTION_CHUNKING) debug_printf(" will write message using CHUNKING\n"); else - debug_printf(" SMTP>> writing message and terminating \".\"\n"); + debug_printf(" SMTP>> (writing message)\n"); transport_count = 0; #ifndef DISABLE_DKIM @@ -4682,7 +4711,10 @@ if (sx->completed_addr && sx->ok && sx->send_quit) open, we must shut down TLS. Not all MTAs allow for the continuation of the SMTP session when TLS is shut down. We test for this by sending a new EHLO. If we don't get a good response, we don't attempt to pass - the socket on. */ + the socket on. + NB: TLS close is *required* per RFC 9266 when tls-exporter info has + been used, which we do under TLSv1.3 for the gsasl SCRAM*PLUS methods. + But we were always doing it anyway. */ tls_close(sx->cctx.tls_ctx, sx->send_tlsclose ? TLS_SHUTDOWN_WAIT : TLS_SHUTDOWN_WONLY); @@ -4954,7 +4986,7 @@ smtp_transport_closedown(transport_instance *tblock) { smtp_transport_options_block * ob = SOB tblock->options_block; client_conn_ctx cctx; -smtp_context sx; +smtp_context sx = {0}; uschar buffer[256]; uschar inbuffer[4096]; uschar outbuffer[16]; @@ -5299,6 +5331,17 @@ retry_non_continued: uschar *retry_message_key = NULL; uschar *serialize_key = NULL; + /* Deal slightly better with a possible Linux kernel bug that results + in intermittent TFO-conn fails deep into the TCP flow. Bug 2907 tracks. + Hack: Clear TFO option for any further hosts on this tpt run. */ + + if (total_hosts_tried > 0) + { + DEBUG(D_transport|D_acl|D_v) + debug_printf("Clearing TFO as not first host for message\n"); + ob->hosts_try_fastopen = US""; + } + /* Default next host is next host. :-) But this can vary if the hosts_max_try limit is hit (see below). It may also be reset if a host address is looked up here (in case the host was multihomed). */