X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/518f0a0dd6df6f0d0ea51bfa126982d134e7a7ff..4687a69c269ee3f2a7f0625e0147a503fd9d3d0b:/src/src/smtp_in.c diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 4f16fd4b8..79176687d 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -2,9 +2,10 @@ * Exim - an Internet mail transport agent * *************************************************/ +/* Copyright (c) The Exim Maintainers 2020 - 2023 */ /* Copyright (c) University of Cambridge 1995 - 2018 */ -/* Copyright (c) The Exim Maintainers 2020 */ /* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* Functions for handling an incoming SMTP call. */ @@ -74,6 +75,9 @@ enum { 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 + XCLIENT_CMD, /* per xlexkiro implementation */ +#endif /* This is a dummy to identify the non-sync commands when pipelining */ @@ -139,7 +143,7 @@ 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; @@ -153,7 +157,7 @@ static struct { BOOL smtputf8_advertised :1; #endif } fl = { - .helo_required = FALSE, + .helo_verify_required = FALSE, .helo_verify = FALSE, .smtp_exit_function_called = FALSE, }; @@ -188,19 +192,34 @@ count of non-mail commands and possibly provoke an error. tls_auth is a pseudo-command, never expected in input. It is activated on TLS startup and looks for a tls authenticator. */ +enum { + CL_RSET = 0, + CL_HELO, + CL_EHLO, + CL_AUTH, +#ifndef DISABLE_TLS + CL_STLS, + CL_TLAU, +#endif +#ifdef EXPERIMENTAL_XCLIENT + CL_XCLI, +#endif +}; + static smtp_cmd_list cmd_list[] = { - /* name len cmd has_arg is_mail_cmd */ + /* name len cmd has_arg is_mail_cmd */ - { "rset", sizeof("rset")-1, RSET_CMD, FALSE, FALSE }, /* First */ - { "helo", sizeof("helo")-1, HELO_CMD, TRUE, FALSE }, - { "ehlo", sizeof("ehlo")-1, EHLO_CMD, TRUE, FALSE }, - { "auth", sizeof("auth")-1, AUTH_CMD, TRUE, TRUE }, + [CL_RSET] = { "rset", sizeof("rset")-1, RSET_CMD, FALSE, FALSE }, /* First */ + [CL_HELO] = { "helo", sizeof("helo")-1, HELO_CMD, TRUE, FALSE }, + [CL_EHLO] = { "ehlo", sizeof("ehlo")-1, EHLO_CMD, TRUE, FALSE }, + [CL_AUTH] = { "auth", sizeof("auth")-1, AUTH_CMD, TRUE, TRUE }, #ifndef DISABLE_TLS - { "starttls", sizeof("starttls")-1, STARTTLS_CMD, FALSE, FALSE }, - { "tls_auth", 0, TLS_AUTH_CMD, FALSE, FALSE }, + [CL_STLS] = { "starttls", sizeof("starttls")-1, STARTTLS_CMD, FALSE, FALSE }, + [CL_TLAU] = { "tls_auth", 0, TLS_AUTH_CMD, FALSE, FALSE }, +#endif +#ifdef EXPERIMENTAL_XCLIENT + [CL_XCLI] = { "xclient", sizeof("xclient")-1, XCLIENT_CMD, TRUE, FALSE }, #endif - -/* If you change anything above here, also fix the definitions below. */ { "mail from:", sizeof("mail from:")-1, MAIL_CMD, TRUE, TRUE }, { "rcpt to:", sizeof("rcpt to:")-1, RCPT_CMD, TRUE, TRUE }, @@ -214,24 +233,30 @@ static smtp_cmd_list cmd_list[] = { { "help", sizeof("help")-1, HELP_CMD, TRUE, FALSE } }; -static smtp_cmd_list *cmd_list_end = - cmd_list + sizeof(cmd_list)/sizeof(smtp_cmd_list); - -#define CMD_LIST_RSET 0 -#define CMD_LIST_HELO 1 -#define CMD_LIST_EHLO 2 -#define CMD_LIST_AUTH 3 -#define CMD_LIST_STARTTLS 4 -#define CMD_LIST_TLS_AUTH 5 - -/* 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. */ +/* This list of names is used for performing the smtp_no_mail logging action. */ 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", - US"STARTTLS", US"VRFY" }; + [SCH_NONE] = US"NONE", + [SCH_AUTH] = US"AUTH", + [SCH_DATA] = US"DATA", + [SCH_BDAT] = US"BDAT", + [SCH_EHLO] = US"EHLO", + [SCH_ETRN] = US"ETRN", + [SCH_EXPN] = US"EXPN", + [SCH_HELO] = US"HELO", + [SCH_HELP] = US"HELP", + [SCH_MAIL] = US"MAIL", + [SCH_NOOP] = US"NOOP", + [SCH_QUIT] = US"QUIT", + [SCH_RCPT] = US"RCPT", + [SCH_RSET] = US"RSET", + [SCH_STARTTLS] = US"STARTTLS", + [SCH_VRFY] = US"VRFY", +#ifdef EXPERIMENTAL_XCLIENT + [SCH_XCLIENT] = US"XCLIENT", +#endif + }; static uschar *protocols_local[] = { US"local-smtp", /* HELO */ @@ -319,96 +344,6 @@ 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); -/************************************************* -* Recheck synchronization * -*************************************************/ - -/* Synchronization checks can never be perfect because a packet may be on its -way but not arrived when the check is done. Normally, the checks happen when -commands are read: Exim ensures that there is no more input in the input buffer. -In normal cases, the response to the command will be fast, and there is no -further check. - -However, for some commands an ACL is run, and that can include delays. In those -cases, it is useful to do another check on the input just before sending the -response. This also applies at the start of a connection. This function does -that check by means of the select() function, as long as the facility is not -disabled or inappropriate. A failure of select() is ignored. - -When there is unwanted input, we read it so that it appears in the log of the -error. - -Arguments: none -Returns: TRUE if all is well; FALSE if there is input pending -*/ - -static BOOL -wouldblock_reading(void) -{ -int fd, rc; -fd_set fds; -struct timeval tzero = {.tv_sec = 0, .tv_usec = 0}; - -#ifndef DISABLE_TLS -if (tls_in.active.sock >= 0) - return !tls_could_read(); -#endif - -if (smtp_inptr < smtp_inend) - return FALSE; - -fd = fileno(smtp_in); -FD_ZERO(&fds); -FD_SET(fd, &fds); -rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero); - -if (rc <= 0) return TRUE; /* Not ready to read */ -rc = smtp_getc(GETC_BUFFER_UNLIMITED); -if (rc < 0) return TRUE; /* End of file or error */ - -smtp_ungetc(rc); -return FALSE; -} - -static BOOL -check_sync(void) -{ -if (!smtp_enforce_sync || !sender_host_address || f.sender_host_notsocket) - return TRUE; - -return wouldblock_reading(); -} - - -/* If there's input waiting (and we're doing pipelineing) then we can pipeline -a reponse with the one following. */ - -static BOOL -pipeline_response(void) -{ -if ( !smtp_enforce_sync || !sender_host_address - || f.sender_host_notsocket || !f.smtp_in_pipelining_advertised) - return FALSE; - -if (wouldblock_reading()) return FALSE; -f.smtp_in_pipelining_used = TRUE; -return TRUE; -} - - -#ifndef DISABLE_PIPE_CONNECT -static BOOL -pipeline_connect_sends(void) -{ -if (!sender_host_address || f.sender_host_notsocket || !fl.pipe_connect_acceptable) - return FALSE; - -if (wouldblock_reading()) return FALSE; -f.smtp_in_early_pipe_used = TRUE; -return TRUE; -} -#endif - /************************************************* * Log incomplete transactions * *************************************************/ @@ -422,7 +357,7 @@ Returns: nothing */ static void -incomplete_transaction_log(uschar *what) +incomplete_transaction_log(uschar * what) { if (!sender_address /* No transaction in progress */ || !LOGGING(smtp_incomplete_transaction)) @@ -432,7 +367,7 @@ if (!sender_address /* No transaction in progress */ if (recipients_count > 0) { - raw_recipients = store_get(recipients_count * sizeof(uschar *), FALSE); + raw_recipients = store_get(recipients_count * sizeof(uschar *), GET_UNTAINTED); for (int i = 0; i < recipients_count; i++) raw_recipients[i] = recipients_list[i].address; raw_recipients_count = recipients_count; @@ -444,13 +379,21 @@ log_write(L_smtp_incomplete_transaction, LOG_MAIN|LOG_SENDER|LOG_RECIPIENTS, +static void +log_close_event(const uschar * reason) +{ +log_write(L_smtp_connection, LOG_MAIN, "%s D=%s closed %s", + smtp_get_connection_info(), string_timesince(&smtp_connection_start), reason); +} + void smtp_command_timeout_exit(void) { log_write(L_lost_incoming_connection, - LOG_MAIN, "SMTP command timeout on%s connection from %s", - tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE)); + LOG_MAIN, "SMTP command timeout on%s connection from %s D=%s", + tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE), + string_timesince(&smtp_connection_start)); if (smtp_batched_input) moan_smtp_batch(NULL, "421 SMTP command timeout"); /* Does not return */ smtp_notquit_exit(US"command-timeout", US"421", @@ -462,7 +405,7 @@ exim_exit(EXIT_FAILURE); void smtp_command_sigterm_exit(void) { -log_write(0, LOG_MAIN, "%s closed after SIGTERM", smtp_get_connection_info()); +log_close_event(US"after SIGTERM"); if (smtp_batched_input) moan_smtp_batch(NULL, "421 SIGTERM received"); /* Does not return */ smtp_notquit_exit(US"signal-exit", US"421", @@ -473,9 +416,10 @@ exim_exit(EXIT_FAILURE); void smtp_data_timeout_exit(void) { -log_write(L_lost_incoming_connection, - LOG_MAIN, "SMTP data timeout (message abandoned) on connection from %s F=<%s>", - sender_fullhost ? sender_fullhost : US"local process", sender_address); +log_write(L_lost_incoming_connection, LOG_MAIN, + "SMTP data timeout (message abandoned) on connection from %s F=<%s> D=%s", + sender_fullhost ? sender_fullhost : US"local process", sender_address, + string_timesince(&smtp_connection_start)); receive_bomb_out(US"data-timeout", US"SMTP incoming data timeout"); /* Does not return */ } @@ -483,14 +427,32 @@ receive_bomb_out(US"data-timeout", US"SMTP incoming data timeout"); void smtp_data_sigint_exit(void) { -log_write(0, LOG_MAIN, "%s closed after %s", - smtp_get_connection_info(), had_data_sigint == SIGTERM ? "SIGTERM":"SIGINT"); +log_close_event(had_data_sigint == SIGTERM ? US"SIGTERM":US"SIGINT"); receive_bomb_out(US"signal-exit", US"Service not available - SIGTERM or SIGINT received"); /* Does not return */ } +/******************************************************************************/ +/* SMTP input buffer handling. Most of these are similar to stdio routines. */ + +static void +smtp_buf_init(void) +{ +/* Set up the buffer for inputting using direct read() calls, and arrange to +call the local functions instead of the standard C ones. Place a NUL at the +end of the buffer to safety-stop C-string reads from it. */ + +if (!(smtp_inbuffer = US malloc(IN_BUFFER_SIZE))) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "malloc() failed for SMTP input buffer"); +smtp_inbuffer[IN_BUFFER_SIZE-1] = '\0'; + +smtp_inptr = smtp_inend = smtp_inbuffer; +smtp_had_eof = smtp_had_error = 0; +} + + /* Refill the buffer, and notify DKIM verification code. Return false for error or EOF. @@ -500,6 +462,7 @@ static BOOL smtp_refill(unsigned lim) { int rc, save_errno; + if (!smtp_out) return FALSE; fflush(smtp_out); if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout); @@ -541,11 +504,18 @@ smtp_inptr = smtp_inbuffer; return TRUE; } -/************************************************* -* SMTP version of getc() * -*************************************************/ -/* This gets the next byte from the SMTP input buffer. If the buffer is empty, +/* Check if there is buffered data */ + +BOOL +smtp_hasc(void) +{ +return smtp_inptr < smtp_inend; +} + +/* SMTP version of getc() + +This gets the next byte from the SMTP input buffer. If the buffer is empty, it flushes the output, and refills the buffer, with a timeout. The signal handler is set appropriately by the calling function. This function is not used after a connection has negotiated itself into an TLS/SSL state. @@ -557,21 +527,20 @@ Returns: the next character or EOF int smtp_getc(unsigned lim) { -if (smtp_inptr >= smtp_inend) - if (!smtp_refill(lim)) - return EOF; +if (!smtp_hasc() && !smtp_refill(lim)) return EOF; return *smtp_inptr++; } +/* Get many bytes, refilling buffer if needed */ + uschar * smtp_getbuf(unsigned * len) { unsigned size; uschar * buf; -if (smtp_inptr >= smtp_inend) - if (!smtp_refill(*len)) - { *len = 0; return NULL; } +if (!smtp_hasc() && !smtp_refill(*len)) + { *len = 0; return NULL; } if ((size = smtp_inend - smtp_inptr) > *len) size = *len; buf = smtp_inptr; @@ -580,19 +549,151 @@ smtp_inptr += size; return buf; } +/* Copy buffered data to the dkim feed. +Called, unless TLS, just before starting to read message headers. */ + void -smtp_get_cache(void) +smtp_get_cache(unsigned lim) { #ifndef DISABLE_DKIM int n = smtp_inend - smtp_inptr; -if (chunking_state == CHUNKING_LAST && chunking_data_left < n) - n = chunking_data_left; +if (n > lim) + n = lim; if (n > 0) dkim_exim_verify_feed(smtp_inptr, n); #endif } +/* SMTP version of ungetc() +Puts a character back in the input buffer. Only ever called once. + +Arguments: + ch the character + +Returns: the character +*/ + +int +smtp_ungetc(int ch) +{ +if (smtp_inptr <= smtp_inbuffer) /* NB: NOT smtp_hasc() ! */ + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in smtp_ungetc"); + +*--smtp_inptr = ch; +return ch; +} + + +/* SMTP version of feof() +Tests for a previous EOF + +Arguments: none +Returns: non-zero if the eof flag is set +*/ + +int +smtp_feof(void) +{ +return smtp_had_eof; +} + + +/* SMTP version of ferror() +Tests for a previous read error, and returns with errno +restored to what it was when the error was detected. + +Arguments: none +Returns: non-zero if the error flag is set +*/ + +int +smtp_ferror(void) +{ +errno = smtp_had_error; +return smtp_had_error; +} + + +/* Check if a getc will block or not */ + +static BOOL +smtp_could_getc(void) +{ +int fd, rc; +fd_set fds; +struct timeval tzero = {.tv_sec = 0, .tv_usec = 0}; + +if (smtp_inptr < smtp_inend) + return TRUE; + +fd = fileno(smtp_in); +FD_ZERO(&fds); +FD_SET(fd, &fds); +rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero); + +if (rc <= 0) return FALSE; /* Not ready to read */ +rc = smtp_getc(GETC_BUFFER_UNLIMITED); +if (rc < 0) return FALSE; /* End of file or error */ + +smtp_ungetc(rc); +return TRUE; +} + + +/******************************************************************************/ +/************************************************* +* Recheck synchronization * +*************************************************/ + +/* Synchronization checks can never be perfect because a packet may be on its +way but not arrived when the check is done. Normally, the checks happen when +commands are read: Exim ensures that there is no more input in the input buffer. +In normal cases, the response to the command will be fast, and there is no +further check. + +However, for some commands an ACL is run, and that can include delays. In those +cases, it is useful to do another check on the input just before sending the +response. This also applies at the start of a connection. This function does +that check by means of the select() function, as long as the facility is not +disabled or inappropriate. A failure of select() is ignored. + +When there is unwanted input, we read it so that it appears in the log of the +error. + +Arguments: none +Returns: TRUE if all is well; FALSE if there is input pending +*/ + +static BOOL +wouldblock_reading(void) +{ +#ifndef DISABLE_TLS +if (tls_in.active.sock >= 0) + return !tls_could_getc(); +#endif + +return !smtp_could_getc(); +} + +static BOOL +check_sync(void) +{ +if (!smtp_enforce_sync || !sender_host_address || f.sender_host_notsocket) + return TRUE; + +return wouldblock_reading(); +} + + +/******************************************************************************/ +/* Variants of the smtp_* input handling functions for use in CHUNKING mode */ + +/* 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 @@ -624,9 +725,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; @@ -658,12 +757,14 @@ 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; } - smtp_printf("250 %u byte chunk received\r\n", FALSE, chunking_datasize); + smtp_printf("250 %u byte chunk received\r\n", SP_NO_MORE, chunking_datasize); chunking_state = CHUNKING_OFFERED; DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state); @@ -701,7 +802,7 @@ next_cmd: case NOOP_CMD: HAD(SCH_NOOP); - smtp_printf("250 OK\r\n", FALSE); + smtp_printf("250 OK\r\n", SP_NO_MORE); goto next_cmd; case BDAT_CMD: @@ -730,9 +831,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 @@ -742,6 +841,14 @@ next_cmd: } } +BOOL +bdat_hasc(void) +{ +if (chunking_data_left > 0) + return lwr_receive_hasc(); +return TRUE; +} + uschar * bdat_getbuf(unsigned * len) { @@ -765,106 +872,66 @@ while (chunking_data_left) if (!bdat_getbuf(&n)) break; } -receive_getc = lwr_receive_getc; -receive_getbuf = lwr_receive_getbuf; -receive_ungetc = lwr_receive_ungetc; - -if (chunking_state != CHUNKING_LAST) - { - chunking_state = CHUNKING_OFFERED; - DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state); - } +bdat_pop_receive_functions(); +chunking_state = CHUNKING_OFFERED; +DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state); } +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; +} -/************************************************* -* SMTP version of ungetc() * -*************************************************/ - -/* Puts a character back in the input buffer. Only ever -called once. - -Arguments: - ch the character - -Returns: the character -*/ - -int -smtp_ungetc(int ch) +static inline void +bdat_pop_receive_functions(void) { -*--smtp_inptr = ch; -return ch; -} +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; +} 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); } -/************************************************* -* SMTP version of feof() * -*************************************************/ - -/* Tests for a previous EOF - -Arguments: none -Returns: non-zero if the eof flag is set -*/ - -int -smtp_feof(void) -{ -return smtp_had_eof; -} - - - - -/************************************************* -* SMTP version of ferror() * -*************************************************/ - -/* Tests for a previous read error, and returns with errno -restored to what it was when the error was detected. - -Arguments: none -Returns: non-zero if the error flag is set -*/ - -int -smtp_ferror(void) -{ -errno = smtp_had_error; -return smtp_had_error; -} - - - -/************************************************* -* Test for characters in the SMTP buffer * -*************************************************/ - -/* Used at the end of a message - -Arguments: none -Returns: TRUE/FALSE -*/ - -BOOL -smtp_buffered(void) -{ -return smtp_inptr < smtp_inend; -} - - +/******************************************************************************/ /************************************************* * Write formatted string to SMTP channel * @@ -918,15 +985,12 @@ that we'll never expand it. */ yield = !! string_vformat(&gs, SVFMT_TAINT_NOCHK, format, ap); string_from_gstring(&gs); -DEBUG(D_receive) - { - uschar *msg_copy, *cr, *end; - msg_copy = string_copy(gs.s); - end = msg_copy + gs.ptr; - while ((cr = Ustrchr(msg_copy, '\r')) != NULL) /* lose CRs */ - memmove(cr, cr + 1, (end--) - cr); - debug_printf("SMTP>> %s", msg_copy); - } +DEBUG(D_receive) for (const uschar * t, * s = gs.s; + s && (t = Ustrchr(s, '\r')); + s = t + 2) /* \r\n */ + debug_printf("%s %.*s\n", + s == gs.s ? "SMTP>>" : " ", + (int)(t - s), s); if (!yield) { @@ -943,7 +1007,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(big_buffer); else if (fl.rcpt_smtp_response_same && Ustrcmp(rcpt_smtp_response, big_buffer) != 0) @@ -994,540 +1058,72 @@ return smtp_write_error; -/************************************************* -* SMTP command read timeout * -*************************************************/ - -/* Signal handler for timing out incoming SMTP commands. This attempts to -finish off tidily. - -Argument: signal number (SIGALRM) -Returns: nothing -*/ - -static void -command_timeout_handler(int sig) -{ -had_command_timeout = sig; -} - - - -/************************************************* -* SIGTERM received * -*************************************************/ - -/* Signal handler for handling SIGTERM. Again, try to finish tidily. - -Argument: signal number (SIGTERM) -Returns: nothing -*/ - -static void -command_sigterm_handler(int sig) -{ -had_command_sigterm = sig; -} - - - - -#ifdef SUPPORT_PROXY -/************************************************* -* Check if host is required proxy host * -*************************************************/ -/* The function determines if inbound host will be a regular smtp host -or if it is configured that it must use Proxy Protocol. A local -connection cannot. - -Arguments: none -Returns: bool -*/ +/* If there's input waiting (and we're doing pipelineing) then we can pipeline +a reponse with the one following. */ static BOOL -check_proxy_protocol_host() +pipeline_response(void) { -int rc; +if ( !smtp_enforce_sync || !sender_host_address + || f.sender_host_notsocket || !f.smtp_in_pipelining_advertised) + return FALSE; -if ( sender_host_address - && (rc = verify_check_this_host(CUSS &hosts_proxy, NULL, NULL, - sender_host_address, NULL)) == OK) - { - DEBUG(D_receive) - debug_printf("Detected proxy protocol configured host\n"); - proxy_session = TRUE; - } -return proxy_session; +if (wouldblock_reading()) return FALSE; +f.smtp_in_pipelining_used = TRUE; +return TRUE; } -/************************************************* -* Read data until newline or end of buffer * -*************************************************/ -/* While SMTP is server-speaks-first, TLS is client-speaks-first, so we can't -read an entire buffer and assume there will be nothing past a proxy protocol -header. Our approach normally is to use stdio, but again that relies upon -"STARTTLS\r\n" and a server response before the client starts TLS handshake, or -reading _nothing_ before client TLS handshake. So we don't want to use the -usual buffering reads which may read enough to block TLS starting. - -So unfortunately we're down to "read one byte at a time, with a syscall each, -and expect a little overhead", for all proxy-opened connections which are v1, -just to handle the TLS-on-connect case. Since SSL functions wrap the -underlying fd, we can't assume that we can feed them any already-read content. - -We need to know where to read to, the max capacity, and we'll read until we -get a CR and one more character. Let the caller scream if it's CR+!LF. - -Return the amount read. -*/ - -static int -swallow_until_crlf(int fd, uschar *base, int already, int capacity) +#ifndef DISABLE_PIPE_CONNECT +static BOOL +pipeline_connect_sends(void) { -uschar *to = base + already; -uschar *cr; -int have = 0; -int ret; -int last = 0; - -/* For "PROXY UNKNOWN\r\n" we, at time of writing, expect to have read -up through the \r; for the _normal_ case, we haven't yet seen the \r. */ - -cr = memchr(base, '\r', already); -if (cr != NULL) - { - if ((cr - base) < already - 1) - { - /* \r and presumed \n already within what we have; probably not - actually proxy protocol, but abort cleanly. */ - return 0; - } - /* \r is last character read, just need one more. */ - last = 1; - } - -while (capacity > 0) - { - do { ret = read(fd, to, 1); } while (ret == -1 && errno == EINTR && !had_command_timeout); - if (ret == -1) - return -1; - have++; - if (last) - return have; - if (*to == '\r') - last = 1; - capacity--; - to++; - } +if (!sender_host_address || f.sender_host_notsocket || !fl.pipe_connect_acceptable) + return FALSE; -/* reached end without having room for a final newline, abort */ -errno = EOVERFLOW; -return -1; +if (wouldblock_reading()) return FALSE; +f.smtp_in_early_pipe_used = TRUE; +return TRUE; } +#endif /************************************************* -* Setup host for proxy protocol * +* SMTP command read timeout * *************************************************/ -/* The function configures the connection based on a header from the -inbound host to use Proxy Protocol. The specification is very exact -so exit with an error if do not find the exact required pieces. This -includes an incorrect number of spaces separating args. - -Arguments: none -Returns: Boolean success -*/ - -static void -setup_proxy_protocol_host() -{ -union { - struct { - uschar line[108]; - } v1; - struct { - uschar sig[12]; - uint8_t ver_cmd; - uint8_t fam; - uint16_t len; - union { - struct { /* TCP/UDP over IPv4, len = 12 */ - uint32_t src_addr; - uint32_t dst_addr; - uint16_t src_port; - uint16_t dst_port; - } ip4; - struct { /* TCP/UDP over IPv6, len = 36 */ - uint8_t src_addr[16]; - uint8_t dst_addr[16]; - uint16_t src_port; - uint16_t dst_port; - } ip6; - struct { /* AF_UNIX sockets, len = 216 */ - uschar src_addr[108]; - uschar dst_addr[108]; - } unx; - } addr; - } v2; -} hdr; - -/* Temp variables used in PPv2 address:port parsing */ -uint16_t tmpport; -char tmpip[INET_ADDRSTRLEN]; -struct sockaddr_in tmpaddr; -char tmpip6[INET6_ADDRSTRLEN]; -struct sockaddr_in6 tmpaddr6; - -/* We can't read "all data until end" because while SMTP is -server-speaks-first, the TLS handshake is client-speaks-first, so for -TLS-on-connect ports the proxy protocol header will usually be immediately -followed by a TLS handshake, and with N TLS libraries, we can't reliably -reinject data for reading by those. So instead we first read "enough to be -safely read within the header, and figure out how much more to read". -For v1 we will later read to the end-of-line, for v2 we will read based upon -the stated length. - -The v2 sig is 12 octets, and another 4 gets us the length, so we know how much -data is needed total. For v1, where the line looks like: -PROXY TCPn L3src L3dest SrcPort DestPort \r\n - -However, for v1 there's also `PROXY UNKNOWN\r\n` which is only 15 octets. -We seem to support that. So, if we read 14 octets then we can tell if we're -v2 or v1. If we're v1, we can continue reading as normal. - -If we're v2, we can't slurp up the entire header. We need the length in the -15th & 16th octets, then to read everything after that. - -So to safely handle v1 and v2, with client-sent-first supported correctly, -we have to do a minimum of 3 read calls, not 1. Eww. -*/ - -#define PROXY_INITIAL_READ 14 -#define PROXY_V2_HEADER_SIZE 16 -#if PROXY_INITIAL_READ > PROXY_V2_HEADER_SIZE -# error Code bug in sizes of data to read for proxy usage -#endif - -int get_ok = 0; -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 */ -socklen_t vslen = sizeof(struct timeval); -BOOL yield = FALSE; - -os_non_restarting_signal(SIGALRM, command_timeout_handler); -ALARM(proxy_protocol_timeout); - -do - { - /* The inbound host was declared to be a Proxy Protocol host, so - 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 = read(fd, &hdr, PROXY_INITIAL_READ); - } - while (ret == -1 && errno == EINTR && !had_command_timeout); - -if (ret == -1) - goto proxyfail; - -/* For v2, handle reading the length, and then the rest. */ -if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0)) - { - int retmore; - uint8_t ver; - - /* First get the length fields. */ - do - { - 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; - ver = (hdr.v2.ver_cmd & 0xf0) >> 4; - - /* May 2014: haproxy combined the version and command into one byte to - allow two full bytes for the length field in order to proxy SSL - connections. SSL Proxy is not supported in this version of Exim, but - must still separate values here. */ - - if (ver != 0x02) - { - DEBUG(D_receive) debug_printf("Invalid Proxy Protocol version: %d\n", ver); - goto proxyfail; - } - - /* The v2 header will always be 16 bytes per the spec. */ - size = 16 + ntohs(hdr.v2.len); - DEBUG(D_receive) debug_printf("Detected PROXYv2 header, size %d (limit %d)\n", - size, (int)sizeof(hdr)); - - /* We should now have 16 octets (PROXY_V2_HEADER_SIZE), and we know the total - amount that we need. Double-check that the size is not unreasonable, then - get the rest. */ - if (size > sizeof(hdr)) - { - DEBUG(D_receive) debug_printf("PROXYv2 header size unreasonably large; security attack?\n"); - goto proxyfail; - } +/* Signal handler for timing out incoming SMTP commands. This attempts to +finish off tidily. - do - { - do - { - retmore = read(fd, (uschar*)&hdr + ret, size-ret); - } while (retmore == -1 && errno == EINTR && !had_command_timeout); - if (retmore == -1) - goto proxyfail; - ret += retmore; - DEBUG(D_receive) debug_printf("PROXYv2: have %d/%d required octets\n", ret, size); - } while (ret < size); +Argument: signal number (SIGALRM) +Returns: nothing +*/ - } /* end scope for getting rest of data for v2 */ +static void +command_timeout_handler(int sig) +{ +had_command_timeout = sig; +} -/* At this point: if PROXYv2, we've read the exact size required for all data; -if PROXYv1 then we've read "less than required for any valid line" and should -read the rest". */ -if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0) - { - uint8_t cmd = (hdr.v2.ver_cmd & 0x0f); - switch (cmd) - { - case 0x01: /* PROXY command */ - switch (hdr.v2.fam) - { - case 0x11: /* TCPv4 address type */ - iptype = US"IPv4"; - tmpaddr.sin_addr.s_addr = hdr.v2.addr.ip4.src_addr; - inet_ntop(AF_INET, &tmpaddr.sin_addr, CS &tmpip, sizeof(tmpip)); - if (!string_is_ip_address(US tmpip, NULL)) - { - DEBUG(D_receive) debug_printf("Invalid %s source IP\n", iptype); - goto proxyfail; - } - proxy_local_address = sender_host_address; - sender_host_address = string_copy(US tmpip); - tmpport = ntohs(hdr.v2.addr.ip4.src_port); - proxy_local_port = sender_host_port; - sender_host_port = tmpport; - /* Save dest ip/port */ - tmpaddr.sin_addr.s_addr = hdr.v2.addr.ip4.dst_addr; - inet_ntop(AF_INET, &tmpaddr.sin_addr, CS &tmpip, sizeof(tmpip)); - if (!string_is_ip_address(US tmpip, NULL)) - { - DEBUG(D_receive) debug_printf("Invalid %s dest port\n", iptype); - goto proxyfail; - } - proxy_external_address = string_copy(US tmpip); - tmpport = ntohs(hdr.v2.addr.ip4.dst_port); - proxy_external_port = tmpport; - goto done; - case 0x21: /* TCPv6 address type */ - iptype = US"IPv6"; - memmove(tmpaddr6.sin6_addr.s6_addr, hdr.v2.addr.ip6.src_addr, 16); - inet_ntop(AF_INET6, &tmpaddr6.sin6_addr, CS &tmpip6, sizeof(tmpip6)); - if (!string_is_ip_address(US tmpip6, NULL)) - { - DEBUG(D_receive) debug_printf("Invalid %s source IP\n", iptype); - goto proxyfail; - } - proxy_local_address = sender_host_address; - sender_host_address = string_copy(US tmpip6); - tmpport = ntohs(hdr.v2.addr.ip6.src_port); - proxy_local_port = sender_host_port; - sender_host_port = tmpport; - /* Save dest ip/port */ - memmove(tmpaddr6.sin6_addr.s6_addr, hdr.v2.addr.ip6.dst_addr, 16); - inet_ntop(AF_INET6, &tmpaddr6.sin6_addr, CS &tmpip6, sizeof(tmpip6)); - if (!string_is_ip_address(US tmpip6, NULL)) - { - DEBUG(D_receive) debug_printf("Invalid %s dest port\n", iptype); - goto proxyfail; - } - proxy_external_address = string_copy(US tmpip6); - tmpport = ntohs(hdr.v2.addr.ip6.dst_port); - proxy_external_port = tmpport; - goto done; - default: - DEBUG(D_receive) - debug_printf("Unsupported PROXYv2 connection type: 0x%02x\n", - hdr.v2.fam); - goto proxyfail; - } - /* Unsupported protocol, keep local connection address */ - break; - case 0x00: /* LOCAL command */ - /* Keep local connection address for LOCAL */ - iptype = US"local"; - break; - default: - DEBUG(D_receive) - debug_printf("Unsupported PROXYv2 command: 0x%x\n", cmd); - goto proxyfail; - } - } -else if (ret >= 8 && memcmp(hdr.v1.line, "PROXY", 5) == 0) - { - uschar *p; - uschar *end; - uschar *sp; /* Utility variables follow */ - int tmp_port; - int r2; - char *endc; - - /* get the rest of the line */ - r2 = swallow_until_crlf(fd, (uschar*)&hdr, ret, sizeof(hdr)-ret); - if (r2 == -1) - goto proxyfail; - ret += r2; - - p = string_copy(hdr.v1.line); - end = memchr(p, '\r', ret - 1); - - if (!end || (end == (uschar*)&hdr + ret) || end[1] != '\n') - { - DEBUG(D_receive) debug_printf("Partial or invalid PROXY header\n"); - goto proxyfail; - } - *end = '\0'; /* Terminate the string */ - size = end + 2 - p; /* Skip header + CRLF */ - DEBUG(D_receive) debug_printf("Detected PROXYv1 header\n"); - DEBUG(D_receive) debug_printf("Bytes read not within PROXY header: %d\n", ret - size); - /* Step through the string looking for the required fields. Ensure - strict adherence to required formatting, exit for any error. */ - p += 5; - if (!isspace(*(p++))) - { - DEBUG(D_receive) debug_printf("Missing space after PROXY command\n"); - goto proxyfail; - } - if (!Ustrncmp(p, CCS"TCP4", 4)) - iptype = US"IPv4"; - else if (!Ustrncmp(p,CCS"TCP6", 4)) - iptype = US"IPv6"; - else if (!Ustrncmp(p,CCS"UNKNOWN", 7)) - { - iptype = US"Unknown"; - goto done; - } - else - { - DEBUG(D_receive) debug_printf("Invalid TCP type\n"); - goto proxyfail; - } +/************************************************* +* SIGTERM received * +*************************************************/ - p += Ustrlen(iptype); - if (!isspace(*(p++))) - { - DEBUG(D_receive) debug_printf("Missing space after TCP4/6 command\n"); - goto proxyfail; - } - /* Find the end of the arg */ - if ((sp = Ustrchr(p, ' ')) == NULL) - { - DEBUG(D_receive) - debug_printf("Did not find proxied src %s\n", iptype); - goto proxyfail; - } - *sp = '\0'; - if(!string_is_ip_address(p, NULL)) - { - DEBUG(D_receive) - debug_printf("Proxied src arg is not an %s address\n", iptype); - goto proxyfail; - } - proxy_local_address = sender_host_address; - sender_host_address = p; - p = sp + 1; - if ((sp = Ustrchr(p, ' ')) == NULL) - { - DEBUG(D_receive) - debug_printf("Did not find proxy dest %s\n", iptype); - goto proxyfail; - } - *sp = '\0'; - if(!string_is_ip_address(p, NULL)) - { - DEBUG(D_receive) - debug_printf("Proxy dest arg is not an %s address\n", iptype); - goto proxyfail; - } - proxy_external_address = p; - p = sp + 1; - if ((sp = Ustrchr(p, ' ')) == NULL) - { - DEBUG(D_receive) debug_printf("Did not find proxied src port\n"); - goto proxyfail; - } - *sp = '\0'; - tmp_port = strtol(CCS p, &endc, 10); - if (*endc || tmp_port == 0) - { - DEBUG(D_receive) - debug_printf("Proxied src port '%s' not an integer\n", p); - goto proxyfail; - } - proxy_local_port = sender_host_port; - sender_host_port = tmp_port; - p = sp + 1; - if ((sp = Ustrchr(p, '\0')) == NULL) - { - DEBUG(D_receive) debug_printf("Did not find proxy dest port\n"); - goto proxyfail; - } - tmp_port = strtol(CCS p, &endc, 10); - if (*endc || tmp_port == 0) - { - DEBUG(D_receive) - debug_printf("Proxy dest port '%s' not an integer\n", p); - goto proxyfail; - } - proxy_external_port = tmp_port; - /* Already checked for /r /n above. Good V1 header received. */ - } -else - { - /* Wrong protocol */ - DEBUG(D_receive) debug_printf("Invalid proxy protocol version negotiation\n"); - (void) swallow_until_crlf(fd, (uschar*)&hdr, ret, sizeof(hdr)-ret); - goto proxyfail; - } +/* Signal handler for handling SIGTERM. Again, try to finish tidily. -done: - DEBUG(D_receive) - debug_printf("Valid %s sender from Proxy Protocol header\n", iptype); - yield = proxy_session; +Argument: signal number (SIGTERM) +Returns: nothing +*/ -/* Don't flush any potential buffer contents. Any input on proxyfail -should cause a synchronization failure */ +static void +command_sigterm_handler(int sig) +{ +had_command_sigterm = sig; +} -proxyfail: - DEBUG(D_receive) if (had_command_timeout) - debug_printf("Timeout while reading proxy header\n"); -bad: - if (yield) - { - sender_host_name = NULL; - (void) host_name_lookup(); - host_build_sender_fullhost(); - } - else - { - f.proxy_session_failed = TRUE; - DEBUG(D_receive) - debug_printf("Failure to extract proxied host, only QUIT allowed\n"); - } -ALARM(0); -return; -} -#endif /************************************************* * Read one command line * @@ -1601,7 +1197,7 @@ if (hadnull) return BADCHAR_CMD; to the start of the actual data characters. Check for SMTP synchronization if required. */ -for (smtp_cmd_list * p = cmd_list; p < cmd_list_end; p++) +for (smtp_cmd_list * p = cmd_list; p < cmd_list + nelem(cmd_list); p++) { #ifdef SUPPORT_PROXY /* Only allow QUIT command if Proxy Protocol parsing failed */ @@ -1615,12 +1211,13 @@ for (smtp_cmd_list * p = cmd_list; p < cmd_list_end; p++) || smtp_cmd_buffer[p->len] == ' ' ) ) { - if (smtp_inptr < smtp_inend && /* Outstanding input */ - p->cmd < sync_cmd_limit && /* Command should sync */ - check_sync && /* Local flag set */ - smtp_enforce_sync && /* Global flag set */ - sender_host_address != NULL && /* Not local input */ - !f.sender_host_notsocket) /* Really is a socket */ + if ( smtp_inptr < smtp_inend /* Outstanding input */ + && p->cmd < sync_cmd_limit /* Command should sync */ + && check_sync /* Local flag set */ + && smtp_enforce_sync /* Global flag set */ + && sender_host_address != NULL /* Not local input */ + && !f.sender_host_notsocket /* Really is a socket */ + ) return BADSYN_CMD; /* The variables $smtp_command and $smtp_command_argument point into the @@ -1630,7 +1227,7 @@ for (smtp_cmd_list * p = cmd_list; p < cmd_list_end; 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; @@ -1669,7 +1266,8 @@ if ( smtp_inptr < smtp_inend /* Outstanding input */ && check_sync /* Local flag set */ && smtp_enforce_sync /* Global flag set */ && sender_host_address /* Not local input */ - && !f.sender_host_notsocket) /* Really is a socket */ + && !f.sender_host_notsocket /* Really is a socket */ + ) return BADSYN_CMD; return OTHER_CMD; @@ -1677,6 +1275,7 @@ return OTHER_CMD; + /************************************************* * Forced closedown of call * *************************************************/ @@ -1695,11 +1294,11 @@ Returns: nothing */ void -smtp_closedown(uschar *message) +smtp_closedown(uschar * message) { if (!smtp_in || smtp_batched_input) return; receive_swallow_smtp(); -smtp_printf("421 %s\r\n", FALSE, message); +smtp_printf("421 %s\r\n", SP_NO_MORE, message); for (;;) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED)) { @@ -1708,16 +1307,16 @@ for (;;) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED)) case QUIT_CMD: f.smtp_in_quit = TRUE; - smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname); + smtp_printf("221 %s closing connection\r\n", SP_NO_MORE, smtp_active_hostname); mac_smtp_fflush(); return; case RSET_CMD: - smtp_printf("250 Reset OK\r\n", FALSE); + smtp_printf("250 Reset OK\r\n", SP_NO_MORE); break; default: - smtp_printf("421 %s\r\n", FALSE, message); + smtp_printf("421 %s\r\n", SP_NO_MORE, message); break; } } @@ -1743,21 +1342,29 @@ smtp_get_connection_info(void) { const uschar * hostname = sender_fullhost ? sender_fullhost : sender_host_address; +gstring * g = string_catn(NULL, US"SMTP connection", 15); + +if (LOGGING(connection_id)) + g = string_fmt_append(g, " Ci=%lu", connection_id); +g = string_catn(g, US" from ", 6); if (host_checking) - return string_sprintf("SMTP connection from %s", hostname); + g = string_cat(g, hostname); + +else if (f.sender_host_unknown || f.sender_host_notsocket) + g = string_cat(g, sender_ident ? sender_ident : US"NULL"); -if (f.sender_host_unknown || f.sender_host_notsocket) - return string_sprintf("SMTP connection from %s", sender_ident); +else if (f.is_inetd) + g = string_append(g, 2, hostname, US" (via inetd)"); -if (f.is_inetd) - return string_sprintf("SMTP connection from %s (via inetd)", hostname); +else if (LOGGING(incoming_interface) && interface_address) + g = string_fmt_append(g, "%s I=[%s]:%d", hostname, interface_address, interface_port); -if (LOGGING(incoming_interface) && interface_address) - return string_sprintf("SMTP connection from %s I=[%s]:%d", hostname, - interface_address, interface_port); +else + g = string_cat(g, hostname); -return string_sprintf("SMTP connection from %s", hostname); +gstring_release_unused(g); +return string_from_gstring(g); } @@ -1967,30 +1574,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--; -v[1] = '\0'; +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 == '"') && (v > smtp_cmd_data + 1)) - 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; @@ -2090,8 +1702,12 @@ prdr_requested = FALSE; #ifdef SUPPORT_I18N message_smtputf8 = FALSE; #endif +#ifdef WITH_CONTENT_SCAN +regex_vars_clear(); +#endif body_linecount = body_zerocount = 0; +lookup_value = NULL; /* Can be set by ACL */ sender_rate = sender_rate_limit = sender_rate_period = NULL; ratelimiters_mail = NULL; /* Updated by ratelimit ACL condition */ /* Note that ratelimiters_conn persists across resets. */ @@ -2182,7 +1798,6 @@ while (done <= 0) bsmtp_transaction_linecount = receive_linecount; break; - /* The MAIL FROM command requires an address as an operand. All we do here is to parse it for syntactic correctness. The form "<>" is a special case which converts into an empty string. The start/end @@ -2206,9 +1821,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 */ @@ -2228,7 +1845,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); } @@ -2258,16 +1876,18 @@ 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 */ 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; @@ -2286,7 +1906,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 @@ -2363,9 +1984,9 @@ return done - 2; /* Convert yield values */ #ifndef DISABLE_TLS static BOOL -smtp_log_tls_fail(uschar * errstr) +smtp_log_tls_fail(const uschar * errstr) { -uschar * conn_info = smtp_get_connection_info(); +const uschar * conn_info = smtp_get_connection_info(); if (Ustrncmp(conn_info, US"SMTP ", 5) == 0) conn_info += 5; /* I'd like to get separated H= here, but too hard for now */ @@ -2428,6 +2049,25 @@ else DEBUG(D_receive) #endif +static void +log_connect_tls_drop(const uschar * what, const uschar * log_msg) +{ +if (log_reject_target) + { + gstring * g = s_tlslog(NULL); + uschar * tls = string_from_gstring(g); + + log_write(L_connection_reject, + log_reject_target, "%s%s%s dropped by %s%s%s", + LOGGING(dnssec) && sender_host_dnssec ? US" DS" : US"", + host_and_ident(TRUE), + tls ? tls : US"", + what, + log_msg ? US": " : US"", log_msg); + } +} + + /************************************************* * Start an SMTP session * *************************************************/ @@ -2493,7 +2133,7 @@ acl_var_c = NULL; /* Allow for trailing 0 in the command and data buffers. Tainted. */ -smtp_cmd_buffer = store_get_perm(2*SMTP_CMD_BUFFER_SIZE + 2, TRUE); +smtp_cmd_buffer = store_get_perm(2*SMTP_CMD_BUFFER_SIZE + 2, GET_TAINTED); smtp_cmd_buffer[0] = 0; smtp_data_buffer = smtp_cmd_buffer + SMTP_CMD_BUFFER_SIZE + 1; @@ -2514,25 +2154,25 @@ else (sender_host_address ? protocols : protocols_local) [pnormal]; /* Set up the buffer for inputting using direct read() calls, and arrange to -call the local functions instead of the standard C ones. Place a NUL at the -end of the buffer to safety-stop C-string reads from it. */ +call the local functions instead of the standard C ones. */ -if (!(smtp_inbuffer = US malloc(IN_BUFFER_SIZE))) - log_write(0, LOG_MAIN|LOG_PANIC_DIE, "malloc() failed for SMTP input buffer"); -smtp_inbuffer[IN_BUFFER_SIZE-1] = '\0'; +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; -receive_smtp_buffered = smtp_buffered; -smtp_inptr = smtp_inend = smtp_inbuffer; -smtp_had_eof = smtp_had_error = 0; +lwr_receive_getc = NULL; +lwr_receive_getbuf = NULL; +lwr_receive_hasc = NULL; +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) { @@ -2588,32 +2228,32 @@ 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 +# 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 +# if OPTSTYLE == 1 EXIM_SOCKLEN_T optlen = sizeof(struct ip_options) + MAX_IPOPTLEN; - struct ip_options *ipopt = store_get(optlen, FALSE); - #elif OPTSTYLE == 2 + 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); - #else +# else struct ipoption ipoptblock; struct ipoption *ipopt = &ipoptblock; EXIM_SOCKLEN_T optlen = sizeof(ipoptblock); - #endif +# endif /* Occasional genuine failures of getsockopt() have been seen - for example, "reset by peer". Therefore, just log and give up on this @@ -2631,7 +2271,7 @@ if (!f.sender_host_unknown) { log_write(0, LOG_MAIN, "getsockopt() failed from %s: %s", host_and_ident(FALSE), strerror(errno)); - smtp_printf("451 SMTP service not available\r\n", FALSE); + smtp_printf("451 SMTP service not available\r\n", SP_NO_MORE); return FALSE; } } @@ -2643,19 +2283,19 @@ if (!f.sender_host_unknown) else if (optlen > 0) { - uschar *p = big_buffer; - uschar *pend = big_buffer + big_buffer_size; - uschar *adptr; + 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 +# 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"); @@ -2666,59 +2306,65 @@ if (!f.sender_host_unknown) switch (*opt) { case IPOPT_EOL: - opt = NULL; - break; + opt = NULL; + break; case IPOPT_NOP: - opt++; - break; + opt++; + break; case IPOPT_SSRR: case IPOPT_LSRR: - if (!string_format(p, pend-p, " %s [@%s", - (*opt == IPOPT_SSRR)? "SSRR" : "LSRR", - #if OPTSTYLE == 1 - inet_ntoa(*((struct in_addr *)(&(ipopt->faddr)))))) - #elif OPTSTYLE == 2 - inet_ntoa(ipopt->ip_dst))) - #else - 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; + 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; + { + 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; @@ -2729,7 +2375,7 @@ if (!f.sender_host_unknown) 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", FALSE); + smtp_printf("554 SMTP service not available\r\n", SP_NO_MORE); return FALSE; } @@ -2781,7 +2427,10 @@ if (!f.sender_host_unknown) { log_write(L_connection_reject, LOG_MAIN|LOG_REJECT, "refused connection " "from %s (host_reject_connection)", host_and_ident(FALSE)); - smtp_printf("554 SMTP service not available\r\n", FALSE); +#ifndef DISABLE_TLS + if (!tls_in.on_connect) +#endif + smtp_printf("554 SMTP service not available\r\n", SP_NO_MORE); return FALSE; } @@ -2812,7 +2461,7 @@ if (!f.sender_host_unknown) log_write(L_connection_reject, LOG_MAIN|LOG_REJECT, "refused connection from %s " "(tcp wrappers)", host_and_ident(FALSE)); - smtp_printf("554 SMTP service not available\r\n", FALSE); + smtp_printf("554 SMTP service not available\r\n", SP_NO_MORE); } else { @@ -2822,7 +2471,7 @@ if (!f.sender_host_unknown) log_write(L_connection_reject, LOG_MAIN|LOG_REJECT, "temporarily refused connection from %s " "(tcp wrappers errno=%d)", host_and_ident(FALSE), save_errno); - smtp_printf("451 Temporary local problem - please try later\r\n", FALSE); + smtp_printf("451 Temporary local problem - please try later\r\n", SP_NO_MORE); } return FALSE; } @@ -2842,7 +2491,7 @@ if (!f.sender_host_unknown) host_and_ident(FALSE), smtp_accept_count - 1, smtp_accept_max, smtp_accept_reserve, (rc == DEFER)? " (lookup deferred)" : ""); smtp_printf("421 %s: Too many concurrent SMTP connections; " - "please try again later\r\n", FALSE, smtp_active_hostname); + "please try again later\r\n", SP_NO_MORE, smtp_active_hostname); return FALSE; } reserved_host = TRUE; @@ -2863,7 +2512,7 @@ if (!f.sender_host_unknown) LOG_MAIN, "temporarily refused connection from %s: not in " "reserve list and load average = %.2f", host_and_ident(FALSE), (double)load_average/1000.0); - smtp_printf("421 %s: Too much load; please try again later\r\n", FALSE, + smtp_printf("421 %s: Too much load; please try again later\r\n", SP_NO_MORE, smtp_active_hostname); return FALSE; } @@ -2883,8 +2532,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 @@ -2893,46 +2542,63 @@ 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 valid Proxy Protocol source is connecting, set up session. -Failure will not allow any SMTP function other than QUIT. */ - -#ifdef SUPPORT_PROXY +#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS) || defined(EXPERIMETAL_XCLIENT) proxy_session = FALSE; -f.proxy_session_failed = FALSE; -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. */ +#ifdef SUPPORT_PROXY +/* If valid Proxy Protocol source is connecting, set up session. +Failure will not allow any SMTP function other than QUIT. */ -#ifndef DISABLE_TLS -if (tls_in.on_connect) +f.proxy_session_failed = FALSE; +if (proxy_protocol_host()) { - if (tls_server_start(&user_msg) != OK) - return smtp_log_tls_fail(user_msg); - cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE; + os_non_restarting_signal(SIGALRM, command_timeout_handler); + proxy_protocol_setup(); } #endif /* Run the connect ACL if it exists */ user_msg = NULL; +GET_OPTION("acl_smtp_connect"); if (acl_smtp_connect) { int rc; if ((rc = acl_check(ACL_WHERE_CONNECT, NULL, acl_smtp_connect, &user_msg, &log_msg)) != OK) { - (void) smtp_handle_acl_fail(ACL_WHERE_CONNECT, rc, user_msg, log_msg); +#ifndef DISABLE_TLS + if (tls_in.on_connect) + log_connect_tls_drop(US"'connect' ACL", log_msg); + else +#endif + (void) smtp_handle_acl_fail(ACL_WHERE_CONNECT, rc, user_msg, log_msg); return FALSE; } } +/* 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(&user_msg) != OK) + return smtp_log_tls_fail(user_msg); + cmd_list[CL_TLAU].is_mail_cmd = TRUE; + } +#endif + /* Output the initial message for a two-way SMTP connection. It may contain newlines, which then cause a multi-line response to be given. */ @@ -2940,13 +2606,7 @@ code = US"220"; /* Default status code */ esc = US""; /* Default extended status code */ esclen = 0; /* Length of esc */ -if (!user_msg) - { - if (!(s = expand_string(smtp_banner))) - log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" (smtp_banner) " - "failed: %s", smtp_banner, expand_string_message); - } -else +if (user_msg) { int codelen = 3; s = user_msg; @@ -2957,12 +2617,27 @@ else esclen = codelen - 4; } } +else + { + 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 */ 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 @@ -3021,20 +2696,20 @@ if (!check_sync()) "synchronization error (input sent without waiting for greeting): " "rejected connection from %s input=\"%s\"", host_and_ident(TRUE), string_printing(string_copyn(smtp_inptr, n))); - smtp_printf("554 SMTP synchronization error\r\n", FALSE); + smtp_printf("554 SMTP synchronization error\r\n", SP_NO_MORE); return FALSE; } /* Now output the banner */ /*XXX the ehlo-resp code does its own tls/nontls bit. Maybe subroutine that? */ -smtp_printf("%s", +smtp_printf("%Y", #ifndef DISABLE_PIPE_CONNECT fl.pipe_connect_acceptable && pipeline_connect_sends(), #else - FALSE, + SP_NO_MORE, #endif - string_from_gstring(ss)); + ss); /* Attempt to see if we sent the banner before the last ACK of the 3-way handshake arrived. If so we must have managed a TFO. */ @@ -3083,18 +2758,18 @@ 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\", %s)", + "syntax or protocol errors (last command was \"%s\", %Y)", host_and_ident(FALSE), string_printing(smtp_cmd_buffer), - string_from_gstring(s_connhad_log(NULL)) + s_connhad_log(NULL) ); } if (code > 0) { - smtp_printf("%d%c%s%s%s\r\n", FALSE, code, yield == 1 ? '-' : ' ', + smtp_printf("%d%c%s%s%s\r\n", SP_NO_MORE, code, yield == 1 ? '-' : ' ', data ? data : US"", data ? US": " : US"", errmess); if (yield == 1) - smtp_printf("%d Too many syntax or protocol errors\r\n", FALSE, code); + smtp_printf("%d Too many syntax or protocol errors\r\n", SP_NO_MORE, code); } return yield; @@ -3121,7 +2796,7 @@ Returns: nothing */ void -smtp_respond(uschar* code, int codelen, BOOL final, uschar *msg) +smtp_respond(uschar * code, int codelen, BOOL final, uschar * msg) { int esclen = 0; uschar *esc = US""; @@ -3142,7 +2817,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) @@ -3157,7 +2832,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; @@ -3170,7 +2845,7 @@ for (;;) } else { - smtp_printf("%.3s-%.*s%.*s\r\n", TRUE, code, esclen, esc, (int)(nl - msg), msg); + smtp_printf("%.3s-%.*s%.*s\r\n", SP_MORE, code, esclen, esc, (int)(nl - msg), msg); msg = nl + 1; Uskip_whitespace(&msg); } @@ -3213,27 +2888,26 @@ void smtp_message_code(uschar **code, int *codelen, uschar **msg, uschar **log_msg, BOOL check_valid) { -int n; -int ovector[3]; +uschar * match; +int len; -if (!msg || !*msg) return; - -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; } @@ -3277,7 +2951,7 @@ Returns: 0 in most cases */ int -smtp_handle_acl_fail(int where, int rc, uschar *user_msg, uschar *log_msg) +smtp_handle_acl_fail(int where, int rc, uschar * user_msg, uschar * log_msg) { BOOL drop = rc == FAIL_DROP; int codelen = 3; @@ -3363,7 +3037,7 @@ if (sender_verified_failed && string_sprintf(": %s", sender_verified_failed->message)); if (rc == FAIL && sender_verified_failed->user_message) - smtp_respond(smtp_code, codelen, FALSE, string_sprintf( + smtp_respond(smtp_code, codelen, SR_NOT_FINAL, string_sprintf( testflag(sender_verified_failed, af_verify_pmfail)? "Postmaster verification failed while checking <%s>\n%s\n" "Several RFCs state that you are required to have a postmaster\n" @@ -3395,7 +3069,7 @@ always a 5xx one - see comments at the start of this function. If the original rc was FAIL_DROP we drop the connection and yield 2. */ if (rc == FAIL) - smtp_respond(smtp_code, codelen, TRUE, + smtp_respond(smtp_code, codelen, SR_FINAL, user_msg ? user_msg : US"Administrative prohibition"); /* Send temporary failure response to the command. Don't give any details, @@ -3413,12 +3087,12 @@ else && sender_verified_failed && sender_verified_failed->message ) - smtp_respond(smtp_code, codelen, FALSE, sender_verified_failed->message); + smtp_respond(smtp_code, codelen, SR_NOT_FINAL, sender_verified_failed->message); - smtp_respond(smtp_code, codelen, TRUE, user_msg); + smtp_respond(smtp_code, codelen, SR_FINAL, user_msg); } else - smtp_respond(smtp_code, codelen, TRUE, + smtp_respond(smtp_code, codelen, SR_FINAL, US"Temporary local problem - please try later"); /* Log the incident to the logs that are specified by log_reject_target @@ -3426,7 +3100,7 @@ else the connection is not forcibly to be dropped, return 0. Otherwise, log why it is closing if required and return 2. */ -if (log_reject_target != 0) +if (log_reject_target) { #ifndef DISABLE_TLS gstring * g = s_tlslog(NULL); @@ -3447,14 +3121,26 @@ if (log_reject_target != 0) if (!drop) return 0; -log_write(L_smtp_connection, LOG_MAIN, "%s closed by DROP in ACL", - smtp_get_connection_info()); +log_close_event(US"by DROP in ACL"); /* Run the not-quit ACL, but without any custom messages. This should not be a problem, because we get here only if some other ACL has issued "drop", and in that case, *its* custom messages will have been used above. */ smtp_notquit_exit(US"acl-drop", NULL, NULL); + +/* An overenthusiastic fail2ban/iptables implimentation has been seen to result +in the TCP conn staying open, and retrying, despite this process exiting. A +malicious client could possibly do the same, tying up server netowrking +resources. Close the socket explicitly to try to avoid that (there's a note in +the Linux socket(7) manpage, SO_LINGER para, to the effect that exim() without +close() results in the socket always lingering). */ + +(void) poll_one_fd(fileno(smtp_in), POLLIN, 200); +DEBUG(D_any) debug_printf_indent("SMTP(close)>>\n"); +(void) fclose(smtp_in); +(void) fclose(smtp_out); + return 2; } @@ -3505,6 +3191,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; @@ -3523,7 +3210,7 @@ responses are all internal, they should be reasonable size. */ if (code && defaultrespond) { if (user_msg) - smtp_respond(code, 3, TRUE, user_msg); + smtp_respond(code, 3, SR_FINAL, user_msg); else { gstring * g; @@ -3532,7 +3219,7 @@ if (code && defaultrespond) va_start(ap, defaultrespond); g = string_vformat(NULL, SVFMT_EXTEND|SVFMT_REBUFFER, CS defaultrespond, ap); va_end(ap); - smtp_printf("%s %s\r\n", FALSE, code, string_from_gstring(g)); + smtp_printf("%s %Y\r\n", SP_NO_MORE, code, g); } mac_smtp_fflush(); } @@ -3689,17 +3376,23 @@ smtp_user_msg(uschar *code, uschar *user_msg) { int len = 3; smtp_message_code(&code, &len, &user_msg, NULL, TRUE); -smtp_respond(code, len, TRUE, user_msg); +smtp_respond(code, len, SR_FINAL, user_msg); } static int -smtp_in_auth(auth_instance *au, uschar ** s, uschar ** ss) +smtp_in_auth(auth_instance *au, uschar ** smtp_resp, uschar ** errmsg) { const uschar *set_id = NULL; int rc; +/* Set up globals for error messages */ + +authenticator_name = au->name; +driver_srcfile = au->srcfile; +driver_srcline = au->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 it as the only set numerical variable. The authenticator may set $auth @@ -3720,6 +3413,7 @@ rc = (au->info->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 */ +driver_srcfile = authenticator_name = NULL; driver_srcline = 0; /* The value of authenticated_id is stored in the spool file and printed in log lines. It must not contain binary zeros or newline characters. In @@ -3752,7 +3446,7 @@ switch(rc) received_protocol = (sender_host_address ? protocols : protocols_local) [pextend + pauthed + (tls_in.active.sock >= 0 ? pcrpted:0)]; - *s = *ss = US"235 Authentication succeeded"; + *smtp_resp = *errmsg = US"235 Authentication succeeded"; authenticated_by = au; break; } @@ -3765,34 +3459,34 @@ switch(rc) case DEFER: if (set_id) authenticated_fail_id = string_copy_perm(set_id, TRUE); - *s = string_sprintf("435 Unable to authenticate at present%s", + *smtp_resp = string_sprintf("435 Unable to authenticate at present%s", auth_defer_user_msg); - *ss = string_sprintf("435 Unable to authenticate at present%s: %s", + *errmsg = 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"; + *smtp_resp = *errmsg = US"501 Invalid base64 data"; break; case CANCELLED: - *s = *ss = US"501 Authentication cancelled"; + *smtp_resp = *errmsg = US"501 Authentication cancelled"; break; case UNEXPECTED: - *s = *ss = US"553 Initial data not expected"; + *smtp_resp = *errmsg = 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); + *smtp_resp = US"535 Incorrect authentication data"; + *errmsg = 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 " + *smtp_resp = US"435 Internal error"; + *errmsg = string_sprintf("435 Internal error%s: return %d from authentication " "check", set_id, rc); break; } @@ -3813,10 +3507,11 @@ 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, +smtp_printf("501 %s: recipient address must contain a domain\r\n", SP_NO_MORE, smtp_cmd_data); log_write(L_smtp_syntax_error, LOG_MAIN|LOG_REJECT, "unqualified %s rejected: <%s> %s%s", @@ -3833,6 +3528,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) @@ -3844,39 +3540,29 @@ if ( acl_smtp_quit #endif if (*user_msgp) - smtp_respond(US"221", 3, TRUE, *user_msgp); + smtp_respond(US"221", 3, SR_FINAL, *user_msgp); else - smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname); + smtp_printf("221 %s closing connection\r\n", SP_NO_MORE, smtp_active_hostname); #ifdef SERVERSIDE_CLOSE_NOWAIT # ifndef DISABLE_TLS tls_close(NULL, TLS_SHUTDOWN_NOWAIT); # endif -log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT", - smtp_get_connection_info()); +log_close_event(US"by QUIT"); #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()); +log_close_event(US"by QUIT"); /* 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*/ +(void) poll_one_fd(fileno(smtp_in), POLLIN, 200); +#endif /*!SERVERSIDE_CLOSE_NOWAIT*/ } @@ -3885,8 +3571,10 @@ smtp_rset_handler(void) { HAD(SCH_RSET); incomplete_transaction_log(US"RSET"); -smtp_printf("250 Reset OK\r\n", FALSE); -cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE; +smtp_printf("250 Reset OK\r\n", SP_NO_MORE); +cmd_list[CL_RSET].is_mail_cmd = FALSE; +if (chunking_state > CHUNKING_OFFERED) + chunking_state = CHUNKING_OFFERED; } @@ -3927,7 +3615,6 @@ int smtp_setup_msg(void) { int done = 0; -int mailmax = -1; BOOL toomany = FALSE; BOOL discarded = FALSE; BOOL last_was_rej_mail = FALSE; @@ -3947,13 +3634,21 @@ message_ended = END_NOTSTARTED; chunking_state = f.chunking_offered ? CHUNKING_OFFERED : CHUNKING_NOT_OFFERED; -cmd_list[CMD_LIST_RSET].is_mail_cmd = TRUE; -cmd_list[CMD_LIST_HELO].is_mail_cmd = TRUE; -cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE; +cmd_list[CL_RSET].is_mail_cmd = TRUE; +cmd_list[CL_HELO].is_mail_cmd = TRUE; +cmd_list[CL_EHLO].is_mail_cmd = TRUE; #ifndef DISABLE_TLS -cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = TRUE; +cmd_list[CL_STLS].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; @@ -3999,14 +3694,15 @@ while (done <= 0) if ( tls_in.active.sock >= 0 && tls_in.peercert && tls_in.certificate_verified - && cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd + && cmd_list[CL_TLAU].is_mail_cmd ) { - cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = FALSE; + 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) { + GET_OPTION("acl_smtp_auth"); if ( acl_smtp_auth && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth, &user_msg, &log_msg)) != OK @@ -4019,7 +3715,18 @@ while (done <= 0) if (smtp_in_auth(au, &s, &ss) == OK) { DEBUG(D_auth) debug_printf("tls auth succeeded\n"); } else - { DEBUG(D_auth) debug_printf("tls auth not succeeded\n"); } + { + DEBUG(D_auth) debug_printf("tls auth not succeeded\n"); +#ifndef DISABLE_EVENT + { + uschar * save_name = sender_host_authenticated, * logmsg; + sender_host_authenticated = au->name; + if ((logmsg = event_raise(event_action, US"auth:fail", s, NULL))) + log_write(0, LOG_MAIN, "%s", logmsg); + sender_host_authenticated = save_name; + } +#endif + } } break; } @@ -4051,7 +3758,7 @@ while (done <= 0) case AUTH_CMD: HAD(SCH_AUTH); authentication_failed = TRUE; - cmd_list[CMD_LIST_AUTH].is_mail_cmd = FALSE; + cmd_list[CL_AUTH].is_mail_cmd = FALSE; if (!fl.auth_advertised && !f.allow_auth_unadvertised) { @@ -4074,6 +3781,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 @@ -4099,8 +3807,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 @@ -4109,6 +3817,8 @@ while (done <= 0) { auth_instance * au; + uschar * smtp_resp, * errmsg; + for (au = auths; au; au = au->next) if (strcmpic(s, au->public_name) == 0 && au->server && (au->advertised || f.allow_auth_unadvertised)) @@ -4116,12 +3826,25 @@ while (done <= 0) if (au) { - c = smtp_in_auth(au, &s, &ss); + int rc = smtp_in_auth(au, &smtp_resp, &errmsg); - smtp_printf("%s\r\n", FALSE, s); - if (c != OK) - log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s", - au->name, host_and_ident(FALSE), ss); + smtp_printf("%s\r\n", SP_NO_MORE, smtp_resp); + if (rc != OK) + { + uschar * logmsg = NULL; +#ifndef DISABLE_EVENT + {uschar * save_name = sender_host_authenticated; + sender_host_authenticated = au->name; + logmsg = event_raise(event_action, US"auth:fail", smtp_resp, NULL); + sender_host_authenticated = save_name; + } +#endif + if (logmsg) + 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); + } } else done = synprot_error(L_smtp_protocol_error, 504, NULL, @@ -4158,15 +3881,15 @@ while (done <= 0) fl.esmtp = TRUE; HELO_EHLO: /* Common code for HELO and EHLO */ - cmd_list[CMD_LIST_HELO].is_mail_cmd = FALSE; - cmd_list[CMD_LIST_EHLO].is_mail_cmd = FALSE; + cmd_list[CL_HELO].is_mail_cmd = FALSE; + cmd_list[CL_EHLO].is_mail_cmd = FALSE; /* Reject the HELO if its argument was invalid or non-existent. A successful check causes the argument to be saved in malloc store. */ if (!check_helo(smtp_cmd_data)) { - smtp_printf("501 Syntactically invalid %s argument(s)\r\n", FALSE, hello); + smtp_printf("501 Syntactically invalid %s argument(s)\r\n", SP_NO_MORE, hello); log_write(0, LOG_MAIN|LOG_REJECT, "rejected %s from %s: syntactically " "invalid argument(s): %s", hello, host_and_ident(FALSE), @@ -4176,9 +3899,9 @@ 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\", %s)", + "syntax or protocol errors (last command was \"%s\", %Y)", host_and_ident(FALSE), string_printing(smtp_cmd_buffer), - string_from_gstring(s_connhad_log(NULL)) + s_connhad_log(NULL) ); done = 1; } @@ -4189,17 +3912,17 @@ 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. */ 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. */ @@ -4217,21 +3940,21 @@ 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, + smtp_printf("%d %s argument does not match calling host\r\n", SP_NO_MORE, tempfail? 451 : 550, hello); log_write(0, LOG_MAIN|LOG_REJECT, "%srejected \"%s %s\" from %s", tempfail? "temporarily " : "", @@ -4253,6 +3976,7 @@ while (done <= 0) /* 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) @@ -4286,13 +4010,13 @@ while (done <= 0) #endif /* Expand the per-connection message count limit option */ - mailmax = expand_mailmax(smtp_accept_max_per_connection); + smtp_mailcmd_max = expand_mailmax(smtp_accept_max_per_connection); smtp_code = US"250 "; /* Default response code plus space*/ if (!user_msg) { /* sender_host_name below will be tainted, so save on copy when we hit it */ - g = string_get_tainted(24, TRUE); + g = string_get_tainted(24, GET_TAINTED); g = string_fmt_append(g, "%.3s %s Hello %s%s%s", smtp_code, smtp_active_hostname, @@ -4310,7 +4034,7 @@ while (done <= 0) else { - char *ss; + char * ss; int codelen = 4; smtp_message_code(&smtp_code, &codelen, &user_msg, NULL, TRUE); s = string_sprintf("%.*s%s", codelen, smtp_code, user_msg); @@ -4330,7 +4054,7 @@ while (done <= 0) if (fl.esmtp) { - g->s[3] = '-'; + g->s[3] = '-'; /* overwrite the space after the SMTP response code */ /* I'm not entirely happy with this, as an MTA is supposed to check that it has enough room to accept a message of maximum size before @@ -4347,15 +4071,15 @@ while (done <= 0) g = string_catn(g, US"-SIZE\r\n", 7); } -#ifdef EXPERIMENTAL_ESMTP_LIMITS - if ( (mailmax > 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 (mailmax > 0) - g = string_fmt_append(g, " MAILMAX=%d", mailmax); - if (recipients_max) - g = string_fmt_append(g, " RCPTMAX=%d", recipients_max); + if (smtp_mailcmd_max > 0) + g = string_fmt_append(g, " MAILMAX=%d", smtp_mailcmd_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 @@ -4384,16 +4108,19 @@ while (done <= 0) /* 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. */ + 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); @@ -4461,9 +4188,9 @@ while (done <= 0) first = FALSE; fl.auth_advertised = TRUE; } - saveptr = g->ptr; + saveptr = gstring_length(g); g = string_catn(g, US" ", 1); - g = string_cat (g, au->public_name); + g = string_cat(g, au->public_name); while (++saveptr < g->ptr) g->s[saveptr] = toupper(g->s[saveptr]); au->advertised = TRUE; } @@ -4497,7 +4224,13 @@ while (done <= 0) fl.tls_advertised = TRUE; } #endif - +#ifdef EXPERIMENTAL_XCLIENT + if (proxy_session || verify_check_host(&hosts_xclient) != FAIL) + { + g = string_catn(g, smtp_code, 3); + g = xclient_smtp_advertise_str(g); + } +#endif #ifndef DISABLE_PRDR /* Per Recipient Data Response, draft by Eric A. Hall extending RFC */ if (prdr_enable) @@ -4526,28 +4259,29 @@ while (done <= 0) /* Terminate the string (for debug), write it, and note that HELO/EHLO has been seen. */ + { + uschar * ehlo_resp; + int len = len_string_from_gstring(g, &ehlo_resp); #ifndef DISABLE_TLS - if (tls_in.active.sock >= 0) - (void)tls_write(NULL, g->s, g->ptr, + if (tls_in.active.sock >= 0) + (void) tls_write(NULL, ehlo_resp, len, # ifndef DISABLE_PIPE_CONNECT - fl.pipe_connect_acceptable && pipeline_connect_sends()); + fl.pipe_connect_acceptable && pipeline_connect_sends()); # else - FALSE); + FALSE); # endif - else + else #endif - (void) fwrite(g->s, 1, g->ptr, smtp_out); + (void) fwrite(ehlo_resp, 1, len, smtp_out); - DEBUG(D_receive) - { - uschar *cr; - - (void) string_from_gstring(g); - while ((cr = Ustrchr(g->s, '\r')) != NULL) /* lose CRs */ - memmove(cr, cr + 1, (g->ptr--) - (cr - g->s)); - debug_printf("SMTP>> %s", g->s); - } - fl.helo_seen = TRUE; + DEBUG(D_receive) for (const uschar * t, * s = ehlo_resp; + s && (t = Ustrchr(s, '\r')); + s = t + 2) /* \r\n */ + debug_printf("%s %.*s\n", + s == g->s ? "SMTP>>" : " ", + (int)(t - s), s); + fl.helo_seen = TRUE; + } /* Reset the protocol and the state, abandoning any previous message. */ received_protocol = @@ -4562,6 +4296,41 @@ while (done <= 0) toomany = FALSE; break; /* HELO/EHLO */ +#ifdef EXPERIMENTAL_XCLIENT + case XCLIENT_CMD: + { + BOOL fatal = fl.helo_seen; + uschar * errmsg; + int resp; + + HAD(SCH_XCLIENT); + smtp_mailcmd_count++; + + if ((errmsg = xclient_smtp_command(smtp_cmd_data, &resp, &fatal))) + if (fatal) + done = synprot_error(L_smtp_syntax_error, resp, NULL, errmsg); + else + { + smtp_printf("%d %s\r\n", SP_NO_MORE, resp, errmsg); + log_write(0, LOG_MAIN|LOG_REJECT, "rejected XCLIENT from %s: %s", + host_and_ident(FALSE), errmsg); + } + else + { + fl.helo_seen = FALSE; /* Require another EHLO */ + smtp_code = string_sprintf("%d", resp); + + /*XXX unclear in spec. if this needs to be an ESMTP banner, + nor whether we get the original client's HELO after (or a proxy fake). + We require that we do; the following HELO/EHLO handling will set + sender_helo_name as normal. */ + + smtp_printf("%s XCLIENT success\r\n", SP_NO_MORE, smtp_code); + } + break; /* XCLIENT */ + } +#endif + /* The MAIL command requires an address as an operand. All we do here is to parse it for syntactic correctness. The form "<>" is @@ -4577,15 +4346,16 @@ while (done <= 0) env_mail_type_t * mail_args; /* Sanity check & validate args */ if (!fl.helo_seen) - if (fl.helo_required) + if ( fl.helo_verify_required + || verify_check_host(&hosts_require_helo) == OK) { - smtp_printf("503 HELO or EHLO required\r\n", FALSE); + 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)); break; } - else if (mailmax < 0) - mailmax = expand_mailmax(smtp_accept_max_per_connection); + else if (smtp_mailcmd_max < 0) + smtp_mailcmd_max = expand_mailmax(smtp_accept_max_per_connection); if (sender_address) { @@ -4604,9 +4374,9 @@ while (done <= 0) /* Check to see if the limit for messages per connection would be exceeded by accepting further messages. */ - if (mailmax > 0 && smtp_mailcmd_count > mailmax) + if (smtp_mailcmd_max > 0 && smtp_mailcmd_count > smtp_mailcmd_max) { - smtp_printf("421 too many messages in this connection\r\n", FALSE); + smtp_printf("421 too many messages in this connection\r\n", SP_NO_MORE); log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL command %s: too many " "messages in one connection", host_and_ident(TRUE)); break; @@ -4746,6 +4516,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"; @@ -4856,7 +4627,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; @@ -4878,7 +4650,7 @@ while (done <= 0) if (thismessage_size_limit > 0 && message_size > thismessage_size_limit) { - smtp_printf("552 Message size exceeds maximum permitted\r\n", FALSE); + smtp_printf("552 Message size exceeds maximum permitted\r\n", SP_NO_MORE); log_write(L_size_reject, LOG_MAIN|LOG_REJECT, "rejected MAIL FROM:<%s> %s: " "message too big: size%s=%d max=%d", @@ -4903,7 +4675,7 @@ while (done <= 0) smtp_check_spool_space && message_size >= 0 ? message_size + 5000 : 0)) { - smtp_printf("452 Space shortage, please try later\r\n", FALSE); + smtp_printf("452 Space shortage, please try later\r\n", SP_NO_MORE); sender_address = NULL; break; } @@ -4918,13 +4690,14 @@ 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); } else { - smtp_printf("501 %s: sender address must contain a domain\r\n", FALSE, + smtp_printf("501 %s: sender address must contain a domain\r\n", SP_NO_MORE, smtp_cmd_data); log_write(L_smtp_syntax_error, LOG_MAIN|LOG_REJECT, @@ -4940,6 +4713,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); @@ -4988,6 +4762,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; @@ -4996,11 +4774,11 @@ while (done <= 0) count this as a protocol error. Reset was_rej_mail so that further RCPTs get the same treatment. */ - if (sender_address == NULL) + if (!sender_address) { if (f.smtp_in_pipelining_advertised && last_was_rej_mail) { - smtp_printf("503 sender not yet given\r\n", FALSE); + smtp_printf("503 sender not yet given\r\n", SP_NO_MORE); was_rej_mail = TRUE; } else @@ -5015,7 +4793,7 @@ while (done <= 0) /* Check for an operand */ - if (smtp_cmd_data[0] == 0) + if (!smtp_cmd_data[0]) { done = synprot_error(L_smtp_syntax_error, 501, NULL, US"RCPT must have an address operand"); @@ -5110,7 +4888,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; @@ -5143,12 +4922,13 @@ while (done <= 0) /* Check maximum allowed */ - if (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) { rcpt_fail_count++; - smtp_printf("552 too many recipients\r\n", FALSE); + smtp_printf("552 too many recipients\r\n", SP_NO_MORE); if (!toomany) log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: message " "rejected: sender=<%s> %s", sender_address, host_and_ident(TRUE)); @@ -5156,7 +4936,7 @@ while (done <= 0) else { rcpt_defer_count++; - smtp_printf("452 too many recipients\r\n", FALSE); + smtp_printf("452 too many recipients\r\n", SP_NO_MORE); if (!toomany) log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: excess " "temporarily rejected: sender=<%s> %s", sender_address, @@ -5189,10 +4969,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 */ @@ -5222,7 +5005,7 @@ while (done <= 0) if (user_msg) smtp_user_msg(US"250", user_msg); else - smtp_printf("250 Accepted\r\n", FALSE); + smtp_printf("250 Accepted\r\n", SP_NO_MORE); rcpt_fail_count++; discarded = TRUE; log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: " @@ -5288,16 +5071,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; @@ -5306,6 +5080,7 @@ 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 */ #ifndef DISABLE_PIPE_CONNECT @@ -5313,19 +5088,19 @@ while (done <= 0) #endif if (!discarded && recipients_count <= 0) { - if (fl.rcpt_smtp_response_same && rcpt_smtp_response != NULL) + if (fl.rcpt_smtp_response_same && rcpt_smtp_response) { uschar *code = US"503"; int len = Ustrlen(rcpt_smtp_response); - smtp_respond(code, 3, FALSE, US"All RCPT commands were rejected with " + smtp_respond(code, 3, SR_NOT_FINAL, US"All RCPT commands were rejected with " "this error:"); /* Responses from smtp_printf() will have \r\n on the end */ if (len > 2 && rcpt_smtp_response[len-2] == '\r') rcpt_smtp_response[len-2] = 0; - smtp_respond(code, 3, FALSE, rcpt_smtp_response); + smtp_respond(code, 3, SR_NOT_FINAL, rcpt_smtp_response); } if (f.smtp_in_pipelining_advertised && last_was_rcpt) - smtp_printf("503 Valid RCPT command must precede %s\r\n", FALSE, + smtp_printf("503 Valid RCPT command must precede %s\r\n", SP_NO_MORE, smtp_names[smtp_connection_had[SMTP_HBUFF_PREV(smtp_ch_index)]]); else done = synprot_error(L_smtp_protocol_error, 503, NULL, @@ -5334,7 +5109,10 @@ while (done <= 0) : US"valid RCPT command must precede BDAT"); if (chunking_state > CHUNKING_OFFERED) + { + bdat_push_receive_functions(); bdat_flush_data(); + } break; } @@ -5342,19 +5120,26 @@ 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); + smtp_printf("554 Too many recipients\r\n", SP_NO_MORE); + + if (chunking_state > CHUNKING_OFFERED) + { + bdat_push_receive_functions(); + bdat_flush_data(); + } break; } if (chunking_state > CHUNKING_OFFERED) - rc = OK; /* No predata ACL or go-ahead output for BDAT */ + rc = OK; /* There is no predata ACL or go-ahead output for BDAT */ else { - /* If there is an ACL, re-check the synchronization afterwards, since the - ACL may have delayed. To handle cutthrough delivery enforce a dummy call - to get the DATA command sent. */ + /* If there is a predata-ACL, re-check the synchronization afterwards, + since the ACL may have delayed. To handle cutthrough delivery enforce a + dummy call to get the DATA command sent. */ - if (acl_smtp_predata == NULL && cutthrough.cctx.sock < 0) + GET_OPTION("acl_smtp_predata"); + if (!acl_smtp_predata && cutthrough.cctx.sock < 0) rc = OK; else { @@ -5377,9 +5162,12 @@ while (done <= 0) smtp_user_msg(US"354", user_msg); else smtp_printf( - "354 Enter message, ending with \".\" on a line by itself\r\n", FALSE); + "354 Enter message, ending with \".\" on a line by itself\r\n", SP_NO_MORE); } + 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, @@ -5400,7 +5188,7 @@ while (done <= 0) if (!(address = parse_extract_address(smtp_cmd_data, &errmess, &start, &end, &recipient_domain, FALSE))) { - smtp_printf("501 %s\r\n", FALSE, errmess); + smtp_printf("501 %s\r\n", SP_NO_MORE, errmess); break; } @@ -5409,6 +5197,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); @@ -5439,7 +5228,7 @@ while (done <= 0) break; } - smtp_printf("%s\r\n", FALSE, s); + smtp_printf("%s\r\n", SP_NO_MORE, s); } break; } @@ -5447,6 +5236,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); @@ -5476,6 +5266,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 @@ -5494,7 +5285,7 @@ while (done <= 0) cancel_cutthrough_connection(TRUE, US"STARTTLS received"); reset_point = smtp_reset(reset_point); toomany = FALSE; - cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = FALSE; + cmd_list[CL_STLS].is_mail_cmd = FALSE; /* There's an attack where more data is read in past the STARTTLS command before TLS is negotiated, then assumed to be part of the secure session @@ -5504,7 +5295,7 @@ while (done <= 0) Pipelining sync checks will normally have protected us too, unless disabled by configuration. */ - if (receive_smtp_buffered()) + if (receive_hasc()) { DEBUG(D_any) debug_printf("Non-empty input buffer after STARTTLS; naive attack?\n"); @@ -5535,9 +5326,9 @@ while (done <= 0) { if (!tls_remember_esmtp) fl.helo_seen = fl.esmtp = fl.auth_advertised = f.smtp_in_pipelining_advertised = FALSE; - cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE; - cmd_list[CMD_LIST_AUTH].is_mail_cmd = TRUE; - cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE; + cmd_list[CL_EHLO].is_mail_cmd = TRUE; + cmd_list[CL_AUTH].is_mail_cmd = TRUE; + cmd_list[CL_TLAU].is_mail_cmd = TRUE; if (sender_helo_name) { sender_helo_name = NULL; @@ -5567,7 +5358,7 @@ while (done <= 0) if (rc == DEFER) { - smtp_printf("454 TLS currently unavailable\r\n", FALSE); + smtp_printf("454 TLS currently unavailable\r\n", SP_NO_MORE); break; } @@ -5581,8 +5372,7 @@ while (done <= 0) while (done <= 0) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED)) { case EOF_CMD: - log_write(L_smtp_connection, LOG_MAIN, "%s closed by EOF", - smtp_get_connection_info()); + log_close_event(US"by EOF"); smtp_notquit_exit(US"tls-failed", NULL, NULL); done = 2; break; @@ -5595,22 +5385,22 @@ 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)) log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s", log_msg); if (user_msg) - smtp_respond(US"221", 3, TRUE, user_msg); + smtp_respond(US"221", 3, SR_FINAL, user_msg); else - smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname); - log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT", - smtp_get_connection_info()); + smtp_printf("221 %s closing connection\r\n", SP_NO_MORE, smtp_active_hostname); + log_close_event(US"by QUIT"); done = 2; break; default: - smtp_printf("554 Security failure\r\n", FALSE); + smtp_printf("554 Security failure\r\n", SP_NO_MORE); break; } tls_close(NULL, TLS_SHUTDOWN_NOWAIT); @@ -5638,7 +5428,7 @@ while (done <= 0) case NOOP_CMD: HAD(SCH_NOOP); - smtp_printf("250 OK\r\n", FALSE); + smtp_printf("250 OK\r\n", SP_NO_MORE); break; @@ -5649,23 +5439,23 @@ while (done <= 0) case HELP_CMD: HAD(SCH_HELP); - smtp_printf("214-Commands supported:\r\n", TRUE); - { - uschar buffer[256]; - buffer[0] = 0; - Ustrcat(buffer, US" AUTH"); - #ifndef DISABLE_TLS - if (tls_in.active.sock < 0 && - verify_check_host(&tls_advertise_hosts) != FAIL) - Ustrcat(buffer, US" STARTTLS"); - #endif - Ustrcat(buffer, US" HELO EHLO MAIL RCPT DATA BDAT"); - Ustrcat(buffer, US" NOOP QUIT RSET HELP"); - if (acl_smtp_etrn) Ustrcat(buffer, US" ETRN"); - if (acl_smtp_expn) Ustrcat(buffer, US" EXPN"); - if (acl_smtp_vrfy) Ustrcat(buffer, US" VRFY"); - smtp_printf("214%s\r\n", FALSE, buffer); - } + smtp_printf("214-Commands supported:\r\n214", SP_MORE); + smtp_printf(" AUTH", SP_MORE); +#ifndef DISABLE_TLS + if (tls_in.active.sock < 0 && + verify_check_host(&tls_advertise_hosts) != FAIL) + smtp_printf(" STARTTLS", SP_MORE); +#endif + smtp_printf(" HELO EHLO MAIL RCPT DATA BDAT", SP_MORE); + smtp_printf(" NOOP QUIT RSET HELP", 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); +#ifdef EXPERIMENTAL_XCLIENT + if (proxy_session || verify_check_host(&hosts_xclient) != FAIL) + smtp_printf(" XCLIENT", SP_MORE); +#endif + smtp_printf("\r\n", SP_NO_MORE); break; @@ -5715,6 +5505,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) { @@ -5731,20 +5522,21 @@ 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; BOOL rc; etrn_command = smtp_etrn_command; deliver_domain = smtp_cmd_data; - rc = transport_set_up_command(&argv, smtp_etrn_command, TRUE, 0, NULL, + rc = transport_set_up_command(&argv, smtp_etrn_command, TSUC_EXPAND_ARGS, 0, NULL, US"ETRN processing", &error); deliver_domain = NULL; if (!rc) { log_write(0, LOG_MAIN|LOG_PANIC, "failed to set up ETRN command: %s", error); - smtp_printf("458 Internal failure\r\n", FALSE); + smtp_printf("458 Internal failure\r\n", SP_NO_MORE); break; } } @@ -5775,7 +5567,7 @@ while (done <= 0) debug_printf("ETRN command is: %s\n", etrn_command); debug_printf("ETRN command execution skipped\n"); } - if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE); + if (user_msg == NULL) smtp_printf("250 OK\r\n", SP_NO_MORE); else smtp_user_msg(US"250", user_msg); break; } @@ -5786,7 +5578,7 @@ while (done <= 0) if (smtp_etrn_serialize && !enq_start(etrn_serialize_key, 1)) { - smtp_printf("458 Already processing %s\r\n", FALSE, smtp_cmd_data); + smtp_printf("458 Already processing %s\r\n", SP_NO_MORE, smtp_cmd_data); break; } @@ -5815,6 +5607,7 @@ while (done <= 0) { DEBUG(D_exec) debug_print_argv(argv); exim_nullstd(); /* Ensure std{in,out,err} exist */ + /* argv[0] should be untainted, from child_exec_exim() */ execv(CS argv[0], (char *const *)argv); log_write(0, LOG_MAIN|LOG_PANIC_DIE, "exec of \"%s\" (ETRN) failed: %s", etrn_command, strerror(errno)); @@ -5850,12 +5643,12 @@ while (done <= 0) { log_write(0, LOG_MAIN|LOG_PANIC, "fork of process for ETRN failed: %s", strerror(errno)); - smtp_printf("458 Unable to fork process\r\n", FALSE); + smtp_printf("458 Unable to fork process\r\n", SP_NO_MORE); if (smtp_etrn_serialize) enq_end(etrn_serialize_key); } else if (!user_msg) - smtp_printf("250 OK\r\n", FALSE); + smtp_printf("250 OK\r\n", SP_NO_MORE); else smtp_user_msg(US"250", user_msg); @@ -5875,33 +5668,33 @@ while (done <= 0) done = synprot_error(L_smtp_syntax_error, 0, NULL, /* Just logs */ US"NUL character(s) present (shown as '?')"); smtp_printf("501 NUL characters are not allowed in SMTP commands\r\n", - FALSE); + SP_NO_MORE); break; case BADSYN_CMD: SYNC_FAILURE: - if (smtp_inend >= smtp_inbuffer + IN_BUFFER_SIZE) - smtp_inend = smtp_inbuffer + IN_BUFFER_SIZE - 1; - c = smtp_inend - smtp_inptr; - if (c > 150) c = 150; /* limit logged amount */ - smtp_inptr[c] = 0; - incomplete_transaction_log(US"sync failure"); - log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error " - "(next input sent too soon: pipelining was%s advertised): " - "rejected \"%s\" %s next input=\"%s\"", - f.smtp_in_pipelining_advertised ? "" : " not", - smtp_cmd_buffer, host_and_ident(TRUE), - string_printing(smtp_inptr)); - smtp_notquit_exit(US"synchronization-error", US"554", - US"SMTP synchronization error"); - done = 1; /* Pretend eof - drops connection */ - break; + { + unsigned nchars = 150; + uschar * buf = receive_getbuf(&nchars); /* destructive read */ + buf[nchars] = '\0'; + incomplete_transaction_log(US"sync failure"); + log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error " + "(next input sent too soon: pipelining was%s advertised): " + "rejected \"%s\" %s next input=\"%s\" (%u bytes)", + f.smtp_in_pipelining_advertised ? "" : " not", + smtp_cmd_buffer, host_and_ident(TRUE), + string_printing(buf), nchars); + smtp_notquit_exit(US"synchronization-error", US"554", + US"SMTP synchronization error"); + done = 1; /* Pretend eof - drops connection */ + break; + } case TOO_MANY_NONMAIL_CMD: s = smtp_cmd_buffer; - while (*s != 0 && !isspace(*s)) s++; + while (*s && !isspace(*s)) 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), @@ -5912,7 +5705,7 @@ while (done <= 0) #ifdef SUPPORT_PROXY case PROXY_FAIL_IGNORE_CMD: - smtp_printf("503 Command refused, required Proxy negotiation failed\r\n", FALSE); + smtp_printf("503 Command refused, required Proxy negotiation failed\r\n", SP_NO_MORE); break; #endif @@ -5943,7 +5736,6 @@ while (done <= 0) COMMAND_LOOP: last_was_rej_mail = was_rej_mail; /* Remember some last commands for */ last_was_rcpt = was_rcpt; /* protocol error handling */ - continue; } return done - 2; /* Convert yield values */