X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/73ec116f6e17189e5a8b284ec9a4433a581adefc..6db92eab5917e515c83fd773dad6111177a0207f:/src/src/smtp_in.c diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 257c33de1..824178e4d 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -3,6 +3,7 @@ *************************************************/ /* Copyright (c) University of Cambridge 1995 - 2018 */ +/* Copyright (c) The Exim Maintainers 2020 - 2021 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions for handling an incoming SMTP call. */ @@ -138,11 +139,11 @@ static struct { #endif BOOL dsn_advertised :1; BOOL esmtp :1; - BOOL helo_required :1; + BOOL helo_verify_required :1; BOOL helo_verify :1; BOOL helo_seen :1; BOOL helo_accept_junk :1; -#ifdef SUPPORT_PIPE_CONNECT +#ifndef DISABLE_PIPE_CONNECT BOOL pipe_connect_acceptable :1; #endif BOOL rcpt_smtp_response_same :1; @@ -152,7 +153,7 @@ static struct { BOOL smtputf8_advertised :1; #endif } fl = { - .helo_required = FALSE, + .helo_verify_required = FALSE, .helo_verify = FALSE, .smtp_exit_function_called = FALSE, }; @@ -226,7 +227,7 @@ static smtp_cmd_list *cmd_list_end = /* This list of names is used for performing the smtp_no_mail logging action. It must be kept in step with the SCH_xxx enumerations. */ -static uschar *smtp_names[] = +uschar * smtp_names[] = { US"NONE", US"AUTH", US"DATA", US"BDAT", US"EHLO", US"ETRN", US"EXPN", US"HELO", US"HELP", US"MAIL", US"NOOP", US"QUIT", US"RCPT", US"RSET", @@ -346,7 +347,7 @@ wouldblock_reading(void) { int fd, rc; fd_set fds; -struct timeval tzero; +struct timeval tzero = {.tv_sec = 0, .tv_usec = 0}; #ifndef DISABLE_TLS if (tls_in.active.sock >= 0) @@ -359,8 +360,6 @@ if (smtp_inptr < smtp_inend) fd = fileno(smtp_in); FD_ZERO(&fds); FD_SET(fd, &fds); -tzero.tv_sec = 0; -tzero.tv_usec = 0; rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero); if (rc <= 0) return TRUE; /* Not ready to read */ @@ -397,7 +396,7 @@ return TRUE; } -#ifdef SUPPORT_PIPE_CONNECT +#ifndef DISABLE_PIPE_CONNECT static BOOL pipeline_connect_sends(void) { @@ -457,7 +456,7 @@ if (smtp_batched_input) smtp_notquit_exit(US"command-timeout", US"421", US"%s: SMTP command timeout - closing connection", smtp_active_hostname); -exim_exit(EXIT_FAILURE, US"receiving"); +exim_exit(EXIT_FAILURE); } void @@ -468,7 +467,7 @@ if (smtp_batched_input) moan_smtp_batch(NULL, "421 SIGTERM received"); /* Does not return */ smtp_notquit_exit(US"signal-exit", US"421", US"%s: Service not available - closing connection", smtp_active_hostname); -exim_exit(EXIT_FAILURE, US"receiving"); +exim_exit(EXIT_FAILURE); } void @@ -564,6 +563,12 @@ if (smtp_inptr >= smtp_inend) return *smtp_inptr++; } +BOOL +smtp_hasc(void) +{ +return smtp_inptr < smtp_inend; +} + uschar * smtp_getbuf(unsigned * len) { @@ -582,16 +587,23 @@ return buf; } void -smtp_get_cache(void) +smtp_get_cache(unsigned lim) { #ifndef DISABLE_DKIM int n = smtp_inend - smtp_inptr; +if (n > lim) + n = lim; if (n > 0) dkim_exim_verify_feed(smtp_inptr, n); #endif } +/* Forward declarations */ +static inline void bdat_push_receive_functions(void); +static inline void bdat_pop_receive_functions(void); + + /* Get a byte from the smtp input, in CHUNKING mode. Handle ack of the previous BDAT chunk and getting new ones when we run out. Uses the underlying smtp_getc or tls_getc both for that and for getting the @@ -623,9 +635,7 @@ for(;;) if (chunking_data_left > 0) return lwr_receive_getc(chunking_data_left--); - receive_getc = lwr_receive_getc; - receive_getbuf = lwr_receive_getbuf; - receive_ungetc = lwr_receive_ungetc; + bdat_pop_receive_functions(); #ifndef DISABLE_DKIM dkim_save = dkim_collect_input; dkim_collect_input = 0; @@ -657,7 +667,9 @@ 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; #endif return EOD; } @@ -729,9 +741,7 @@ next_cmd: goto repeat_until_rset; } - receive_getc = bdat_getc; - receive_getbuf = bdat_getbuf; /* r~getbuf is never actually used */ - receive_ungetc = bdat_ungetc; + bdat_push_receive_functions(); #ifndef DISABLE_DKIM dkim_collect_input = dkim_save; #endif @@ -741,6 +751,14 @@ next_cmd: } } +BOOL +bdat_hasc(void) +{ +if (chunking_data_left > 0) + return lwr_receive_hasc(); +return TRUE; +} + uschar * bdat_getbuf(unsigned * len) { @@ -764,9 +782,7 @@ while (chunking_data_left) if (!bdat_getbuf(&n)) break; } -receive_getc = lwr_receive_getc; -receive_getbuf = lwr_receive_getbuf; -receive_ungetc = lwr_receive_ungetc; +bdat_pop_receive_functions(); if (chunking_state != CHUNKING_LAST) { @@ -776,7 +792,48 @@ if (chunking_state != CHUNKING_LAST) } +static inline void +bdat_push_receive_functions(void) +{ +/* push the current receive_* function on the "stack", and +replace them by bdat_getc(), which in turn will use the lwr_receive_* +functions to do the dirty work. */ +if (!lwr_receive_getc) + { + lwr_receive_getc = receive_getc; + lwr_receive_getbuf = receive_getbuf; + lwr_receive_hasc = receive_hasc; + lwr_receive_ungetc = receive_ungetc; + } +else + { + DEBUG(D_receive) debug_printf("chunking double-push receive functions\n"); + } +receive_getc = bdat_getc; +receive_getbuf = bdat_getbuf; +receive_hasc = bdat_hasc; +receive_ungetc = bdat_ungetc; +} + +static inline void +bdat_pop_receive_functions(void) +{ +if (!lwr_receive_getc) + { + DEBUG(D_receive) debug_printf("chunking double-pop receive functions\n"); + return; + } +receive_getc = lwr_receive_getc; +receive_getbuf = lwr_receive_getbuf; +receive_hasc = lwr_receive_hasc; +receive_ungetc = lwr_receive_ungetc; + +lwr_receive_getc = NULL; +lwr_receive_getbuf = NULL; +lwr_receive_hasc = NULL; +lwr_receive_ungetc = NULL; +} /************************************************* * SMTP version of ungetc() * @@ -794,6 +851,9 @@ Returns: the character int smtp_ungetc(int ch) { +if (smtp_inptr <= smtp_inbuffer) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in smtp_ungetc"); + *--smtp_inptr = ch; return ch; } @@ -803,6 +863,7 @@ int bdat_ungetc(int ch) { chunking_data_left++; +bdat_push_receive_functions(); /* we're not done yet, calling push is safe, because it checks the state before pushing anything */ return lwr_receive_ungetc(ch); } @@ -877,6 +938,8 @@ flush for non-TLS connections. The smtp_fflush() function is available for checking that: for convenience, TLS output errors are remembered here so that they are also picked up later by smtp_fflush(). +This function is exposed to the local_scan API; do not change the signature. + Arguments: format format string more further data expected @@ -897,7 +960,10 @@ va_end(ap); /* This is split off so that verify.c:respond_printf() can, in effect, call smtp_printf(), bearing in mind that in C a vararg function can't directly -call another vararg function, only a function which accepts a va_list. */ +call another vararg function, only a function which accepts a va_list. + +This function is exposed to the local_scan API; do not change the signature. +*/ /*XXX consider passing caller-info in, for string_vformat-onward */ void @@ -926,7 +992,7 @@ if (!yield) { log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_printf()"); smtp_closedown(US"Unexpected error"); - exim_exit(EXIT_FAILURE, NULL); + exim_exit(EXIT_FAILURE); } /* If this is the first output for a (non-batch) RCPT command, see if all RCPTs @@ -947,16 +1013,13 @@ if (fl.rcpt_in_progress) /* Now write the string */ +if ( #ifndef DISABLE_TLS -if (tls_in.active.sock >= 0) - { - if (tls_write(NULL, gs.s, gs.ptr, more) < 0) - smtp_write_error = -1; - } -else + tls_in.active.sock >= 0 ? (tls_write(NULL, gs.s, gs.ptr, more) < 0) : #endif - -if (fprintf(smtp_out, "%s", gs.s) < 0) smtp_write_error = -1; + (fwrite(gs.s, gs.ptr, 1, smtp_out) == 0) + ) + smtp_write_error = -1; } @@ -967,8 +1030,7 @@ if (fprintf(smtp_out, "%s", gs.s) < 0) smtp_write_error = -1; /* This function isn't currently used within Exim (it detects errors when it tries to read the next SMTP input), but is available for use in local_scan(). -For non-TLS connections, it flushes the output and checks for errors. For -TLS-connections, it checks for a previously-detected TLS write error. +It flushes the output and checks for errors. Arguments: none Returns: 0 for no error; -1 after an error @@ -978,6 +1040,15 @@ int smtp_fflush(void) { if (tls_in.active.sock < 0 && fflush(smtp_out) != 0) smtp_write_error = -1; + +if ( +#ifndef DISABLE_TLS + tls_in.active.sock >= 0 ? (tls_write(NULL, NULL, 0, FALSE) < 0) : +#endif + (fflush(smtp_out) != 0) + ) + smtp_write_error = -1; + return smtp_write_error; } @@ -1022,25 +1093,6 @@ had_command_sigterm = sig; #ifdef SUPPORT_PROXY -/************************************************* -* Restore socket timeout to previous value * -*************************************************/ -/* If the previous value was successfully retrieved, restore -it before returning control to the non-proxy routines - -Arguments: fd - File descriptor for input - get_ok - Successfully retrieved previous values - tvtmp - Time struct with previous values - vslen - Length of time struct -Returns: none -*/ -static void -restore_socket_timeout(int fd, int get_ok, struct timeval * tvtmp, socklen_t vslen) -{ -if (get_ok == 0) - (void) setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS tvtmp, vslen); -} - /************************************************* * Check if host is required proxy host * *************************************************/ @@ -1117,7 +1169,7 @@ if (cr != NULL) while (capacity > 0) { - do { ret = recv(fd, to, 1, 0); } while (ret == -1 && errno == EINTR); + do { ret = read(fd, to, 1); } while (ret == -1 && errno == EINTR && !had_command_timeout); if (ret == -1) return -1; have++; @@ -1221,20 +1273,11 @@ int size, ret; int fd = fileno(smtp_in); const char v2sig[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"; uschar * iptype; /* To display debug info */ -struct timeval tv; -struct timeval tvtmp; socklen_t vslen = sizeof(struct timeval); BOOL yield = FALSE; -/* Save current socket timeout values */ -get_ok = getsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &tvtmp, &vslen); - -/* Proxy Protocol host must send header within a short time -(default 3 seconds) or it's considered invalid */ -tv.tv_sec = PROXY_NEGOTIATION_TIMEOUT_SEC; -tv.tv_usec = PROXY_NEGOTIATION_TIMEOUT_USEC; -if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &tv, sizeof(tv)) < 0) - goto bad; +os_non_restarting_signal(SIGALRM, command_timeout_handler); +ALARM(proxy_protocol_timeout); do { @@ -1242,9 +1285,9 @@ do don't do a PEEK into the data, actually slurp up enough to be "safe". Can't take it all because TLS-on-connect clients follow immediately with TLS handshake. */ - ret = recv(fd, &hdr, PROXY_INITIAL_READ, 0); + ret = read(fd, &hdr, PROXY_INITIAL_READ); } - while (ret == -1 && errno == EINTR); + while (ret == -1 && errno == EINTR && !had_command_timeout); if (ret == -1) goto proxyfail; @@ -1258,8 +1301,8 @@ if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0)) /* First get the length fields. */ do { - retmore = recv(fd, (uschar*)&hdr + ret, PROXY_V2_HEADER_SIZE - PROXY_INITIAL_READ, 0); - } while (retmore == -1 && errno == EINTR); + retmore = read(fd, (uschar*)&hdr + ret, PROXY_V2_HEADER_SIZE - PROXY_INITIAL_READ); + } while (retmore == -1 && errno == EINTR && !had_command_timeout); if (retmore == -1) goto proxyfail; ret += retmore; @@ -1295,8 +1338,8 @@ if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0)) { do { - retmore = recv(fd, (uschar*)&hdr + ret, size-ret, 0); - } while (retmore == -1 && errno == EINTR); + retmore = read(fd, (uschar*)&hdr + ret, size-ret); + } while (retmore == -1 && errno == EINTR && !had_command_timeout); if (retmore == -1) goto proxyfail; ret += retmore; @@ -1524,7 +1567,8 @@ done: should cause a synchronization failure */ proxyfail: - restore_socket_timeout(fd, get_ok, &tvtmp, vslen); + DEBUG(D_receive) if (had_command_timeout) + debug_printf("Timeout while reading proxy header\n"); bad: if (yield) @@ -1540,6 +1584,7 @@ bad: debug_printf("Failure to extract proxied host, only QUIT allowed\n"); } +ALARM(0); return; } #endif @@ -1722,6 +1767,7 @@ for (;;) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED)) return; case QUIT_CMD: + f.smtp_in_quit = TRUE; smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname); mac_smtp_fflush(); return; @@ -1790,7 +1836,7 @@ s_tlslog(gstring * g) if (LOGGING(tls_cipher) && tls_in.cipher) { g = string_append(g, 2, US" X=", tls_in.cipher); -#ifdef EXPERIMENTAL_TLS_RESUME +#ifndef DISABLE_TLS_RESUME if (LOGGING(tls_resumption) && tls_in.resumption & RESUME_USED) g = string_catn(g, US"*", 1); #endif @@ -1800,11 +1846,31 @@ if (LOGGING(tls_certificate_verified) && tls_in.cipher) if (LOGGING(tls_peerdn) && tls_in.peerdn) g = string_append(g, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\""); if (LOGGING(tls_sni) && tls_in.sni) - g = string_append(g, 3, US" SNI=\"", string_printing(tls_in.sni), US"\""); + g = string_append(g, 2, US" SNI=", string_printing2(tls_in.sni, SP_TAB|SP_SPACE)); return g; } #endif + + +static gstring * +s_connhad_log(gstring * g) +{ +const uschar * sep = smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE + ? US" C=..." : US" C="; + +for (int i = smtp_ch_index; i < SMTP_HBUFF_SIZE; i++) + if (smtp_connection_had[i] != SCH_NONE) + { + g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]); + sep = US","; + } +for (int i = 0; i < smtp_ch_index; i++, sep = US",") + g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]); +return g; +} + + /************************************************* * Log lack of MAIL if so configured * *************************************************/ @@ -1820,7 +1886,7 @@ Returns: nothing void smtp_log_no_mail(void) { -uschar * sep, * s; +uschar * s; gstring * g = NULL; if (smtp_mailcmd_count > 0 || !LOGGING(smtp_no_mail)) @@ -1836,20 +1902,7 @@ if (sender_host_authenticated) g = s_tlslog(g); #endif -sep = smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE ? US" C=..." : US" C="; - -for (int i = smtp_ch_index; i < SMTP_HBUFF_SIZE; i++) - if (smtp_connection_had[i] != SCH_NONE) - { - g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]); - sep = US","; - } - -for (int i = 0; i < smtp_ch_index; i++) - { - g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]); - sep = US","; - } +g = s_connhad_log(g); if (!(s = string_from_gstring(g))) s = US""; @@ -1974,29 +2027,35 @@ static BOOL extract_option(uschar **name, uschar **value) { uschar *n; -uschar *v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1; -while (isspace(*v)) v--; +uschar *v; +if (Ustrlen(smtp_cmd_data) <= 0) return FALSE; +v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1; +while (v > smtp_cmd_data && isspace(*v)) v--; v[1] = 0; + while (v > smtp_cmd_data && *v != '=' && !isspace(*v)) { /* Take care to not stop at a space embedded in a quoted local-part */ - - if (*v == '"') do v--; while (*v != '"' && v > smtp_cmd_data+1); + if (*v == '"') + { + do v--; while (v > smtp_cmd_data && *v != '"'); + if (v <= smtp_cmd_data) return FALSE; + } v--; } +if (v <= smtp_cmd_data) return FALSE; n = v; if (*v == '=') { - while(isalpha(n[-1])) n--; + while (n > smtp_cmd_data && isalpha(n[-1])) n--; /* RFC says SP, but TAB seen in wild and other major MTAs accept it */ - if (!isspace(n[-1])) return FALSE; + if (n <= smtp_cmd_data || !isspace(n[-1])) return FALSE; n[-1] = 0; } else { n++; - if (v == smtp_cmd_data) return FALSE; } *v++ = 0; *name = n; @@ -2027,29 +2086,32 @@ rcpt_count = rcpt_defer_count = rcpt_fail_count = raw_recipients_count = recipients_count = recipients_list_max = 0; message_linecount = 0; message_size = -1; +message_body = message_body_end = NULL; acl_added_headers = NULL; acl_removed_headers = NULL; f.queue_only_policy = FALSE; rcpt_smtp_response = NULL; fl.rcpt_smtp_response_same = TRUE; fl.rcpt_in_progress = FALSE; -f.deliver_freeze = FALSE; /* Can be set by ACL */ -freeze_tell = freeze_tell_config; /* Can be set by ACL */ -fake_response = OK; /* Can be set by ACL */ +f.deliver_freeze = FALSE; /* Can be set by ACL */ +freeze_tell = freeze_tell_config; /* Can be set by ACL */ +fake_response = OK; /* Can be set by ACL */ #ifdef WITH_CONTENT_SCAN -f.no_mbox_unspool = FALSE; /* Can be set by ACL */ +f.no_mbox_unspool = FALSE; /* Can be set by ACL */ #endif -f.submission_mode = FALSE; /* Can be set by ACL */ +f.submission_mode = FALSE; /* Can be set by ACL */ f.suppress_local_fixups = f.suppress_local_fixups_default; /* Can be set by ACL */ -f.active_local_from_check = local_from_check; /* Can be set by ACL */ -f.active_local_sender_retain = local_sender_retain; /* Can be set by ACL */ +f.active_local_from_check = local_from_check; /* Can be set by ACL */ +f.active_local_sender_retain = local_sender_retain; /* Can be set by ACL */ sending_ip_address = NULL; return_path = sender_address = NULL; -sender_data = NULL; /* Can be set by ACL */ +deliver_localpart_data = deliver_domain_data = +recipient_data = sender_data = NULL; /* Can be set by ACL */ +recipient_verify_failure = NULL; deliver_localpart_parent = deliver_localpart_orig = NULL; deliver_domain_parent = deliver_domain_orig = NULL; callout_address = NULL; -submission_name = NULL; /* Can be set by ACL */ +submission_name = NULL; /* Can be set by ACL */ raw_sender = NULL; /* After SMTP rewrite, before qualifying */ sender_address_unrewritten = NULL; /* Set only after verify rewrite */ sender_verified_list = NULL; /* No senders verified */ @@ -2075,13 +2137,14 @@ dkim_collect_input = 0; dkim_verify_overall = dkim_verify_status = dkim_verify_reason = NULL; dkim_key_length = 0; #endif -#ifdef EXPERIMENTAL_DMARC +#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; @@ -2102,23 +2165,7 @@ ratelimiters_mail = NULL; /* Updated by ratelimit ACL condition */ acl_var_m = NULL; -/* The message body variables use malloc store. They may be set if this is -not the first message in an SMTP session and the previous message caused them -to be referenced in an ACL. */ - -if (message_body) - { - store_free(message_body); - message_body = NULL; - } - -if (message_body_end) - { - store_free(message_body_end); - message_body_end = NULL; - } - -/* Warning log messages are also saved in malloc store. They are saved to avoid +/* Warning log messages are saved in malloc store. They are saved to avoid repetition in the same message, but it seems right to repeat them for different messages. */ @@ -2128,7 +2175,11 @@ while (acl_warn_logged) acl_warn_logged = acl_warn_logged->next; store_free(this); } + +message_tidyup(); store_reset(reset_point); + +message_start(); return store_mark(); } @@ -2205,7 +2256,7 @@ while (done <= 0) case MAIL_CMD: smtp_mailcmd_count++; /* Count for no-mail log */ - if (sender_address != NULL) + if (sender_address) /* The function moan_smtp_batch() does not return. */ moan_smtp_batch(smtp_cmd_buffer, "503 Sender already given"); @@ -2220,9 +2271,11 @@ while (done <= 0) /* Apply SMTP rewrite */ - raw_sender = ((rewrite_existflags & rewrite_smtp) != 0)? - rewrite_one(smtp_cmd_data, rewrite_smtp|rewrite_smtp_sender, NULL, FALSE, - US"", global_rewrite_rules) : smtp_cmd_data; + raw_sender = rewrite_existflags & rewrite_smtp + /* deconst ok as smtp_cmd_data was not const */ + ? US rewrite_one(smtp_cmd_data, rewrite_smtp|rewrite_smtp_sender, NULL, + FALSE, US"", global_rewrite_rules) + : smtp_cmd_data; /* Extract the address; the TRUE flag allows <> as valid */ @@ -2242,7 +2295,8 @@ while (done <= 0) && sender_address[0] != 0 && sender_address[0] != '@') if (f.allow_unqualified_sender) { - sender_address = rewrite_address_qualify(sender_address, FALSE); + /* deconst ok as sender_address was not const */ + sender_address = US rewrite_address_qualify(sender_address, FALSE); DEBUG(D_receive) debug_printf("unqualified address %s accepted " "and rewritten\n", raw_sender); } @@ -2281,7 +2335,8 @@ while (done <= 0) recipient address */ recipient = rewrite_existflags & rewrite_smtp - ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"", + /* deconst ok as smtp_cmd_data was not const */ + ? US rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"", global_rewrite_rules) : smtp_cmd_data; @@ -2300,7 +2355,8 @@ while (done <= 0) { DEBUG(D_receive) debug_printf("unqualified address %s accepted\n", recipient); - recipient = rewrite_address_qualify(recipient, TRUE); + /* deconst ok as recipient was not const */ + recipient = US rewrite_address_qualify(recipient, TRUE); } /* The function moan_smtp_batch() does not return. */ else @@ -2343,8 +2399,9 @@ while (done <= 0) break; - case EOF_CMD: case QUIT_CMD: + f.smtp_in_quit = TRUE; + case EOF_CMD: done = 2; break; @@ -2395,24 +2452,47 @@ return FALSE; static void tfo_in_check(void) { -# ifdef TCP_INFO +# ifdef __FreeBSD__ +int is_fastopen; +socklen_t len = sizeof(is_fastopen); + +/* The tinfo TCPOPT_FAST_OPEN bit seems unreliable, and we don't see state +TCP_SYN_RCV (as of 12.1) so no idea about data-use. */ + +if (getsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_FASTOPEN, &is_fastopen, &len) == 0) + { + if (is_fastopen) + { + DEBUG(D_receive) + debug_printf("TFO mode connection (TCP_FASTOPEN getsockopt)\n"); + f.tcp_in_fastopen = TRUE; + } + } +else DEBUG(D_receive) + debug_printf("TCP_INFO getsockopt: %s\n", strerror(errno)); + +# elif defined(TCP_INFO) struct tcp_info tinfo; socklen_t len = sizeof(tinfo); if (getsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0) -#ifdef TCPI_OPT_SYN_DATA /* FreeBSD 11 does not seem to have this yet */ +# ifdef TCPI_OPT_SYN_DATA /* FreeBSD 11,12 do not seem to have this yet */ if (tinfo.tcpi_options & TCPI_OPT_SYN_DATA) { - DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (ACKd data-on-SYN)\n"); + DEBUG(D_receive) + debug_printf("TFO mode connection (ACKd data-on-SYN)\n"); f.tcp_in_fastopen_data = f.tcp_in_fastopen = TRUE; } else -#endif - if (tinfo.tcpi_state == TCP_SYN_RECV) +# endif + if (tinfo.tcpi_state == TCP_SYN_RECV) /* Not seen on FreeBSD 12.1 */ { - DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (state TCP_SYN_RECV)\n"); + DEBUG(D_receive) + debug_printf("TFO mode connection (state TCP_SYN_RECV)\n"); f.tcp_in_fastopen = TRUE; } +else DEBUG(D_receive) + debug_printf("TCP_INFO getsockopt: %s\n", strerror(errno)); # endif } #endif @@ -2466,7 +2546,7 @@ if (!host_checking && !f.sender_host_notsocket) authenticated_by = NULL; #ifndef DISABLE_TLS -tls_in.cipher = tls_in.peerdn = NULL; +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; @@ -2514,10 +2594,15 @@ smtp_inbuffer[IN_BUFFER_SIZE-1] = '\0'; 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; receive_smtp_buffered = smtp_buffered; +lwr_receive_getc = NULL; +lwr_receive_getbuf = NULL; +lwr_receive_hasc = NULL; +lwr_receive_ungetc = NULL; smtp_inptr = smtp_inend = smtp_inbuffer; smtp_had_eof = smtp_had_error = 0; @@ -2873,8 +2958,8 @@ if (!f.sender_host_unknown) /* Determine whether HELO/EHLO is required for this host. The requirement can be hard or soft. */ - fl.helo_required = verify_check_host(&helo_verify_hosts) == OK; - if (!fl.helo_required) + fl.helo_verify_required = verify_check_host(&helo_verify_hosts) == OK; + if (!fl.helo_verify_required) fl.helo_verify = verify_check_host(&helo_try_verify_hosts) == OK; /* Determine whether this hosts is permitted to send syntactic junk @@ -2888,7 +2973,7 @@ if (!f.sender_host_unknown) if (smtp_batched_input) return TRUE; /* If valid Proxy Protocol source is connecting, set up session. - * Failure will not allow any SMTP function other than QUIT. */ +Failure will not allow any SMTP function other than QUIT. */ #ifdef SUPPORT_PROXY proxy_session = FALSE; @@ -2897,16 +2982,16 @@ if (check_proxy_protocol_host()) setup_proxy_protocol_host(); #endif - /* Start up TLS if tls_on_connect is set. This is for supporting the legacy - smtps port for use with older style SSL MTAs. */ +/* Start up TLS if tls_on_connect is set. This is for supporting the legacy +smtps port for use with older style SSL MTAs. */ #ifndef DISABLE_TLS - if (tls_in.on_connect) - { - if (tls_server_start(tls_require_ciphers, &user_msg) != OK) - return smtp_log_tls_fail(user_msg); - cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE; - } +if (tls_in.on_connect) + { + if (tls_server_start(&user_msg) != OK) + return smtp_log_tls_fail(user_msg); + cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE; + } #endif /* Run the connect ACL if it exists */ @@ -2952,7 +3037,7 @@ else p = s + Ustrlen(s); while (p > s && isspace(p[-1])) p--; -*p = 0; +s = string_copyn(s, p-s); /* It seems that CC:Mail is braindead, and assumes that the greeting message is all contained in a single IP packet. The original code wrote out the @@ -2992,7 +3077,7 @@ while (*p); /* Before we write the banner, check that there is no input pending, unless this synchronisation check is disabled. */ -#ifdef SUPPORT_PIPE_CONNECT +#ifndef DISABLE_PIPE_CONNECT fl.pipe_connect_acceptable = sender_host_address && verify_check_host(&pipe_connect_advertise_hosts) == OK; @@ -3005,7 +3090,7 @@ if (!check_sync()) #endif { unsigned n = smtp_inend - smtp_inptr; - if (n > 32) n = 32; + if (n > 128) n = 128; log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol " "synchronization error (input sent without waiting for greeting): " @@ -3019,7 +3104,7 @@ if (!check_sync()) /*XXX the ehlo-resp code does its own tls/nontls bit. Maybe subroutine that? */ smtp_printf("%s", -#ifdef SUPPORT_PIPE_CONNECT +#ifndef DISABLE_PIPE_CONNECT fl.pipe_connect_acceptable && pipeline_connect_sends(), #else FALSE, @@ -3030,7 +3115,7 @@ smtp_printf("%s", handshake arrived. If so we must have managed a TFO. */ #ifdef TCP_FASTOPEN -tfo_in_check(); +if (sender_host_address && !f.sender_host_notsocket) tfo_in_check(); #endif return TRUE; @@ -3066,15 +3151,17 @@ synprot_error(int type, int code, uschar *data, uschar *errmess) int yield = -1; log_write(type, LOG_MAIN, "SMTP %s error in \"%s\" %s %s", - (type == L_smtp_syntax_error)? "syntax" : "protocol", + type == L_smtp_syntax_error ? "syntax" : "protocol", string_printing(smtp_cmd_buffer), host_and_ident(TRUE), errmess); if (++synprot_error_count > smtp_max_synprot_errors) { yield = 1; log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many " - "syntax or protocol errors (last command was \"%s\")", - host_and_ident(FALSE), string_printing(smtp_cmd_buffer)); + "syntax or protocol errors (last command was \"%s\", %s)", + host_and_ident(FALSE), string_printing(smtp_cmd_buffer), + string_from_gstring(s_connhad_log(NULL)) + ); } if (code > 0) @@ -3130,7 +3217,7 @@ which sometimes uses smtp_printf() and sometimes smtp_respond(). */ if (fl.rcpt_in_progress) { - if (rcpt_smtp_response == NULL) + if (!rcpt_smtp_response) rcpt_smtp_response = string_copy(msg); else if (fl.rcpt_smtp_response_same && Ustrcmp(rcpt_smtp_response, msg) != 0) @@ -3145,7 +3232,7 @@ not the whole MAIL/RCPT/DATA response set. */ for (;;) { uschar *nl = Ustrchr(msg, '\n'); - if (nl == NULL) + if (!nl) { smtp_printf("%.3s%c%.*s%s\r\n", !final, code, final ? ' ':'-', esclen, esc, msg); return; @@ -3160,7 +3247,7 @@ for (;;) { smtp_printf("%.3s-%.*s%.*s\r\n", TRUE, code, esclen, esc, (int)(nl - msg), msg); msg = nl + 1; - while (isspace(*msg)) msg++; + Uskip_whitespace(&msg); } } } @@ -3201,27 +3288,26 @@ void smtp_message_code(uschar **code, int *codelen, uschar **msg, uschar **log_msg, BOOL check_valid) { -int n; -int ovector[3]; - -if (!msg || !*msg) return; +uschar * match; +int len; -if ((n = pcre_exec(regex_smtp_code, NULL, CS *msg, Ustrlen(*msg), 0, - PCRE_EOPT, ovector, sizeof(ovector)/sizeof(int))) < 0) return; +if (!msg || !*msg || !regex_match(regex_smtp_code, *msg, -1, &match)) + return; +len = Ustrlen(match); if (check_valid && (*msg)[0] != (*code)[0]) { log_write(0, LOG_MAIN|LOG_PANIC, "configured error code starts with " "incorrect digit (expected %c) in \"%s\"", (*code)[0], *msg); - if (log_msg != NULL && *log_msg == *msg) - *log_msg = string_sprintf("%s %s", *code, *log_msg + ovector[1]); + if (log_msg && *log_msg == *msg) + *log_msg = string_sprintf("%s %s", *code, *log_msg + len); } else { *code = *msg; - *codelen = ovector[1]; /* Includes final space */ + *codelen = len; /* Includes final space */ } -*msg += ovector[1]; /* Chop the code off the message */ +*msg += len; /* Chop the code off the message */ return; } @@ -3272,18 +3358,7 @@ int codelen = 3; uschar *smtp_code; uschar *lognl; uschar *sender_info = US""; -uschar *what = -#ifdef WITH_CONTENT_SCAN - where == ACL_WHERE_MIME ? US"during MIME ACL checks" : -#endif - where == ACL_WHERE_PREDATA ? US"DATA" : - where == ACL_WHERE_DATA ? US"after DATA" : -#ifndef DISABLE_PRDR - where == ACL_WHERE_PRDR ? US"after DATA PRDR" : -#endif - smtp_cmd_data ? - string_sprintf("%s %s", acl_wherenames[where], smtp_cmd_data) : - string_sprintf("%s in \"connect\" ACL", acl_wherenames[where]); +uschar *what; if (drop) rc = FAIL; @@ -3299,19 +3374,45 @@ 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 address to retain backward compatibility. */ -#ifndef WITH_CONTENT_SCAN -if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA) -#else -if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA || where == ACL_WHERE_MIME) +switch (where) + { +#ifdef WITH_CONTENT_SCAN + case ACL_WHERE_MIME: what = US"during MIME ACL checks"; break; +#endif + case ACL_WHERE_PREDATA: what = US"DATA"; break; + case ACL_WHERE_DATA: what = US"after DATA"; break; +#ifndef DISABLE_PRDR + case ACL_WHERE_PRDR: what = US"after DATA PRDR"; break; #endif + default: + { + uschar * place = smtp_cmd_data ? smtp_cmd_data : US"in \"connect\" ACL"; + int lim = 100; + + 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 */ + } + what = string_sprintf("%s %.*s", acl_wherenames[where], lim, place); + } + } +switch (where) { - sender_info = string_sprintf("F=<%s>%s%s%s%s ", - sender_address_unrewritten ? sender_address_unrewritten : sender_address, - sender_host_authenticated ? US" A=" : US"", - sender_host_authenticated ? sender_host_authenticated : US"", - sender_host_authenticated && authenticated_id ? US":" : US"", - sender_host_authenticated && authenticated_id ? authenticated_id : US"" - ); + case ACL_WHERE_RCPT: + case ACL_WHERE_DATA: +#ifdef WITH_CONTENT_SCAN + case ACL_WHERE_MIME: +#endif + sender_info = string_sprintf("F=<%s>%s%s%s%s ", + sender_address_unrewritten ? sender_address_unrewritten : sender_address, + sender_host_authenticated ? US" A=" : US"", + sender_host_authenticated ? sender_host_authenticated : US"", + sender_host_authenticated && authenticated_id ? US":" : US"", + sender_host_authenticated && authenticated_id ? authenticated_id : US"" + ); + break; } /* If there's been a sender verification failure with a specific message, and @@ -3714,60 +3815,60 @@ if (rc != OK) switch(rc) { case OK: - 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_auth_pubname = au->public_name; - authentication_failed = FALSE; - authenticated_fail_id = NULL; /* Impossible to already be set? */ - - received_protocol = - (sender_host_address ? protocols : protocols_local) - [pextend + pauthed + (tls_in.active.sock >= 0 ? pcrpted:0)]; - *s = *ss = US"235 Authentication succeeded"; - authenticated_by = au; - break; - } + 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_auth_pubname = au->public_name; + authentication_failed = FALSE; + authenticated_fail_id = NULL; /* Impossible to already be set? */ - /* Authentication succeeded, but we failed to expand the set_id string. - Treat this as a temporary error. */ + received_protocol = + (sender_host_address ? protocols : protocols_local) + [pextend + pauthed + (tls_in.active.sock >= 0 ? pcrpted:0)]; + *s = *ss = US"235 Authentication succeeded"; + authenticated_by = au; + break; + } + + /* Authentication succeeded, but we failed to expand the set_id string. + Treat this as a temporary error. */ - auth_defer_msg = expand_string_message; - /* Fall through */ + auth_defer_msg = expand_string_message; + /* Fall through */ case DEFER: - if (set_id) authenticated_fail_id = string_copy_perm(set_id, TRUE); - *s = string_sprintf("435 Unable to authenticate at present%s", - auth_defer_user_msg); - *ss = string_sprintf("435 Unable to authenticate at present%s: %s", - set_id, auth_defer_msg); - break; + if (set_id) authenticated_fail_id = string_copy_perm(set_id, TRUE); + *s = string_sprintf("435 Unable to authenticate at present%s", + auth_defer_user_msg); + *ss = string_sprintf("435 Unable to authenticate at present%s: %s", + set_id, auth_defer_msg); + break; case BAD64: - *s = *ss = US"501 Invalid base64 data"; - break; + *s = *ss = US"501 Invalid base64 data"; + break; case CANCELLED: - *s = *ss = US"501 Authentication cancelled"; - break; + *s = *ss = US"501 Authentication cancelled"; + break; case UNEXPECTED: - *s = *ss = US"553 Initial data not expected"; - break; + *s = *ss = US"553 Initial data not expected"; + break; case FAIL: - if (set_id) authenticated_fail_id = string_copy_perm(set_id, TRUE); - *s = US"535 Incorrect authentication data"; - *ss = string_sprintf("535 Incorrect authentication data%s", set_id); - break; + if (set_id) authenticated_fail_id = string_copy_perm(set_id, TRUE); + *s = US"535 Incorrect authentication data"; + *ss = string_sprintf("535 Incorrect authentication data%s", set_id); + break; default: - if (set_id) authenticated_fail_id = string_copy_perm(set_id, TRUE); - *s = US"435 Internal error"; - *ss = string_sprintf("435 Internal error%s: return %d from authentication " - "check", set_id, rc); - break; + if (set_id) authenticated_fail_id = string_copy_perm(set_id, TRUE); + *s = US"435 Internal error"; + *ss = string_sprintf("435 Internal error%s: return %d from authentication " + "check", set_id, rc); + break; } return rc; @@ -3786,7 +3887,8 @@ if (f.allow_unqualified_recipient || strcmpic(*recipient, US"postmaster") == 0) DEBUG(D_receive) debug_printf("unqualified address %s accepted\n", *recipient); rd = Ustrlen(recipient) + 1; - *recipient = rewrite_address_qualify(*recipient, TRUE); + /* deconst ok as *recipient was not const */ + *recipient = US rewrite_address_qualify(*recipient, TRUE); return rd; } smtp_printf("501 %s: recipient address must contain a domain\r\n", FALSE, @@ -3804,17 +3906,16 @@ static void smtp_quit_handler(uschar ** user_msgp, uschar ** log_msgp) { HAD(SCH_QUIT); +f.smtp_in_quit = TRUE; incomplete_transaction_log(US"QUIT"); -if (acl_smtp_quit) - { - int rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, user_msgp, log_msgp); - if (rc == ERROR) +if ( acl_smtp_quit + && acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, user_msgp, log_msgp) + == ERROR) log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s", *log_msgp); - } -#ifdef TCP_CORK -(void) setsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_CORK, US &on, sizeof(on)); +#ifdef EXIM_TCP_CORK +(void) setsockopt(fileno(smtp_out), IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on)); #endif if (*user_msgp) @@ -3822,12 +3923,35 @@ if (*user_msgp) else smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname); -#ifndef DISABLE_TLS +#ifdef SERVERSIDE_CLOSE_NOWAIT +# ifndef DISABLE_TLS tls_close(NULL, TLS_SHUTDOWN_NOWAIT); -#endif +# endif + +log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT", + smtp_get_connection_info()); +#else + +# ifndef DISABLE_TLS +tls_close(NULL, TLS_SHUTDOWN_WAIT); +# endif log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT", smtp_get_connection_info()); + +/* Pause, hoping client will FIN first so that they get the TIME_WAIT. +The socket should become readble (though with no data) */ + + { + int fd = fileno(smtp_in); + fd_set fds; + struct timeval t_limit = {.tv_sec = 0, .tv_usec = 200*1000}; + + FD_ZERO(&fds); + FD_SET(fd, &fds); + (void) select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &t_limit); + } +#endif /*!DAEMON_CLOSE_NOWAIT*/ } @@ -3841,6 +3965,13 @@ cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE; } +static int +expand_mailmax(const uschar * s) +{ +if (!(s = expand_cstring(s))) + log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand smtp_accept_max_per_connection"); +return *s ? Uatoi(s) : 0; +} /************************************************* * Initialize for SMTP incoming message * @@ -3897,6 +4028,14 @@ cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE; cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = TRUE; #endif +if (lwr_receive_getc != NULL) + { + /* This should have already happened, but if we've gotten confused, + force a reset here. */ + DEBUG(D_receive) debug_printf("WARNING: smtp_setup_msg had to restore receive functions to lowers\n"); + bdat_pop_receive_functions(); + } + /* Set the local signal handler for SIGTERM - it tries to end off tidily */ had_command_sigterm = 0; @@ -3906,6 +4045,12 @@ os_non_restarting_signal(SIGTERM, command_sigterm_handler); if (smtp_batched_input) return smtp_setup_batch_msg(); +#ifdef TCP_QUICKACK +if (smtp_in) /* Avoid pure-ACKs while in cmd pingpong phase */ + (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK, + US &off, sizeof(off)); +#endif + /* Deal with SMTP commands. This loop is exited by setting done to a POSITIVE value. The values are 2 larger than the required yield of the function. */ @@ -3963,14 +4108,8 @@ while (done <= 0) } #endif -#ifdef TCP_QUICKACK - if (smtp_in) /* Avoid pure-ACKs while in cmd pingpong phase */ - (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK, - US &off, sizeof(off)); -#endif - switch(smtp_read_command( -#ifdef SUPPORT_PIPE_CONNECT +#ifndef DISABLE_PIPE_CONNECT !fl.pipe_connect_acceptable, #else TRUE, @@ -4029,21 +4168,18 @@ while (done <= 0) /* Find the name of the requested authentication mechanism. */ s = smtp_cmd_data; - while ((c = *smtp_cmd_data) != 0 && !isspace(c)) - { + for (; (c = *smtp_cmd_data) && !isspace(c); smtp_cmd_data++) if (!isalnum(c) && c != '-' && c != '_') { done = synprot_error(L_smtp_syntax_error, 501, NULL, US"invalid character in authentication mechanism name"); goto COMMAND_LOOP; } - smtp_cmd_data++; - } /* If not at the end of the line, we must be at white space. Terminate the name and move the pointer on to any data that may be present. */ - if (*smtp_cmd_data != 0) + if (*smtp_cmd_data) { *smtp_cmd_data++ = 0; while (isspace(*smtp_cmd_data)) smtp_cmd_data++; @@ -4122,8 +4258,10 @@ while (done <= 0) if (++synprot_error_count > smtp_max_synprot_errors) { log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many " - "syntax or protocol errors (last command was \"%s\")", - host_and_ident(FALSE), string_printing(smtp_cmd_buffer)); + "syntax or protocol errors (last command was \"%s\", %s)", + host_and_ident(FALSE), string_printing(smtp_cmd_buffer), + string_from_gstring(s_connhad_log(NULL)) + ); done = 1; } @@ -4133,7 +4271,7 @@ while (done <= 0) /* If sender_host_unknown is true, we have got here via the -bs interface, not called from inetd. Otherwise, we are running an IP connection and the host address will be set. If the helo name is the primary name of this - host and we haven't done a reverse lookup, force one now. If helo_required + host and we haven't done a reverse lookup, force one now. If helo_verify_required is set, ensure that the HELO name matches the actual host. If helo_verify is set, do the same check, but softly. */ @@ -4161,19 +4299,19 @@ while (done <= 0) tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE)); /* Verify if configured. This doesn't give much security, but it does - make some people happy to be able to do it. If helo_required is set, + make some people happy to be able to do it. If helo_verify_required is set, (host matches helo_verify_hosts) failure forces rejection. If helo_verify is set (host matches helo_try_verify_hosts), it does not. This is perhaps now obsolescent, since the verification can now be requested selectively at ACL time. */ f.helo_verified = f.helo_verify_failed = sender_helo_dnssec = FALSE; - if (fl.helo_required || fl.helo_verify) + if (fl.helo_verify_required || fl.helo_verify) { BOOL tempfail = !smtp_verify_helo(); if (!f.helo_verified) { - if (fl.helo_required) + if (fl.helo_verify_required) { smtp_printf("%d %s argument does not match calling host\r\n", FALSE, tempfail? 451 : 550, hello); @@ -4206,7 +4344,7 @@ while (done <= 0) host_build_sender_fullhost(); /* Rebuild */ break; } -#ifdef SUPPORT_PIPE_CONNECT +#ifndef DISABLE_PIPE_CONNECT else if (!fl.pipe_connect_acceptable && !check_sync()) #else else if (!check_sync()) @@ -4229,6 +4367,9 @@ while (done <= 0) fl.smtputf8_advertised = FALSE; #endif + /* Expand the per-connection message count limit option */ + smtp_mailcmd_max = expand_mailmax(smtp_accept_max_per_connection); + smtp_code = US"250 "; /* Default response code plus space*/ if (!user_msg) { @@ -4288,6 +4429,19 @@ while (done <= 0) g = string_catn(g, US"-SIZE\r\n", 7); } +#ifdef EXPERIMENTAL_ESMTP_LIMITS + if ( (smtp_mailcmd_max > 0 || recipients_max) + && 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); + g = string_catn(g, US"\r\n", 2); + } +#endif + /* Exim does not do protocol conversion or data conversion. It is 8-bit clean; if it has an 8-bit character in its hand, it just sends it. It cannot therefore specify 8BITMIME and remain consistent with the RFCs. @@ -4339,7 +4493,7 @@ while (done <= 0) sync_cmd_limit = NON_SYNC_CMD_PIPELINING; f.smtp_in_pipelining_advertised = TRUE; -#ifdef SUPPORT_PIPE_CONNECT +#ifndef DISABLE_PIPE_CONNECT if (fl.pipe_connect_acceptable) { f.smtp_in_early_pipe_advertised = TRUE; @@ -4374,8 +4528,8 @@ while (done <= 0) if (au->server) { DEBUG(D_auth+D_expand) debug_printf_indent( - "Evaluating advertise_condition for %s athenticator\n", - au->public_name); + "Evaluating advertise_condition for %s %s athenticator\n", + au->name, au->public_name); if ( !au->advertise_condition || expand_check_condition(au->advertise_condition, au->name, US"authenticator") @@ -4457,17 +4611,15 @@ while (done <= 0) #ifndef DISABLE_TLS if (tls_in.active.sock >= 0) (void)tls_write(NULL, g->s, g->ptr, -# ifdef SUPPORT_PIPE_CONNECT +# ifndef DISABLE_PIPE_CONNECT fl.pipe_connect_acceptable && pipeline_connect_sends()); # else FALSE); # endif else #endif + (void) fwrite(g->s, 1, g->ptr, smtp_out); - { - int i = fwrite(g->s, 1, g->ptr, smtp_out); i = i; /* compiler quietening */ - } DEBUG(D_receive) { uschar *cr; @@ -4502,16 +4654,21 @@ while (done <= 0) case MAIL_CMD: HAD(SCH_MAIL); smtp_mailcmd_count++; /* Count for limit and ratelimit */ + message_start(); was_rej_mail = TRUE; /* Reset if accepted */ env_mail_type_t * mail_args; /* Sanity check & validate args */ - if (fl.helo_required && !fl.helo_seen) - { - smtp_printf("503 HELO or EHLO required\r\n", FALSE); - log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no " - "HELO/EHLO given", host_and_ident(FALSE)); - break; - } + if (!fl.helo_seen) + if ( fl.helo_verify_required + || verify_check_host(&hosts_require_helo) == OK) + { + smtp_printf("503 HELO or EHLO required\r\n", FALSE); + log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no " + "HELO/EHLO given", host_and_ident(FALSE)); + break; + } + else if (smtp_mailcmd_max < 0) + smtp_mailcmd_max = expand_mailmax(smtp_accept_max_per_connection); if (sender_address) { @@ -4530,8 +4687,7 @@ while (done <= 0) /* Check to see if the limit for messages per connection would be exceeded by accepting further messages. */ - if (smtp_accept_max_per_connection > 0 && - smtp_mailcmd_count > smtp_accept_max_per_connection) + if (smtp_mailcmd_max > 0 && smtp_mailcmd_count > smtp_mailcmd_max) { smtp_printf("421 too many messages in this connection\r\n", FALSE); log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL command %s: too many " @@ -4783,7 +4939,8 @@ while (done <= 0) TRUE flag allows "<>" as a sender address. */ raw_sender = rewrite_existflags & rewrite_smtp - ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"", + /* deconst ok as smtp_cmd_data was not const */ + ? US rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"", global_rewrite_rules) : smtp_cmd_data; @@ -4827,8 +4984,8 @@ while (done <= 0) and EXPN etc. to be used when space is short. */ if (!receive_check_fs( - (smtp_check_spool_space && message_size >= 0)? - message_size + 5000 : 0)) + smtp_check_spool_space && message_size >= 0 + ? message_size + 5000 : 0)) { smtp_printf("452 Space shortage, please try later\r\n", FALSE); sender_address = NULL; @@ -4845,7 +5002,8 @@ while (done <= 0) if (f.allow_unqualified_sender) { sender_domain = Ustrlen(sender_address) + 1; - sender_address = rewrite_address_qualify(sender_address, FALSE); + /* deconst ok as sender_address was not const */ + sender_address = US rewrite_address_qualify(sender_address, FALSE); DEBUG(D_receive) debug_printf("unqualified address %s accepted\n", raw_sender); } @@ -4915,6 +5073,10 @@ while (done <= 0) case RCPT_CMD: HAD(SCH_RCPT); + /* We got really to many recipients. A check against configured + limits is done later */ + if (rcpt_count < 0 || rcpt_count >= INT_MAX/2) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Too many recipients: %d", rcpt_count); rcpt_count++; was_rcpt = fl.rcpt_in_progress = TRUE; @@ -5037,7 +5199,8 @@ while (done <= 0) as a recipient address */ recipient = rewrite_existflags & rewrite_smtp - ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"", + /* deconst ok as smtp_cmd_data was not const */ + ? US rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"", global_rewrite_rules) : smtp_cmd_data; @@ -5070,7 +5233,7 @@ while (done <= 0) /* Check maximum allowed */ - if (rcpt_count > recipients_max && recipients_max > 0) + if (rcpt_count+1 < 0 || rcpt_count > recipients_max && recipients_max > 0) { if (recipients_max_reject) { @@ -5137,9 +5300,9 @@ while (done <= 0) recipients_list[recipients_count-1].orcpt = orcpt; recipients_list[recipients_count-1].dsn_flags = dsn_flags; - DEBUG(D_receive) debug_printf("DSN: orcpt: %s flags: %d\n", + /* DEBUG(D_receive) debug_printf("DSN: orcpt: %s flags: %d\n", recipients_list[recipients_count-1].orcpt, - recipients_list[recipients_count-1].dsn_flags); + recipients_list[recipients_count-1].dsn_flags); */ } /* The recipient was discarded */ @@ -5154,8 +5317,8 @@ while (done <= 0) discarded = TRUE; log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: " "discarded by %s ACL%s%s", host_and_ident(TRUE), - sender_address_unrewritten? sender_address_unrewritten : sender_address, - smtp_cmd_argument, f.recipients_discarded? "MAIL" : "RCPT", + sender_address_unrewritten ? sender_address_unrewritten : sender_address, + smtp_cmd_argument, f.recipients_discarded ? "MAIL" : "RCPT", log_msg ? US": " : US"", log_msg ? log_msg : US""); } @@ -5215,16 +5378,7 @@ while (done <= 0) DEBUG(D_receive) debug_printf("chunking state %d, %d bytes\n", (int)chunking_state, chunking_data_left); - /* push the current receive_* function on the "stack", and - replace them by bdat_getc(), which in turn will use the lwr_receive_* - functions to do the dirty work. */ - lwr_receive_getc = receive_getc; - lwr_receive_getbuf = receive_getbuf; - lwr_receive_ungetc = receive_ungetc; - - receive_getc = bdat_getc; - receive_ungetc = bdat_ungetc; - + f.bdat_readers_wanted = TRUE; /* FIXME: redundant vs chunking_state? */ f.dot_ends = FALSE; goto DATA_BDAT; @@ -5233,9 +5387,10 @@ while (done <= 0) case DATA_CMD: HAD(SCH_DATA); f.dot_ends = TRUE; + f.bdat_readers_wanted = FALSE; DATA_BDAT: /* Common code for DATA and BDAT */ -#ifdef SUPPORT_PIPE_CONNECT +#ifndef DISABLE_PIPE_CONNECT fl.pipe_connect_acceptable = FALSE; #endif if (!discarded && recipients_count <= 0) @@ -5253,15 +5408,18 @@ while (done <= 0) } if (f.smtp_in_pipelining_advertised && last_was_rcpt) smtp_printf("503 Valid RCPT command must precede %s\r\n", FALSE, - smtp_names[smtp_connection_had[smtp_ch_index-1]]); + smtp_names[smtp_connection_had[SMTP_HBUFF_PREV(smtp_ch_index)]]); else done = synprot_error(L_smtp_protocol_error, 503, NULL, - smtp_connection_had[smtp_ch_index-1] == SCH_DATA + smtp_connection_had[SMTP_HBUFF_PREV(smtp_ch_index)] == SCH_DATA ? US"valid RCPT command must precede DATA" : US"valid RCPT command must precede BDAT"); if (chunking_state > CHUNKING_OFFERED) + { + bdat_push_receive_functions(); bdat_flush_data(); + } break; } @@ -5270,6 +5428,12 @@ while (done <= 0) sender_address = NULL; /* This will allow a new MAIL without RSET */ sender_address_unrewritten = NULL; smtp_printf("554 Too many recipients\r\n", FALSE); + + if (chunking_state > CHUNKING_OFFERED) + { + bdat_push_receive_functions(); + bdat_flush_data(); + } break; } @@ -5307,6 +5471,9 @@ while (done <= 0) "354 Enter message, ending with \".\" on a line by itself\r\n", FALSE); } + if (f.bdat_readers_wanted) + bdat_push_receive_functions(); + #ifdef TCP_QUICKACK if (smtp_in) /* all ACKs needed to ramp window up for bulk data */ (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK, @@ -5458,7 +5625,7 @@ while (done <= 0) STARTTLS that don't add to the nonmail command count. */ s = NULL; - if ((rc = tls_server_start(tls_require_ciphers, &s)) == OK) + if ((rc = tls_server_start(&s)) == OK) { if (!tls_remember_esmtp) fl.helo_seen = fl.esmtp = fl.auth_advertised = f.smtp_in_pipelining_advertised = FALSE; @@ -5520,6 +5687,7 @@ while (done <= 0) some sense is perhaps "right". */ case QUIT_CMD: + f.smtp_in_quit = TRUE; user_msg = NULL; if ( acl_smtp_quit && ((rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg, @@ -5725,7 +5893,7 @@ while (done <= 0) oldsignal = signal(SIGCHLD, SIG_IGN); - if ((pid = fork()) == 0) + if ((pid = exim_fork(US"etrn-command")) == 0) { smtp_input = FALSE; /* This process is not associated with the */ (void)fclose(smtp_in); /* SMTP call any more. */ @@ -5736,7 +5904,8 @@ while (done <= 0) /* If not serializing, do the exec right away. Otherwise, fork down into another process. */ - if (!smtp_etrn_serialize || (pid = fork()) == 0) + if ( !smtp_etrn_serialize + || (pid = exim_fork(US"etrn-serialised-command")) == 0) { DEBUG(D_exec) debug_print_argv(argv); exim_nullstd(); /* Ensure std{in,out,err} exist */ @@ -5884,12 +6053,14 @@ if (!sender_host_authenticated) g = string_append(g, 2, US";\n\tauth=pass (", sender_host_auth_pubname); -if (Ustrcmp(sender_host_auth_pubname, "tls") != 0) - g = string_append(g, 2, US") smtp.auth=", authenticated_id); -else if (authenticated_id) - g = string_append(g, 2, US") x509.auth=", authenticated_id); +if (Ustrcmp(sender_host_auth_pubname, "tls") == 0) + g = authenticated_id + ? string_append(g, 2, US") x509.auth=", authenticated_id) + : string_cat(g, US") reason=x509.auth"); else - g = string_catn(g, US") reason=x509.auth", 17); + g = authenticated_id + ? string_append(g, 2, US") smtp.auth=", authenticated_id) + : string_cat(g, US", no id saved)"); if (authenticated_sender) g = string_append(g, 2, US" smtp.mailfrom=", authenticated_sender);