X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/cf1376206284f2a4f11e32d931d4aade34c206c5..b6e0e128c9963c1b88717a150695603baa9f035a:/src/src/smtp_in.c?ds=sidebyside diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index aeaffeb37..9f9ff7016 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) The Exim Maintainers 2020 - 2023 */ +/* Copyright (c) The Exim Maintainers 2020 - 2024 */ /* 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 */ @@ -72,7 +72,7 @@ enum { HELO_CMD, EHLO_CMD, DATA_CMD, /* These are listed in the pipelining */ VRFY_CMD, EXPN_CMD, NOOP_CMD, /* RFC as requiring synchronization */ - ETRN_CMD, /* This by analogy with TURN from the RFC */ + ATRN_CMD, ETRN_CMD, /* This by analogy with TURN from the RFC */ STARTTLS_CMD, /* Required by the STARTTLS RFC */ TLS_AUTH_CMD, /* auto-command at start of SSL */ #ifdef EXPERIMENTAL_XCLIENT @@ -86,6 +86,9 @@ enum { /* These commands need not be synchronized when pipelining */ MAIL_CMD, RCPT_CMD, RSET_CMD, +#ifndef DISABLE_WELLKNOWN + WELLKNOWN_CMD, +#endif /* This is a dummy to identify the non-sync commands when not pipelining */ @@ -121,7 +124,8 @@ enum { /* These are specials that don't correspond to actual commands */ EOF_CMD, OTHER_CMD, BADARG_CMD, BADCHAR_CMD, BADSYN_CMD, - TOO_MANY_NONMAIL_CMD }; + TOO_MANY_NONMAIL_CMD +}; /* This is a convenience macro for adding the identity of an SMTP command @@ -227,10 +231,14 @@ static smtp_cmd_list cmd_list[] = { { "bdat", sizeof("bdat")-1, BDAT_CMD, TRUE, TRUE }, { "quit", sizeof("quit")-1, QUIT_CMD, FALSE, TRUE }, { "noop", sizeof("noop")-1, NOOP_CMD, TRUE, FALSE }, + { "atrn", sizeof("atrn")-1, ATRN_CMD, TRUE, FALSE }, { "etrn", sizeof("etrn")-1, ETRN_CMD, TRUE, FALSE }, { "vrfy", sizeof("vrfy")-1, VRFY_CMD, TRUE, FALSE }, { "expn", sizeof("expn")-1, EXPN_CMD, TRUE, FALSE }, - { "help", sizeof("help")-1, HELP_CMD, TRUE, FALSE } + { "help", sizeof("help")-1, HELP_CMD, TRUE, FALSE }, +#ifndef DISABLE_WELLKNOWN + { "wellknown", sizeof("wellknown")-1, WELLKNOWN_CMD, TRUE, FALSE }, +#endif }; /* This list of names is used for performing the smtp_no_mail logging action. */ @@ -253,6 +261,9 @@ uschar * smtp_names[] = [SCH_RSET] = US"RSET", [SCH_STARTTLS] = US"STARTTLS", [SCH_VRFY] = US"VRFY", +#ifndef DISABLE_WELLKNOWN + [SCH_WELLKNOWN] = US"WELLKNOWN", +#endif #ifdef EXPERIMENTAL_XCLIENT [SCH_XCLIENT] = US"XCLIENT", #endif @@ -340,7 +351,6 @@ static int smtp_had_error; /* forward declarations */ static int smtp_read_command(BOOL check_sync, unsigned buffer_lim); -static int synprot_error(int type, int code, uschar *data, uschar *errmess); static void smtp_quit_handler(uschar **, uschar **); static void smtp_rset_handler(void); @@ -454,6 +464,23 @@ smtp_had_eof = smtp_had_error = 0; +#ifndef DISABLE_DKIM +/* Feed received message data to the dkim module */ +/*XXX maybe a global dkim_info? */ +void +smtp_verify_feed(const uschar * s, unsigned n) +{ +static misc_module_info * dkim_mi = NULL; +typedef void (*fn_t)(const uschar *, int); + +if (!dkim_mi && !(dkim_mi = misc_mod_findonly(US"dkim"))) + return; + +(((fn_t *) dkim_mi->functions)[DKIM_VERIFY_FEED]) (s, n); +} +#endif + + /* Refill the buffer, and notify DKIM verification code. Return false for error or EOF. */ @@ -497,7 +524,7 @@ if (rc <= 0) return FALSE; } #ifndef DISABLE_DKIM -dkim_exim_verify_feed(smtp_inbuffer, rc); +smtp_verify_feed(smtp_inbuffer, rc); #endif smtp_inend = smtp_inbuffer + rc; smtp_inptr = smtp_inbuffer; @@ -560,7 +587,7 @@ int n = smtp_inend - smtp_inptr; if (n > lim) n = lim; if (n > 0) - dkim_exim_verify_feed(smtp_inptr, n); + smtp_verify_feed(smtp_inptr, n); #endif } @@ -716,19 +743,24 @@ bdat_getc(unsigned lim) uschar * user_msg = NULL; uschar * log_msg; -for(;;) - { #ifndef DISABLE_DKIM - unsigned dkim_save; +misc_module_info * dkim_info = misc_mod_findonly(US"dkim"); +typedef void (*dkim_pause_t)(BOOL); +dkim_pause_t dkim_pause; + +dkim_pause = dkim_info + ? ((dkim_pause_t *) dkim_info->functions)[DKIM_VERIFY_PAUSE] : NULL; #endif +for(;;) + { + if (chunking_data_left > 0) return lwr_receive_getc(chunking_data_left--); bdat_pop_receive_functions(); #ifndef DISABLE_DKIM - dkim_save = dkim_collect_input; - dkim_collect_input = 0; + if (dkim_pause) dkim_pause(TRUE); #endif /* Unless PIPELINING was offered, there should be no next command @@ -757,9 +789,7 @@ for(;;) if (chunking_state == CHUNKING_LAST) { #ifndef DISABLE_DKIM - dkim_collect_input = dkim_save; - dkim_exim_verify_feed(NULL, 0); /* notify EOD */ - dkim_collect_input = 0; + smtp_verify_feed(NULL, 0); /* notify EOD */ #endif return EOD; } @@ -833,7 +863,7 @@ next_cmd: bdat_push_receive_functions(); #ifndef DISABLE_DKIM - dkim_collect_input = dkim_save; + if (dkim_pause) dkim_pause(FALSE); #endif break; /* to top of main loop */ } @@ -1227,7 +1257,7 @@ for (smtp_cmd_list * p = cmd_list; p < cmd_list + nelem(cmd_list); p++) follow the sender address. */ smtp_cmd_argument = smtp_cmd_buffer + p->len; - while (isspace(*smtp_cmd_argument)) smtp_cmd_argument++; + Uskip_whitespace(&smtp_cmd_argument); Ustrcpy(smtp_data_buffer, smtp_cmd_argument); smtp_cmd_data = smtp_data_buffer; @@ -1345,14 +1375,17 @@ const uschar * hostname = sender_fullhost gstring * g = string_catn(NULL, US"SMTP connection", 15); if (LOGGING(connection_id)) - g = string_fmt_append(g, " Ci=%lu", connection_id); + g = string_fmt_append(g, " Ci=%s", connection_id); g = string_catn(g, US" from ", 6); if (host_checking) g = string_cat(g, hostname); else if (f.sender_host_unknown || f.sender_host_notsocket) - g = string_cat(g, sender_ident); + g = string_cat(g, sender_ident ? sender_ident : US"NULL"); + +else if (atrn_mode) + g = string_append(g, 2, hostname, US" (ODMR customer)"); else if (f.is_inetd) g = string_append(g, 2, hostname, US" (via inetd)"); @@ -1671,28 +1704,7 @@ bmi_run = 0; bmi_verdicts = NULL; #endif dnslist_domain = dnslist_matched = NULL; -#ifdef SUPPORT_SPF -spf_header_comment = spf_received = spf_result = spf_smtp_comment = NULL; -spf_result_guessed = FALSE; -#endif -#ifndef DISABLE_DKIM -dkim_cur_signer = dkim_signers = -dkim_signing_domain = dkim_signing_selector = dkim_signatures = NULL; -dkim_cur_signer = dkim_signers = dkim_signing_domain = dkim_signing_selector = NULL; -f.dkim_disable_verify = FALSE; -dkim_collect_input = 0; -dkim_verify_overall = dkim_verify_status = dkim_verify_reason = NULL; -dkim_key_length = 0; -#endif -#ifdef SUPPORT_DMARC -f.dmarc_has_been_checked = f.dmarc_disable_verify = f.dmarc_enable_forensic = FALSE; -dmarc_domain_policy = dmarc_status = dmarc_status_text = -dmarc_used_domain = NULL; -#endif -#ifdef EXPERIMENTAL_ARC -arc_state = arc_state_reason = NULL; -arc_received_instance = 0; -#endif + dsn_ret = 0; dsn_envid = NULL; deliver_host = deliver_host_address = NULL; /* Can be set by ACL */ @@ -1727,6 +1739,7 @@ while (acl_warn_logged) store_free(this); } +misc_mod_smtp_reset(); message_tidyup(); store_reset(reset_point); @@ -1876,10 +1889,11 @@ while (done <= 0) /* Check maximum number allowed */ - if (recipients_max > 0 && recipients_count + 1 > recipients_max) + if ( recipients_max_expanded > 0 + && recipients_count + 1 > recipients_max_expanded) /* The function moan_smtp_batch() does not return. */ moan_smtp_batch(smtp_cmd_buffer, "%s too many recipients", - recipients_max_reject? "552": "452"); + recipients_max_reject ? "552": "452"); /* Apply SMTP rewrite, then extract address. Don't allow "<>" as a recipient address */ @@ -1938,13 +1952,13 @@ while (done <= 0) break; - /* The VRFY, EXPN, HELP, ETRN, and NOOP commands are ignored. */ + /* The VRFY, EXPN, HELP, ETRN, ATRN and NOOP commands are ignored. */ - case VRFY_CMD: - case EXPN_CMD: - case HELP_CMD: - case NOOP_CMD: - case ETRN_CMD: + case VRFY_CMD: case EXPN_CMD: case HELP_CMD: case NOOP_CMD: + case ETRN_CMD: case ATRN_CMD: +#ifndef DISABLE_WELLKNOWN + case WELLKNOWN_CMD: +#endif bsmtp_transaction_linecount = receive_linecount; break; @@ -2053,9 +2067,12 @@ log_connect_tls_drop(const uschar * what, const uschar * log_msg) { if (log_reject_target) { +#ifdef DISABLE_TLS + uschar * tls = NULL; +#else gstring * g = s_tlslog(NULL); uschar * tls = string_from_gstring(g); - +#endif log_write(L_connection_reject, log_reject_target, "%s%s%s dropped by %s%s%s", LOGGING(dnssec) && sender_host_dnssec ? US" DS" : US"", @@ -2067,6 +2084,132 @@ if (log_reject_target) } + + +/* IP options: log and reject the connection. + +Deal with any IP options that are set. On the systems I have looked at, +the value of MAX_IPOPTLEN has been 40, meaning that there should never be +more logging data than will fit in big_buffer. Nevertheless, after somebody +questioned this code, I've added in some paranoid checking. + +Given the disuse of options on the internet as of 2024 I'm tempted to +drop the detailed parsing and logging. */ + +#ifdef GLIBC_IP_OPTIONS +# if (!defined __GLIBC__) || (__GLIBC__ < 2) +# define OPTSTYLE 1 +# else +# define OPTSTYLE 2 +# endif +#elif defined DARWIN_IP_OPTIONS +# define OPTSTYLE 2 +#else +# define OPTSTYLE 3 +# endif + +#if OPTSTYLE == 1 +# define EXIM_IP_OPT_T struct ip_options +# define OPTSTART (ipopt->__data) +#elif OPTSTYLE == 2 +# define EXIM_IP_OPT_T struct ip_opts +# define OPTSTART (ipopt->ip_opts); +#else +# define EXIM_IP_OPT_T struct ipoption +# define OPTSTART (ipopt->ipopt_list); +#endif + +static BOOL +smtp_in_reject_options(EXIM_IP_OPT_T * ipopt, EXIM_SOCKLEN_T optlen) +{ +uschar * p, * pend = big_buffer + big_buffer_size; +uschar * adptr; +int optcount, sprint_len; +struct in_addr addr; +uschar * optstart = US OPTSTART; + +DEBUG(D_receive) debug_printf("IP options exist\n"); + +p = Ustpcpy(big_buffer, "IP options on incoming call:"); + +for (uschar * opt = optstart; opt && opt < US (ipopt) + optlen; ) + switch (*opt) + { + case IPOPT_EOL: + opt = NULL; + break; + + case IPOPT_NOP: + opt++; + break; + + case IPOPT_SSRR: + case IPOPT_LSRR: + if (! +# if OPTSTYLE == 1 + string_format(p, pend-p, " %s [@%s%n", + *opt == IPOPT_SSRR ? "SSRR" : "LSRR", + inet_ntoa(*(struct in_addr *)&ipopt->faddr), + &sprint_len) +# elif OPTSTYLE == 2 + string_format(p, pend-p, " %s [@%s%n", + *opt == IPOPT_SSRR ? "SSRR" : "LSRR", + inet_ntoa(ipopt->ip_dst), + &sprint_len) +# else + string_format(p, pend-p, " %s [@%s%n", + *opt == IPOPT_SSRR ? "SSRR" : "LSRR", + inet_ntoa(ipopt->ipopt_dst), + &sprint_len) +# endif + ) + opt = NULL; + else + { + p += sprint_len; + optcount = (opt[1] - 3) / sizeof(struct in_addr); + adptr = opt + 3; + while (optcount-- > 0) + { + memcpy(&addr, adptr, sizeof(addr)); + if (!string_format(p, pend - p - 1, "%s%s%n", + optcount == 0 ? ":" : "@", inet_ntoa(addr), &sprint_len)) + { opt = NULL; goto bad_srr; } + p += sprint_len; + adptr += sizeof(struct in_addr); + } + *p++ = ']'; + opt += opt[1]; + } +bad_srr: break; + + default: + if (pend - p < 4 + 3*opt[1]) + opt = NULL; + else + { + p = Ustpcpy(p, "[ "); + for (int i = 0; i < opt[1]; i++) + p += sprintf(CS p, "%2.2x ", opt[i]); + *p++ = ']'; + opt += opt[1]; + } + break; + } + +*p = '\0'; +log_write(0, LOG_MAIN, "%s", big_buffer); + +/* Refuse any call with IP options. This is what tcpwrappers 7.5 does. */ + +log_write(0, LOG_MAIN|LOG_REJECT, + "connection from %s refused (IP options)", host_and_ident(FALSE)); + +smtp_printf("554 SMTP service not available\r\n", SP_NO_MORE); +return FALSE; +} + + /************************************************* * Start an SMTP session * *************************************************/ @@ -2115,10 +2258,13 @@ if (!host_checking && !f.sender_host_notsocket) authenticated_by = NULL; #ifndef DISABLE_TLS -tls_in.ver = tls_in.cipher = tls_in.peerdn = NULL; -tls_in.ourcert = tls_in.peercert = NULL; -tls_in.sni = NULL; -tls_in.ocsp = OCSP_NOT_REQ; +if (!atrn_mode) + { + tls_in.ver = tls_in.cipher = tls_in.peerdn = NULL; + tls_in.ourcert = tls_in.peercert = NULL; + tls_in.sni = NULL; + tls_in.ocsp = OCSP_NOT_REQ; + } fl.tls_advertised = FALSE; #endif fl.dsn_advertised = FALSE; @@ -2157,13 +2303,28 @@ call the local functions instead of the standard C ones. */ smtp_buf_init(); -receive_getc = smtp_getc; -receive_getbuf = smtp_getbuf; -receive_get_cache = smtp_get_cache; -receive_hasc = smtp_hasc; -receive_ungetc = smtp_ungetc; -receive_feof = smtp_feof; -receive_ferror = smtp_ferror; +#ifndef DISABLE_TLS +if (atrn_mode && tls_in.active.sock >= 0) + { + receive_getc = tls_getc; + receive_getbuf = tls_getbuf; + receive_get_cache = tls_get_cache; + receive_hasc = tls_hasc; + receive_ungetc = tls_ungetc; + receive_feof = tls_feof; + receive_ferror = tls_ferror; + } +else +#endif + { + receive_getc = smtp_getc; + receive_getbuf = smtp_getbuf; + receive_get_cache = smtp_get_cache; + receive_hasc = smtp_hasc; + receive_ungetc = smtp_ungetc; + receive_feof = smtp_feof; + receive_ferror = smtp_ferror; + } lwr_receive_getc = NULL; lwr_receive_getbuf = NULL; lwr_receive_hasc = NULL; @@ -2171,6 +2332,7 @@ lwr_receive_ungetc = NULL; /* Set up the message size limit; this may be host-specific */ +GET_OPTION("message_size_limit"); thismessage_size_limit = expand_string_integer(message_size_limit, TRUE); if (expand_string_message) { @@ -2226,31 +2388,15 @@ if (!f.sender_host_unknown) #if !HAVE_IPV6 && !defined(NO_IP_OPTIONS) -# ifdef GLIBC_IP_OPTIONS -# if (!defined __GLIBC__) || (__GLIBC__ < 2) -# define OPTSTYLE 1 -# else -# define OPTSTYLE 2 -# endif -# elif defined DARWIN_IP_OPTIONS -# define OPTSTYLE 2 -# else -# define OPTSTYLE 3 -# endif - if (!host_checking && !f.sender_host_notsocket) { # if OPTSTYLE == 1 - EXIM_SOCKLEN_T optlen = sizeof(struct ip_options) + MAX_IPOPTLEN; - struct ip_options *ipopt = store_get(optlen, GET_UNTAINTED); -# elif OPTSTYLE == 2 - struct ip_opts ipoptblock; - struct ip_opts *ipopt = &ipoptblock; - EXIM_SOCKLEN_T optlen = sizeof(ipoptblock); + EXIM_SOCKLEN_T optlen = sizeof(EXIM_IP_OPT_T) + MAX_IPOPTLEN; + EXIM_IP_OPT_T * ipopt = store_get(optlen, GET_UNTAINTED); # else - struct ipoption ipoptblock; - struct ipoption *ipopt = &ipoptblock; - EXIM_SOCKLEN_T optlen = sizeof(ipoptblock); + EXIM_IP_OPT_T ipoptblock; + EXIM_IP_OPT_T * ipopt = &ipoptblock; + EXIM_SOCKLEN_T optlen = sizeof(EXIM_IP_OPT_T); # endif /* Occasional genuine failures of getsockopt() have been seen - for @@ -2262,7 +2408,7 @@ if (!f.sender_host_unknown) DEBUG(D_receive) debug_printf("checking for IP options\n"); - if (getsockopt(fileno(smtp_out), IPPROTO_IP, IP_OPTIONS, US (ipopt), + if (getsockopt(fileno(smtp_out), IPPROTO_IP, IP_OPTIONS, US ipopt, &optlen) < 0) { if (errno != ENOPROTOOPT) @@ -2280,102 +2426,7 @@ if (!f.sender_host_unknown) questioned this code, I've added in some paranoid checking. */ else if (optlen > 0) - { - uschar * p = big_buffer; - uschar * pend = big_buffer + big_buffer_size; - uschar * adptr; - int optcount; - struct in_addr addr; - -# if OPTSTYLE == 1 - uschar * optstart = US (ipopt->__data); -# elif OPTSTYLE == 2 - uschar * optstart = US (ipopt->ip_opts); -# else - uschar * optstart = US (ipopt->ipopt_list); -# endif - - DEBUG(D_receive) debug_printf("IP options exist\n"); - - Ustrcpy(p, "IP options on incoming call:"); - p += Ustrlen(p); - - for (uschar * opt = optstart; opt && opt < US (ipopt) + optlen; ) - switch (*opt) - { - case IPOPT_EOL: - opt = NULL; - break; - - case IPOPT_NOP: - opt++; - break; - - case IPOPT_SSRR: - case IPOPT_LSRR: - if (! -# if OPTSTYLE == 1 - string_format(p, pend-p, " %s [@%s", - (*opt == IPOPT_SSRR)? "SSRR" : "LSRR", - inet_ntoa(*((struct in_addr *)(&(ipopt->faddr))))) -# elif OPTSTYLE == 2 - string_format(p, pend-p, " %s [@%s", - (*opt == IPOPT_SSRR)? "SSRR" : "LSRR", - inet_ntoa(ipopt->ip_dst)) -# else - string_format(p, pend-p, " %s [@%s", - (*opt == IPOPT_SSRR)? "SSRR" : "LSRR", - inet_ntoa(ipopt->ipopt_dst)) -# endif - ) - { - opt = NULL; - break; - } - - p += Ustrlen(p); - optcount = (opt[1] - 3) / sizeof(struct in_addr); - adptr = opt + 3; - while (optcount-- > 0) - { - memcpy(&addr, adptr, sizeof(addr)); - if (!string_format(p, pend - p - 1, "%s%s", - (optcount == 0)? ":" : "@", inet_ntoa(addr))) - { - opt = NULL; - break; - } - p += Ustrlen(p); - adptr += sizeof(struct in_addr); - } - *p++ = ']'; - opt += opt[1]; - break; - - default: - { - if (pend - p < 4 + 3*opt[1]) { opt = NULL; break; } - Ustrcat(p, "[ "); - p += 2; - for (int i = 0; i < opt[1]; i++) - p += sprintf(CS p, "%2.2x ", opt[i]); - *p++ = ']'; - } - opt += opt[1]; - break; - } - - *p = 0; - log_write(0, LOG_MAIN, "%s", big_buffer); - - /* Refuse any call with IP options. This is what tcpwrappers 7.5 does. */ - - log_write(0, LOG_MAIN|LOG_REJECT, - "connection from %s refused (IP options)", host_and_ident(FALSE)); - - smtp_printf("554 SMTP service not available\r\n", SP_NO_MORE); - return FALSE; - } + return smtp_in_reject_options(ipopt); /* Length of options = 0 => there are no options */ @@ -2540,11 +2591,16 @@ if (!f.sender_host_unknown) fl.helo_accept_junk = verify_check_host(&helo_accept_junk_hosts) == OK; } +/* Expand recipients_max, if needed */ + { + uschar * rme = expand_string(recipients_max); + recipients_max_expanded = atoi(CCS rme); + } /* For batch SMTP input we are now done. */ if (smtp_batched_input) return TRUE; -#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS) || defined(EXPERIMETAL_XCLIENT) +#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS) || defined(EXPERIMENTAL_XCLIENT) proxy_session = FALSE; #endif @@ -2563,6 +2619,7 @@ if (proxy_protocol_host()) /* Run the connect ACL if it exists */ user_msg = NULL; +GET_OPTION("acl_smtp_connect"); if (acl_smtp_connect) { int rc; @@ -2609,16 +2666,20 @@ if (user_msg) esclen = codelen - 4; } } -else if (!(s = expand_string(smtp_banner))) +else { - log_write(0, f.expand_string_forcedfail ? LOG_MAIN : LOG_MAIN|LOG_PANIC_DIE, - "Expansion of \"%s\" (smtp_banner) failed: %s", - smtp_banner, expand_string_message); - /* for force-fail */ -#ifndef DISABLE_TLS - if (tls_in.on_connect) tls_close(NULL, TLS_SHUTDOWN_WAIT); -#endif - return FALSE; + GET_OPTION("smtp_banner"); + if (!(s = expand_string(smtp_banner))) + { + log_write(0, f.expand_string_forcedfail ? LOG_MAIN : LOG_MAIN|LOG_PANIC_DIE, + "Expansion of \"%s\" (smtp_banner) failed: %s", + smtp_banner, expand_string_message); + /* for force-fail */ + #ifndef DISABLE_TLS + if (tls_in.on_connect) tls_close(NULL, TLS_SHUTDOWN_WAIT); + #endif + return FALSE; + } } /* Remove any terminating newlines; might as well remove trailing space too */ @@ -2703,7 +2764,8 @@ smtp_printf("%Y", handshake arrived. If so we must have managed a TFO. */ #ifdef TCP_FASTOPEN -if (sender_host_address && !f.sender_host_notsocket) tfo_in_check(); +if (sender_host_address && !f.sender_host_notsocket && !atrn_mode) + tfo_in_check(); #endif return TRUE; @@ -2733,11 +2795,17 @@ Returns: -1 limit of syntax/protocol errors NOT exceeded These values fit in with the values of the "done" variable in the main processing loop in smtp_setup_msg(). */ -static int +int synprot_error(int type, int code, uschar *data, uschar *errmess) { int yield = -1; +#ifndef DISABLE_EVENT +event_raise(event_action, + L_smtp_syntax_error ? US"smtp:fail:syntax" : US"smtp:fail:protocol", + errmess, NULL); +#endif + log_write(type, LOG_MAIN, "SMTP %s error in \"%s\" %s %s", type == L_smtp_syntax_error ? "syntax" : "protocol", string_printing(smtp_cmd_buffer), host_and_ident(TRUE), errmess); @@ -2817,10 +2885,8 @@ if (fl.rcpt_in_progress) We only handle pipelining these responses as far as nonfinal/final groups, not the whole MAIL/RCPT/DATA response set. */ -for (;;) - { - uschar *nl = Ustrchr(msg, '\n'); - if (!nl) +for (uschar * nl;;) + if (!(nl = Ustrchr(msg, '\n'))) { smtp_printf("%.3s%c%.*s%s\r\n", !final, code, final ? ' ':'-', esclen, esc, msg); return; @@ -2837,7 +2903,6 @@ for (;;) msg = nl + 1; Uskip_whitespace(&msg); } - } } @@ -2956,7 +3021,8 @@ smtp_code = rc == FAIL ? acl_wherecodes[where] : US"451"; smtp_message_code(&smtp_code, &codelen, &user_msg, &log_msg, where != ACL_WHERE_VRFY); -/* We used to have sender_address here; however, there was a bug that was not +/* Get info for logging. +We used to have sender_address here; however, there was a bug that was not updating sender_address after a rewrite during a verify. When this bug was fixed, sender_address at this point became the rewritten address. I'm not sure this is what should be logged, so I've changed to logging the unrewritten @@ -2979,9 +3045,9 @@ switch (where) if (where == ACL_WHERE_AUTH) /* avoid logging auth creds */ { - uschar * s; - for (s = smtp_cmd_data; *s && !isspace(*s); ) s++; - lim = s - smtp_cmd_data; /* atop after method */ + uschar * s = smtp_cmd_data; + Uskip_nonwhite(&s); + lim = s - smtp_cmd_data; /* stop after method */ } what = string_sprintf("%s %.*s", acl_wherenames[where], lim, place); } @@ -3179,6 +3245,7 @@ fl.smtp_exit_function_called = TRUE; /* Call the not-QUIT ACL, if there is one, unless no reason is given. */ +GET_OPTION("acl_smtp_notquit"); if (acl_smtp_notquit && reason) { smtp_notquit_reason = reason; @@ -3359,7 +3426,7 @@ Returns: nothing */ static void -smtp_user_msg(uschar *code, uschar *user_msg) +smtp_user_msg(uschar * code, uschar * user_msg) { int len = 3; smtp_message_code(&code, &len, &user_msg, NULL, TRUE); @@ -3376,9 +3443,9 @@ int rc; /* Set up globals for error messages */ -authenticator_name = au->name; -driver_srcfile = au->srcfile; -driver_srcline = au->srcline; +authenticator_name = au->drinst.name; +driver_srcfile = au->drinst.srcfile; +driver_srcline = au->drinst.srcline; /* Run the checking code, passing the remainder of the command line as data. Initials the $auth variables as empty. Initialize $0 empty and set @@ -3396,7 +3463,10 @@ for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL; expand_nmax = 0; expand_nlength[0] = 0; /* $0 contains nothing */ -rc = (au->info->servercode)(au, smtp_cmd_data); + { + auth_info * ai = au->drinst.info; + rc = (ai->servercode)(au, smtp_cmd_data); + } if (au->set_id) set_id = expand_string(au->set_id); expand_nmax = -1; /* Reset numeric variables */ for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL; /* Reset $auth */ @@ -3425,7 +3495,7 @@ switch(rc) if (!au->set_id || set_id) /* Complete success */ { if (set_id) authenticated_id = string_copy_perm(set_id, TRUE); - sender_host_authenticated = au->name; + sender_host_authenticated = au->drinst.name; sender_host_auth_pubname = au->public_name; authentication_failed = FALSE; authenticated_fail_id = NULL; /* Impossible to already be set? */ @@ -3515,6 +3585,7 @@ smtp_quit_handler(uschar ** user_msgp, uschar ** log_msgp) HAD(SCH_QUIT); f.smtp_in_quit = TRUE; incomplete_transaction_log(US"QUIT"); +GET_OPTION("acl_smtp_quit"); if ( acl_smtp_quit && acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, user_msgp, log_msgp) == ERROR) @@ -3564,6 +3635,36 @@ if (chunking_state > CHUNKING_OFFERED) } +#ifndef DISABLE_WELLKNOWN +static int +smtp_wellknown_handler(void) +{ +if (verify_check_host(&wellknown_advertise_hosts) != FAIL) + { + GET_OPTION("acl_smtp_wellknown"); + if (acl_smtp_wellknown) + { + uschar * user_msg = NULL, * log_msg; + int rc; + + if ((rc = acl_check(ACL_WHERE_WELLKNOWN, NULL, acl_smtp_wellknown, + &user_msg, &log_msg)) != OK) + return smtp_handle_acl_fail(ACL_WHERE_WELLKNOWN, rc, user_msg, log_msg); + else if (!wellknown_response) + return smtp_handle_acl_fail(ACL_WHERE_WELLKNOWN, ERROR, user_msg, log_msg); + smtp_user_msg(US"250", wellknown_response); + return 0; + } + } + +smtp_printf("554 not permitted\r\n", SP_NO_MORE); +log_write(0, LOG_MAIN|LOG_REJECT, "rejected \"%s\" from %s", + smtp_cmd_buffer, sender_helo_name, host_and_ident(FALSE)); +return 0; +} +#endif + + static int expand_mailmax(const uschar * s) { @@ -3627,7 +3728,7 @@ cmd_list[CL_EHLO].is_mail_cmd = TRUE; cmd_list[CL_STLS].is_mail_cmd = TRUE; #endif -if (lwr_receive_getc != NULL) +if (lwr_receive_getc && !atrn_mode) { /* This should have already happened, but if we've gotten confused, force a reset here. */ @@ -3655,24 +3756,17 @@ value. The values are 2 larger than the required yield of the function. */ while (done <= 0) { - const uschar **argv; - uschar *etrn_command; - uschar *etrn_serialize_key; - uschar *errmess; - uschar *log_msg, *smtp_code; - uschar *user_msg = NULL; - uschar *recipient = NULL; - uschar *hello = NULL; - uschar *s, *ss; - BOOL was_rej_mail = FALSE; - BOOL was_rcpt = FALSE; + const uschar ** argv; + uschar * etrn_command, * etrn_serialize_key, * errmess; + uschar * log_msg, * smtp_code; + uschar * user_msg = NULL, * recipient = NULL, * hello = NULL; + uschar * s, * ss; + BOOL was_rej_mail = FALSE, was_rcpt = FALSE; void (*oldsignal)(int); pid_t pid; int start, end, sender_domain, recipient_domain; - int rc; - int c; - uschar *orcpt = NULL; - int dsn_flags; + int rc, c, dsn_flags; + uschar * orcpt = NULL; gstring * g; #ifdef AUTH_TLS @@ -3685,9 +3779,10 @@ while (done <= 0) { cmd_list[CL_TLAU].is_mail_cmd = FALSE; - for (auth_instance * au = auths; au; au = au->next) - if (strcmpic(US"tls", au->driver_name) == 0) + for (auth_instance * au = auths; au; au = au->drinst.next) + if (strcmpic(US"tls", au->drinst.driver_name) == 0) { + GET_OPTION("acl_smtp_auth"); if ( acl_smtp_auth && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth, &user_msg, &log_msg)) != OK @@ -3705,7 +3800,7 @@ while (done <= 0) #ifndef DISABLE_EVENT { uschar * save_name = sender_host_authenticated, * logmsg; - sender_host_authenticated = au->name; + sender_host_authenticated = au->drinst.name; if ((logmsg = event_raise(event_action, US"auth:fail", s, NULL))) log_write(0, LOG_MAIN, "%s", logmsg); sender_host_authenticated = save_name; @@ -3766,6 +3861,7 @@ while (done <= 0) /* Check the ACL */ + GET_OPTION("acl_smtp_auth"); if ( acl_smtp_auth && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth, &user_msg, &log_msg)) != OK @@ -3791,8 +3887,8 @@ while (done <= 0) if (*smtp_cmd_data) { - *smtp_cmd_data++ = 0; - while (isspace(*smtp_cmd_data)) smtp_cmd_data++; + *smtp_cmd_data++ = '\0'; + Uskip_whitespace(&smtp_cmd_data); } /* Search for an authentication mechanism which is configured for use @@ -3803,7 +3899,7 @@ while (done <= 0) auth_instance * au; uschar * smtp_resp, * errmsg; - for (au = auths; au; au = au->next) + for (au = auths; au; au = au->drinst.next) if (strcmpic(s, au->public_name) == 0 && au->server && (au->advertised || f.allow_auth_unadvertised)) break; @@ -3818,7 +3914,7 @@ while (done <= 0) uschar * logmsg = NULL; #ifndef DISABLE_EVENT {uschar * save_name = sender_host_authenticated; - sender_host_authenticated = au->name; + sender_host_authenticated = au->drinst.name; logmsg = event_raise(event_action, US"auth:fail", smtp_resp, NULL); sender_host_authenticated = save_name; } @@ -3827,7 +3923,7 @@ while (done <= 0) log_write(0, LOG_MAIN|LOG_REJECT, "%s", logmsg); else log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s", - au->name, host_and_ident(FALSE), errmsg); + au->drinst.name, host_and_ident(FALSE), errmsg); } } else @@ -3903,10 +3999,10 @@ while (done <= 0) if (!f.sender_host_unknown) { BOOL old_helo_verified = f.helo_verified; - uschar *p = smtp_cmd_data; + uschar * p = smtp_cmd_data; - while (*p != 0 && !isspace(*p)) { *p = tolower(*p); p++; } - *p = 0; + while (*p && !isspace(*p)) { *p = tolower(*p); p++; } + *p = '\0'; /* Force a reverse lookup if HELO quoted something in helo_lookup_domains because otherwise the log can be confusing. */ @@ -3952,14 +4048,19 @@ while (done <= 0) } } -#ifdef SUPPORT_SPF - /* set up SPF context */ - spf_conn_init(sender_helo_name, sender_host_address); -#endif + /* For any misc-module having a connection-init routine, call it. */ + + if (misc_mod_conn_init(sender_helo_name, sender_host_address) != OK) + { + DEBUG(D_receive) debug_printf("A module conn-init routine failed\n"); + done = 1; + break; + } /* Apply an ACL check if one is defined; afterwards, recheck synchronization in case the client started sending in a delay. */ + GET_OPTION("acl_smtp_helo"); if (acl_smtp_helo) if ((rc = acl_check(ACL_WHERE_HELO, NULL, acl_smtp_helo, &user_msg, &log_msg)) != OK) @@ -4054,15 +4155,15 @@ while (done <= 0) g = string_catn(g, US"-SIZE\r\n", 7); } -#ifdef EXPERIMENTAL_ESMTP_LIMITS - if ( (smtp_mailcmd_max > 0 || recipients_max) +#ifndef DISABLE_ESMTP_LIMITS + if ( (smtp_mailcmd_max > 0 || recipients_max_expanded > 0) && verify_check_host(&limits_advertise_hosts) == OK) { g = string_fmt_append(g, "%.3s-LIMITS", smtp_code); if (smtp_mailcmd_max > 0) g = string_fmt_append(g, " MAILMAX=%d", smtp_mailcmd_max); - if (recipients_max) - g = string_fmt_append(g, " RCPTMAX=%d", recipients_max); + if (recipients_max_expanded > 0) + g = string_fmt_append(g, " RCPTMAX=%d", recipients_max_expanded); g = string_catn(g, US"\r\n", 2); } #endif @@ -4088,19 +4189,33 @@ while (done <= 0) fl.dsn_advertised = TRUE; } - /* Advertise ETRN/VRFY/EXPN if there's are ACL checking whether a host is - permitted to issue them; a check is made when any host actually tries. */ + /* Advertise ATRN/ETRN/VRFY/EXPN if there's are ACL checking whether a + host is permitted to issue them; a check is made when any host actually + tries. */ + GET_OPTION("acl_smtp_atrn"); + if (acl_smtp_atrn && !atrn_mode) + { + const uschar * s = expand_cstring(acl_smtp_atrn); + if (s && *s) + { + g = string_catn(g, smtp_code, 3); + g = string_catn(g, US"-ATRN\r\n", 7); + } + } + GET_OPTION("acl_smtp_etrn"); if (acl_smtp_etrn) { g = string_catn(g, smtp_code, 3); g = string_catn(g, US"-ETRN\r\n", 7); } + GET_OPTION("acl_smtp_vrfy"); if (acl_smtp_vrfy) { g = string_catn(g, smtp_code, 3); g = string_catn(g, US"-VRFY\r\n", 7); } + GET_OPTION("acl_smtp_expn"); if (acl_smtp_expn) { g = string_catn(g, smtp_code, 3); @@ -4147,17 +4262,17 @@ while (done <= 0) ) { BOOL first = TRUE; - for (auth_instance * au = auths; au; au = au->next) + for (auth_instance * au = auths; au; au = au->drinst.next) { au->advertised = FALSE; if (au->server) { DEBUG(D_auth+D_expand) debug_printf_indent( "Evaluating advertise_condition for %s %s athenticator\n", - au->name, au->public_name); + au->drinst.name, au->public_name); if ( !au->advertise_condition - || expand_check_condition(au->advertise_condition, au->name, - US"authenticator") + || expand_check_condition(au->advertise_condition, + au->drinst.name, US"authenticator") ) { int saveptr; @@ -4190,12 +4305,12 @@ while (done <= 0) chunking_state = CHUNKING_OFFERED; } +#ifndef DISABLE_TLS /* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer) if it has been included in the binary, and the host matches tls_advertise_hosts. We must *not* advertise if we are already in a secure connection. */ -#ifndef DISABLE_TLS if (tls_in.active.sock < 0 && verify_check_host(&tls_advertise_hosts) != FAIL) { @@ -4229,6 +4344,13 @@ while (done <= 0) fl.smtputf8_advertised = TRUE; } #endif +#ifndef DISABLE_WELLKNOWN + if (verify_check_host(&wellknown_advertise_hosts) != FAIL) + { + g = string_catn(g, smtp_code, 3); + g = string_catn(g, US"-WELLKNOWN\r\n", 12); + } +#endif /* Finish off the multiline reply with one that is always available. */ @@ -4276,6 +4398,14 @@ while (done <= 0) toomany = FALSE; break; /* HELO/EHLO */ +#ifndef DISABLE_WELLKNOWN + case WELLKNOWN_CMD: + HAD(SCH_WELLKNOWN); + smtp_mailcmd_count++; + smtp_wellknown_handler(); + break; +#endif + #ifdef EXPERIMENTAL_XCLIENT case XCLIENT_CMD: { @@ -4329,9 +4459,10 @@ while (done <= 0) if ( fl.helo_verify_required || verify_check_host(&hosts_require_helo) == OK) { - smtp_printf("503 HELO or EHLO required\r\n", SP_NO_MORE); log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no " "HELO/EHLO given", host_and_ident(FALSE)); + done = synprot_error(L_smtp_protocol_error, 503, NULL, + US"HELO or EHLO required"); break; } else if (smtp_mailcmd_max < 0) @@ -4487,7 +4618,7 @@ while (done <= 0) int rc; uschar *ignore_msg; - if (auth_xtextdecode(value, &authenticated_sender) < 0) + if (xtextdecode(value, &authenticated_sender) < 0) { /* Put back terminator overrides for error message */ value[-1] = '='; @@ -4496,6 +4627,7 @@ while (done <= 0) US"invalid data for AUTH"); goto COMMAND_LOOP; } + GET_OPTION("acl_smtp_mailauth"); if (!acl_smtp_mailauth) { ignore_msg = US"client not authenticated"; @@ -4514,7 +4646,7 @@ while (done <= 0) if (authenticated_by == NULL || authenticated_by->mail_auth_condition == NULL || expand_check_condition(authenticated_by->mail_auth_condition, - authenticated_by->name, US"authenticator")) + authenticated_by->drinst.name, US"authenticator")) break; /* Accept the AUTH */ ignore_msg = US"server_mail_auth_condition failed"; @@ -4692,6 +4824,7 @@ while (done <= 0) when pipelining is not advertised, do another sync check in case the ACL delayed and the client started sending in the meantime. */ + GET_OPTION("acl_smtp_mail"); if (acl_smtp_mail) { rc = acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg); @@ -4900,7 +5033,8 @@ while (done <= 0) /* Check maximum allowed */ - if (rcpt_count+1 < 0 || rcpt_count > recipients_max && recipients_max > 0) + if ( rcpt_count+1 < 0 + || rcpt_count > recipients_max_expanded && recipients_max_expanded > 0) { if (recipients_max_reject) { @@ -4946,10 +5080,13 @@ while (done <= 0) if (f.recipients_discarded) rc = DISCARD; else + { + GET_OPTION("acl_smtp_rcpt"); if ( (rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg, &log_msg)) == OK && !f.smtp_in_pipelining_advertised && !check_sync()) goto SYNC_FAILURE; + } /* The ACL was happy */ @@ -5112,11 +5249,9 @@ while (done <= 0) since the ACL may have delayed. To handle cutthrough delivery enforce a dummy call to get the DATA command sent. */ + GET_OPTION("acl_smtp_predata"); if (!acl_smtp_predata && cutthrough.cctx.sock < 0) - { - if (!check_sync()) goto SYNC_FAILURE; rc = OK; - } else { uschar * acl = acl_smtp_predata ? acl_smtp_predata : US"accept"; @@ -5173,6 +5308,7 @@ while (done <= 0) US"verify"))) break; + GET_OPTION("acl_smtp_vrfy"); if ((rc = acl_check(ACL_WHERE_VRFY, address, acl_smtp_vrfy, &user_msg, &log_msg)) != OK) done = smtp_handle_acl_fail(ACL_WHERE_VRFY, rc, user_msg, log_msg); @@ -5211,6 +5347,7 @@ while (done <= 0) case EXPN_CMD: HAD(SCH_EXPN); + GET_OPTION("acl_smtp_expn"); rc = acl_check(ACL_WHERE_EXPN, NULL, acl_smtp_expn, &user_msg, &log_msg); if (rc != OK) done = smtp_handle_acl_fail(ACL_WHERE_EXPN, rc, user_msg, log_msg); @@ -5240,6 +5377,7 @@ while (done <= 0) /* Apply an ACL check if one is defined */ + GET_OPTION("acl_smtp_starttls"); if ( acl_smtp_starttls && (rc = acl_check(ACL_WHERE_STARTTLS, NULL, acl_smtp_starttls, &user_msg, &log_msg)) != OK @@ -5358,6 +5496,7 @@ while (done <= 0) case QUIT_CMD: f.smtp_in_quit = TRUE; user_msg = NULL; + GET_OPTION("acl_smtp_quit"); if ( acl_smtp_quit && ((rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg, &log_msg)) == ERROR)) @@ -5420,9 +5559,14 @@ while (done <= 0) #endif smtp_printf(" HELO EHLO MAIL RCPT DATA BDAT", SP_MORE); smtp_printf(" NOOP QUIT RSET HELP", SP_MORE); + if (acl_smtp_atrn) smtp_printf(" ATRN", SP_MORE); if (acl_smtp_etrn) smtp_printf(" ETRN", SP_MORE); if (acl_smtp_expn) smtp_printf(" EXPN", SP_MORE); if (acl_smtp_vrfy) smtp_printf(" VRFY", SP_MORE); +#ifndef DISABLE_WELLKNOWN + if (verify_check_host(&wellknown_advertise_hosts) != FAIL) + smtp_printf(" WELLKNOWN", SP_MORE); +#endif #ifdef EXPERIMENTAL_XCLIENT if (proxy_session || verify_check_host(&hosts_xclient) != FAIL) smtp_printf(" XCLIENT", SP_MORE); @@ -5465,6 +5609,11 @@ while (done <= 0) break; + case ATRN_CMD: + HAD(SCH_ATRN); + done = atrn_handle_provider(&user_msg, &log_msg); /* Normal: exit() */ + break; /* Error cases */ + case ETRN_CMD: HAD(SCH_ETRN); if (sender_address) @@ -5477,6 +5626,7 @@ while (done <= 0) log_write(L_etrn, LOG_MAIN, "ETRN %s received from %s", smtp_cmd_argument, host_and_ident(FALSE)); + GET_OPTION("acl_smtp_etrn"); if ((rc = acl_check(ACL_WHERE_ETRN, NULL, acl_smtp_etrn, &user_msg, &log_msg)) != OK) { @@ -5493,6 +5643,7 @@ while (done <= 0) since that is strictly the only kind of ETRN that can be implemented according to the RFC. */ + GET_OPTION("smtp_etrn_command"); if (smtp_etrn_command) { uschar *error; @@ -5664,7 +5815,7 @@ while (done <= 0) case TOO_MANY_NONMAIL_CMD: s = smtp_cmd_buffer; - while (*s && !isspace(*s)) s++; + Uskip_nonwhite(&s); incomplete_transaction_log(US"too many non-mail commands"); log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many " "nonmail commands (last was \"%.*s\")", host_and_ident(FALSE),