X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=src%2Fsrc%2Fsmtp_in.c;h=fcf165c192aa058902dec7499c182e51ee425d5e;hb=3c2238744f437aba7215008aed259e4c5af2c57e;hp=881bfff58793cebd610443338ba2b9da96f09551;hpb=a5bd321b2f16ff323e3e268d59606e89b747a901;p=exim.git diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 881bfff58..fcf165c19 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -1,10 +1,10 @@ -/* $Cambridge: exim/src/src/smtp_in.c,v 1.39 2006/07/13 13:53:33 ph10 Exp $ */ +/* $Cambridge: exim/src/src/smtp_in.c,v 1.58 2007/04/16 11:17:13 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2006 */ +/* Copyright (c) University of Cambridge 1995 - 2007 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions for handling an incoming SMTP call. */ @@ -96,6 +96,13 @@ enum { TOO_MANY_NONMAIL_CMD }; +/* This is a convenience macro for adding the identity of an SMTP command +to the circular buffer that holds a list of the last n received. */ + +#define HAD(n) \ + smtp_connection_had[smtp_ch_index++] = n; \ + if (smtp_ch_index >= SMTP_HBUFF_SIZE) smtp_ch_index = 0 + /************************************************* * Local static variables * @@ -113,12 +120,18 @@ static BOOL helo_seen; static BOOL helo_accept_junk; static BOOL count_nonmail; static BOOL pipelining_advertised; +static BOOL rcpt_smtp_response_same; +static BOOL rcpt_in_progress; static int nonmail_command_count; static int synprot_error_count; static int unknown_command_count; static int sync_cmd_limit; static int smtp_write_error = 0; +static uschar *rcpt_smtp_response; +static uschar *smtp_data_buffer; +static uschar *smtp_cmd_data; + /* We need to know the position of RSET, HELO, EHLO, AUTH, and STARTTLS. Their final fields of all except AUTH are forced TRUE at the start of a new message setup, to allow one of each between messages that is not counted as a nonmail @@ -165,6 +178,15 @@ static smtp_cmd_list *cmd_list_end = #define CMD_LIST_AUTH 3 #define CMD_LIST_STARTTLS 4 +/* This list of names is used for performing the smtp_no_mail logging action. +It must be kept in step with the SCH_xxx enumerations. */ + +static uschar *smtp_names[] = + { + US"NONE", US"AUTH", US"DATA", US"EHLO", US"ETRN", US"EXPN", US"HELO", + US"HELP", US"MAIL", US"NOOP", US"QUIT", US"RCPT", US"RSET", US"STARTTLS", + US"VRFY" }; + static uschar *protocols[] = { US"local-smtp", /* HELO */ US"local-smtps", /* The rare case EHLO->STARTTLS->HELO */ @@ -311,6 +333,23 @@ 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 * @@ -349,28 +388,41 @@ DEBUG(D_receive) } va_start(ap, format); +if (!string_vformat(big_buffer, big_buffer_size, format, ap)) + { + log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_printf()"); + smtp_closedown(US"Unexpected error"); + exim_exit(EXIT_FAILURE); + } +va_end(ap); -/* If in a TLS session we have to format the string, and then write it using a -TLS function. */ +/* If this is the first output for a (non-batch) RCPT command, see if all RCPTs +have had the same. Note: this code is also present in smtp_respond(). It would +be tidier to have it only in one place, but when it was added, it was easier to +do it that way, so as not to have to mess with the code for the RCPT command, +which sometimes uses smtp_printf() and sometimes smtp_respond(). */ + +if (rcpt_in_progress) + { + if (rcpt_smtp_response == NULL) + rcpt_smtp_response = string_copy(big_buffer); + else if (rcpt_smtp_response_same && + Ustrcmp(rcpt_smtp_response, big_buffer) != 0) + rcpt_smtp_response_same = FALSE; + rcpt_in_progress = FALSE; + } + +/* Now write the string */ #ifdef SUPPORT_TLS if (tls_active >= 0) { - if (!string_vformat(big_buffer, big_buffer_size, format, ap)) - { - log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_printf"); - smtp_closedown(US"Unexpected error"); - exim_exit(EXIT_FAILURE); - } if (tls_write(big_buffer, Ustrlen(big_buffer)) < 0) smtp_write_error = -1; } else #endif -/* Otherwise, just use the standard library function. */ - -if (vfprintf(smtp_out, format, ap) < 0) smtp_write_error = -1; -va_end(ap); +if (fprintf(smtp_out, "%s", big_buffer) < 0) smtp_write_error = -1; } @@ -450,6 +502,7 @@ exim_exit(EXIT_FAILURE); + /************************************************* * Read one command line * *************************************************/ @@ -523,7 +576,10 @@ if required. */ for (p = cmd_list; p < cmd_list_end; p++) { - if (strncmpic(smtp_cmd_buffer, US p->name, p->len) == 0) + if (strncmpic(smtp_cmd_buffer, US p->name, p->len) == 0 && + (smtp_cmd_buffer[p->len-1] == ':' || /* "mail from:" or "rcpt to:" */ + smtp_cmd_buffer[p->len] == 0 || + smtp_cmd_buffer[p->len] == ' ')) { if (smtp_inptr < smtp_inend && /* Outstanding input */ p->cmd < sync_cmd_limit && /* Command should sync */ @@ -533,11 +589,16 @@ for (p = cmd_list; p < cmd_list_end; p++) !sender_host_notsocket) /* Really is a socket */ return BADSYN_CMD; - /* Point after the command, but don't skip over leading spaces till after - the following test, so that if it fails, the command name can easily be - logged. */ + /* The variables $smtp_command and $smtp_command_argument point into the + unmodified input buffer. A copy of the latter is taken for actual + processing, so that it can be chopped up into separate parts if necessary, + for example, when processing a MAIL command options such as SIZE that can + follow the sender address. */ smtp_cmd_argument = smtp_cmd_buffer + p->len; + while (isspace(*smtp_cmd_argument)) smtp_cmd_argument++; + Ustrcpy(smtp_data_buffer, smtp_cmd_argument); + smtp_cmd_data = smtp_data_buffer; /* Count non-mail commands from those hosts that are controlled in this way. The default is all hosts. We don't waste effort checking the list @@ -555,11 +616,10 @@ for (p = cmd_list; p < cmd_list_end; p++) return TOO_MANY_NONMAIL_CMD; } - /* Get the data pointer over leading spaces and return; if there is data - for a command that does not expect it, give the error centrally here. */ + /* If there is data for a command that does not expect it, generate the + error here. */ - while (isspace(*smtp_cmd_argument)) smtp_cmd_argument++; - return (p->has_arg || *smtp_cmd_argument == 0)? p->cmd : BADARG_CMD; + return (p->has_arg || *smtp_cmd_data == 0)? p->cmd : BADARG_CMD; } } @@ -577,6 +637,60 @@ return OTHER_CMD; +/************************************************* +* Recheck synchronization * +*************************************************/ + +/* Synchronization checks can never be perfect because a packet may be on its +way but not arrived when the check is done. Such checks can in any case only be +done when TLS is not in use. 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 +check_sync(void) +{ +int fd, rc; +fd_set fds; +struct timeval tzero; + +if (!smtp_enforce_sync || sender_host_address == NULL || + sender_host_notsocket || tls_active >= 0) + return TRUE; + +fd = fileno(smtp_in); +FD_ZERO(&fds); +FD_SET(fd, &fds); +tzero.tv_sec = 0; +tzero.tv_usec = 0; +rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero); + +if (rc <= 0) return TRUE; /* Not ready to read */ +rc = smtp_getc(); +if (rc < 0) return TRUE; /* End of file or error */ + +smtp_ungetc(rc); +rc = smtp_inend - smtp_inptr; +if (rc > 150) rc = 150; +smtp_inptr[rc] = 0; +return FALSE; +} + + + /************************************************* * Forced closedown of call * *************************************************/ @@ -663,6 +777,74 @@ return string_sprintf("SMTP connection from %s", hostname); +/************************************************* +* Log lack of MAIL if so configured * +*************************************************/ + +/* This function is called when an SMTP session ends. If the log selector +smtp_no_mail is set, write a log line giving some details of what has happened +in the SMTP session. + +Arguments: none +Returns: nothing +*/ + +void +smtp_log_no_mail(void) +{ +int size, ptr, i; +uschar *s, *sep; + +if (smtp_mailcmd_count > 0 || (log_extra_selector & LX_smtp_no_mail) == 0) + return; + +s = NULL; +size = ptr = 0; + +if (sender_host_authenticated != NULL) + { + s = string_append(s, &size, &ptr, 2, US" A=", sender_host_authenticated); + if (authenticated_id != NULL) + s = string_append(s, &size, &ptr, 2, US":", authenticated_id); + } + +#ifdef SUPPORT_TLS +if ((log_extra_selector & LX_tls_cipher) != 0 && tls_cipher != NULL) + s = string_append(s, &size, &ptr, 2, US" X=", tls_cipher); +if ((log_extra_selector & LX_tls_certificate_verified) != 0 && + tls_cipher != NULL) + s = string_append(s, &size, &ptr, 2, US" CV=", + tls_certificate_verified? "yes":"no"); +if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_peerdn != NULL) + s = string_append(s, &size, &ptr, 3, US" DN=\"", tls_peerdn, US"\""); +#endif + +sep = (smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE)? + US" C=..." : US" C="; +for (i = smtp_ch_index; i < SMTP_HBUFF_SIZE; i++) + { + if (smtp_connection_had[i] != SCH_NONE) + { + s = string_append(s, &size, &ptr, 2, sep, + smtp_names[smtp_connection_had[i]]); + sep = US","; + } + } + +for (i = 0; i < smtp_ch_index; i++) + { + s = string_append(s, &size, &ptr, 2, sep, smtp_names[smtp_connection_had[i]]); + sep = US","; + } + +if (s != NULL) s[ptr] = 0; else s = US""; +log_write(0, LOG_MAIN, "no MAIL in SMTP connection from %s D=%s%s", + host_and_ident(FALSE), + readconf_printtime(time(NULL) - smtp_connection_start), s); +} + + + /************************************************* * Check HELO line and set sender_helo_name * *************************************************/ @@ -752,7 +934,7 @@ return yield; * Extract SMTP command option * *************************************************/ -/* This function picks the next option setting off the end of smtp_cmd_argument. It +/* This function picks the next option setting off the end of smtp_cmd_data. It is called for MAIL FROM and RCPT TO commands, to pick off the optional ESMTP things that can appear there. @@ -767,11 +949,11 @@ static BOOL extract_option(uschar **name, uschar **value) { uschar *n; -uschar *v = smtp_cmd_argument + Ustrlen(smtp_cmd_argument) -1; +uschar *v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1; while (isspace(*v)) v--; v[1] = 0; -while (v > smtp_cmd_argument && *v != '=' && !isspace(*v)) v--; +while (v > smtp_cmd_data && *v != '=' && !isspace(*v)) v--; if (*v != '=') return FALSE; n = v; @@ -790,8 +972,6 @@ return TRUE; - - /************************************************* * Reset for new message * *************************************************/ @@ -806,7 +986,6 @@ Returns: nothing static void smtp_reset(void *reset_point) { -int i; store_reset(reset_point); recipients_list = NULL; rcpt_count = rcpt_defer_count = rcpt_fail_count = @@ -815,6 +994,9 @@ message_linecount = 0; message_size = -1; acl_added_headers = NULL; queue_only_policy = FALSE; +rcpt_smtp_response = NULL; +rcpt_smtp_response_same = TRUE; +rcpt_in_progress = FALSE; deliver_freeze = FALSE; /* Can be set by ACL */ freeze_tell = freeze_tell_config; /* Can be set by ACL */ fake_response = OK; /* Can be set by ACL */ @@ -852,9 +1034,9 @@ sender_rate = sender_rate_limit = sender_rate_period = NULL; ratelimiters_mail = NULL; /* Updated by ratelimit ACL condition */ /* Note that ratelimiters_conn persists across resets. */ -/* The message variables follow the connection variables. */ +/* Reset message ACL variables */ -for (i = 0; i < ACL_MVARS; i++) acl_var[ACL_CVARS + i] = NULL; +acl_var_m = NULL; /* The message body variables use malloc store. They may be set if this is not the first message in an SMTP session and the previous message caused them @@ -874,7 +1056,7 @@ if (message_body_end != NULL) /* Warning log messages are also saved in malloc store. They are saved to avoid repetition in the same message, but it seems right to repeat them for different -messagess. */ +messages. */ while (acl_warn_logged != NULL) { @@ -938,7 +1120,7 @@ while (done <= 0) case HELO_CMD: case EHLO_CMD: - check_helo(smtp_cmd_argument); + check_helo(smtp_cmd_data); /* Fall through */ case RSET_CMD: @@ -958,7 +1140,7 @@ while (done <= 0) /* The function moan_smtp_batch() does not return. */ moan_smtp_batch(smtp_cmd_buffer, "503 Sender already given"); - if (smtp_cmd_argument[0] == 0) + if (smtp_cmd_data[0] == 0) /* The function moan_smtp_batch() does not return. */ moan_smtp_batch(smtp_cmd_buffer, "501 MAIL FROM must have an address operand"); @@ -969,8 +1151,8 @@ while (done <= 0) /* Apply SMTP rewrite */ raw_sender = ((rewrite_existflags & rewrite_smtp) != 0)? - rewrite_one(smtp_cmd_argument, rewrite_smtp|rewrite_smtp_sender, NULL, FALSE, - US"", global_rewrite_rules) : smtp_cmd_argument; + 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 */ @@ -1013,7 +1195,7 @@ while (done <= 0) /* The function moan_smtp_batch() does not return. */ moan_smtp_batch(smtp_cmd_buffer, "503 No sender yet given"); - if (smtp_cmd_argument[0] == 0) + if (smtp_cmd_data[0] == 0) /* The function moan_smtp_batch() does not return. */ moan_smtp_batch(smtp_cmd_buffer, "501 RCPT TO must have an address operand"); @@ -1028,8 +1210,8 @@ while (done <= 0) recipient address */ recipient = ((rewrite_existflags & rewrite_smtp) != 0)? - rewrite_one(smtp_cmd_argument, rewrite_smtp, NULL, FALSE, US"", - global_rewrite_rules) : smtp_cmd_argument; + rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"", + global_rewrite_rules) : smtp_cmd_data; /* rfc821_domains = TRUE; << no longer needed */ recipient = parse_extract_address(recipient, &errmess, &start, &end, @@ -1141,22 +1323,34 @@ BOOL smtp_start_session(void) { int size = 256; -int i, ptr; +int ptr, esclen; +uschar *user_msg, *log_msg; +uschar *code, *esc; uschar *p, *s, *ss; +smtp_connection_start = time(NULL); +for (smtp_ch_index = 0; smtp_ch_index < SMTP_HBUFF_SIZE; smtp_ch_index++) + smtp_connection_had[smtp_ch_index] = SCH_NONE; +smtp_ch_index = 0; + /* Default values for certain variables */ helo_seen = esmtp = helo_accept_junk = FALSE; +smtp_mailcmd_count = 0; count_nonmail = TRUE_UNSET; synprot_error_count = unknown_command_count = nonmail_command_count = 0; smtp_delay_mail = smtp_rlm_base; auth_advertised = FALSE; pipelining_advertised = FALSE; +pipelining_enable = TRUE; sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING; memset(sender_host_cache, 0, sizeof(sender_host_cache)); -sender_host_authenticated = NULL; +/* If receiving by -bs from a trusted user, or testing with -bh, we allow +authentication settings from -oMaa to remain in force. */ + +if (!host_checking && !sender_host_notsocket) sender_host_authenticated = NULL; authenticated_by = NULL; #ifdef SUPPORT_TLS @@ -1166,14 +1360,15 @@ tls_advertised = FALSE; /* Reset ACL connection variables */ -for (i = 0; i < ACL_CVARS; i++) acl_var[i] = NULL; +acl_var_c = NULL; -/* Allow for trailing 0 in the command buffer. */ +/* Allow for trailing 0 in the command and data buffers. */ -smtp_cmd_buffer = (uschar *)malloc(smtp_cmd_buffer_size + 1); +smtp_cmd_buffer = (uschar *)malloc(2*smtp_cmd_buffer_size + 2); if (smtp_cmd_buffer == NULL) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "malloc() failed for SMTP command buffer"); +smtp_data_buffer = smtp_cmd_buffer + smtp_cmd_buffer_size + 1; /* For batched input, the protocol setting can be overridden from the command line by a trusted caller. */ @@ -1200,13 +1395,14 @@ receive_getc = smtp_getc; 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; /* Set up the message size limit; this may be host-specific */ -thismessage_size_limit = expand_string_integer(message_size_limit); -if (thismessage_size_limit < 0) +thismessage_size_limit = expand_string_integer(message_size_limit, TRUE); +if (expand_string_message != NULL) { if (thismessage_size_limit == -1) log_write(0, LOG_MAIN|LOG_PANIC, "unable to expand message_size_limit: " @@ -1445,7 +1641,9 @@ if (!sender_host_unknown) smtps port for use with older style SSL MTAs. */ #ifdef SUPPORT_TLS - if (tls_on_connect && tls_server_start(tls_require_ciphers) != OK) + if (tls_on_connect && + tls_server_start(tls_require_ciphers, + gnutls_require_mac, gnutls_require_kx, gnutls_require_proto) != OK) return FALSE; #endif @@ -1497,18 +1695,18 @@ if (!sender_host_unknown) } #endif - /* Check for reserved slots. Note that the count value doesn't include - this process, as it gets upped in the parent process. */ + /* Check for reserved slots. The value of smtp_accept_count has already been + incremented to include this process. */ if (smtp_accept_max > 0 && - smtp_accept_count + 1 > smtp_accept_max - smtp_accept_reserve) + smtp_accept_count > smtp_accept_max - smtp_accept_reserve) { if ((rc = verify_check_host(&smtp_reserve_hosts)) != OK) { log_write(L_connection_reject, LOG_MAIN, "temporarily refused connection from %s: not in " "reserve list: connected=%d max=%d reserve=%d%s", - host_and_ident(FALSE), smtp_accept_count, smtp_accept_max, + 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", smtp_active_hostname); @@ -1568,10 +1766,10 @@ if (smtp_batched_input) return TRUE; /* Run the ACL if it exists */ +user_msg = NULL; if (acl_smtp_connect != NULL) { int rc; - uschar *user_msg, *log_msg; rc = acl_check(ACL_WHERE_CONNECT, NULL, acl_smtp_connect, &user_msg, &log_msg); if (rc != OK) @@ -1584,10 +1782,28 @@ if (acl_smtp_connect != NULL) /* Output the initial message for a two-way SMTP connection. It may contain newlines, which then cause a multi-line response to be given. */ -s = expand_string(smtp_banner); -if (s == NULL) - log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" (smtp_banner) " - "failed: %s", smtp_banner, expand_string_message); +code = US"220"; /* Default status code */ +esc = US""; /* Default extended status code */ +esclen = 0; /* Length of esc */ + +if (user_msg == NULL) + { + s = expand_string(smtp_banner); + if (s == NULL) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" (smtp_banner) " + "failed: %s", smtp_banner, expand_string_message); + } +else + { + int codelen = 3; + s = user_msg; + smtp_message_code(&code, &codelen, &s, NULL); + if (codelen > 4) + { + esc = code + 4; + esclen = codelen - 4; + } + } /* Remove any terminating newlines; might as well remove trailing space too */ @@ -1612,16 +1828,18 @@ do /* At least once, in case we have an empty string */ { int len; uschar *linebreak = Ustrchr(p, '\n'); + ss = string_cat(ss, &size, &ptr, code, 3); if (linebreak == NULL) { len = Ustrlen(p); - ss = string_cat(ss, &size, &ptr, US"220 ", 4); + ss = string_cat(ss, &size, &ptr, US" ", 1); } else { len = linebreak - p; - ss = string_cat(ss, &size, &ptr, US"220-", 4); + ss = string_cat(ss, &size, &ptr, US"-", 1); } + ss = string_cat(ss, &size, &ptr, esc, esclen); ss = string_cat(ss, &size, &ptr, p, len); ss = string_cat(ss, &size, &ptr, US"\r\n", 2); p += len; @@ -1634,30 +1852,14 @@ ss[ptr] = 0; /* string_cat leaves room for this */ /* Before we write the banner, check that there is no input pending, unless this synchronisation check is disabled. */ -if (smtp_enforce_sync && sender_host_address != NULL && !sender_host_notsocket) +if (!check_sync()) { - fd_set fds; - struct timeval tzero; - tzero.tv_sec = 0; - tzero.tv_usec = 0; - FD_ZERO(&fds); - FD_SET(fileno(smtp_in), &fds); - if (select(fileno(smtp_in) + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, - &tzero) > 0) - { - int rc = read(fileno(smtp_in), smtp_inbuffer, in_buffer_size); - if (rc > 0) - { - if (rc > 150) rc = 150; - smtp_inbuffer[rc] = 0; - log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol " - "synchronization error (input sent without waiting for greeting): " - "rejected connection from %s input=\"%s\"", host_and_ident(TRUE), - string_printing(smtp_inbuffer)); - smtp_printf("554 SMTP synchronization error\r\n"); - return FALSE; - } - } + log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol " + "synchronization error (input sent without waiting for greeting): " + "rejected connection from %s input=\"%s\"", host_and_ident(TRUE), + string_printing(smtp_inptr)); + smtp_printf("554 SMTP synchronization error\r\n"); + return FALSE; } /* Now output the banner */ @@ -1768,7 +1970,7 @@ output nothing for non-final calls, and only the first line for anything else. Arguments: code SMTP code, may involve extended status codes - codelen length of smtp code; uf > 3 there's an ESC + codelen length of smtp code; if > 4 there's an ESC final FALSE if the last line isn't the final line msg message text, possibly containing newlines @@ -1783,12 +1985,30 @@ uschar *esc = US""; if (!final && no_multiline_responses) return; -if (codelen > 3) +if (codelen > 4) { esc = code + 4; esclen = codelen - 4; } +/* If this is the first output for a (non-batch) RCPT command, see if all RCPTs +have had the same. Note: this code is also present in smtp_printf(). It would +be tidier to have it only in one place, but when it was added, it was easier to +do it that way, so as not to have to mess with the code for the RCPT command, +which sometimes uses smtp_printf() and sometimes smtp_respond(). */ + +if (rcpt_in_progress) + { + if (rcpt_smtp_response == NULL) + rcpt_smtp_response = string_copy(msg); + else if (rcpt_smtp_response_same && + Ustrcmp(rcpt_smtp_response, msg) != 0) + rcpt_smtp_response_same = FALSE; + rcpt_in_progress = FALSE; + } + +/* Not output the message, splitting it up into multiple lines if necessary. */ + for (;;) { uschar *nl = Ustrchr(msg, '\n'); @@ -1815,6 +2035,65 @@ for (;;) +/************************************************* +* Parse user SMTP message * +*************************************************/ + +/* This function allows for user messages overriding the response code details +by providing a suitable response code string at the start of the message +user_msg. Check the message for starting with a response code and optionally an +extended status code. If found, check that the first digit is valid, and if so, +change the code pointer and length to use the replacement. An invalid code +causes a panic log; in this case, if the log messages is the same as the user +message, we must also adjust the value of the log message to show the code that +is actually going to be used (the original one). + +This function is global because it is called from receive.c as well as within +this module. + +Note that the code length returned includes the terminating whitespace +character, which is always included in the regex match. + +Arguments: + code SMTP code, may involve extended status codes + codelen length of smtp code; if > 4 there's an ESC + msg message text + log_msg optional log message, to be adjusted with the new SMTP code + +Returns: nothing +*/ + +void +smtp_message_code(uschar **code, int *codelen, uschar **msg, uschar **log_msg) +{ +int n; +int ovector[3]; + +if (msg == NULL || *msg == NULL) return; + +n = pcre_exec(regex_smtp_code, NULL, CS *msg, Ustrlen(*msg), 0, + PCRE_EOPT, ovector, sizeof(ovector)/sizeof(int)); +if (n < 0) return; + +if ((*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]); + } +else + { + *code = *msg; + *codelen = ovector[1]; /* Includes final space */ + } +*msg += ovector[1]; /* Chop the code off the message */ +return; +} + + + + /************************************************* * Handle an ACL failure * *************************************************/ @@ -1855,7 +2134,6 @@ smtp_handle_acl_fail(int where, int rc, uschar *user_msg, uschar *log_msg) { BOOL drop = rc == FAIL_DROP; int codelen = 3; -int ovector[3]; uschar *smtp_code; uschar *lognl; uschar *sender_info = US""; @@ -1865,46 +2143,16 @@ uschar *what = #endif (where == ACL_WHERE_PREDATA)? US"DATA" : (where == ACL_WHERE_DATA)? US"after DATA" : - (smtp_cmd_argument == NULL)? + (smtp_cmd_data == NULL)? string_sprintf("%s in \"connect\" ACL", acl_wherenames[where]) : - string_sprintf("%s %s", acl_wherenames[where], smtp_cmd_argument); + string_sprintf("%s %s", acl_wherenames[where], smtp_cmd_data); if (drop) rc = FAIL; -/* Set the default SMTP code */ +/* Set the default SMTP code, and allow a user message to change it. */ smtp_code = (rc != FAIL)? US"451" : acl_wherecodes[where]; - -/* Check a user message for starting with a response code and optionally an -extended status code. If found, check that the first digit is valid, and if so, -use it instead of the default code. */ - -if (user_msg != NULL) - { - int n = pcre_exec(regex_smtp_code, NULL, CS user_msg, Ustrlen(user_msg), 0, - PCRE_EOPT, ovector, sizeof(ovector)/sizeof(int)); - if (n >= 0) - { - if (user_msg[0] != smtp_code[0]) - { - log_write(0, LOG_MAIN|LOG_PANIC, "configured error code starts with " - "incorrect digit (expected %c) in \"%s\"", smtp_code[0], user_msg); - - /* If log_msg == user_msg (the default set in acl.c if no log message is - specified, we must adjust the log message to show the code that is - actually going to be used. */ - - if (log_msg == user_msg) - log_msg = string_sprintf("%s %s", smtp_code, log_msg + ovector[1]); - } - else - { - smtp_code = user_msg; - codelen = ovector[1]; /* Includes final space */ - } - user_msg += ovector[1]; /* Chop the code off the message */ - } - } +smtp_message_code(&smtp_code, &codelen, &user_msg, &log_msg); /* We used to have sender_address here; however, there was a bug that was not updating sender_address after a rewrite during a verify. When this bug was @@ -1930,6 +2178,9 @@ unless the sender_verify_fail log selector has been turned off. */ if (sender_verified_failed != NULL && !testflag(sender_verified_failed, af_sverify_told)) { + BOOL save_rcpt_in_progress = rcpt_in_progress; + rcpt_in_progress = FALSE; /* So as not to treat these as the error */ + setflag(sender_verified_failed, af_sverify_told); if (rc != FAIL || (log_extra_selector & LX_sender_verify_fail) != 0) @@ -1959,6 +2210,8 @@ if (sender_verified_failed != NULL && "Verification failed for <%s>\n%s", sender_verified_failed->address, sender_verified_failed->user_message)); + + rcpt_in_progress = save_rcpt_in_progress; } /* Sort out text for logging */ @@ -1999,12 +2252,15 @@ else US"Temporary local problem - please try later"); } -/* Log the incident. If the connection is not forcibly to be dropped, return 0. -Otherwise, log why it is closing if required and return 2. */ +/* Log the incident to the logs that are specified by log_reject_target +(default main, reject). This can be empty to suppress logging of rejections. If +the connection is not forcibly to be dropped, return 0. Otherwise, log why it +is closing if required and return 2. */ -log_write(0, LOG_MAIN|LOG_REJECT, "%s %s%srejected %s%s", - host_and_ident(TRUE), - sender_info, (rc == FAIL)? US"" : US"temporarily ", what, log_msg); +if (log_reject_target != 0) + log_write(0, log_reject_target, "%s %s%srejected %s%s", + host_and_ident(TRUE), + sender_info, (rc == FAIL)? US"" : US"temporarily ", what, log_msg); if (!drop) return 0; @@ -2048,6 +2304,16 @@ if (sender_helo_name == NULL) HDEBUG(D_receive) debug_printf("no EHLO/HELO command was issued\n"); } +/* Deal with the case of -bs without an IP address */ + +else if (sender_host_address == NULL) + { + HDEBUG(D_receive) debug_printf("no client IP address: assume success\n"); + helo_verified = TRUE; + } + +/* Deal with the more common case when there is a sending IP address */ + else if (sender_helo_name[0] == '[') { helo_verified = Ustrncmp(sender_helo_name+1, sender_host_address, @@ -2113,7 +2379,7 @@ else h.next = NULL; HDEBUG(D_receive) debug_printf("getting IP address for %s\n", sender_helo_name); - rc = host_find_byname(&h, NULL, NULL, TRUE); + rc = host_find_byname(&h, NULL, 0, NULL, TRUE); if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL) { host_item *hh = &h; @@ -2133,13 +2399,40 @@ else } } -if (!helo_verified) helo_verify_failed = FALSE; /* We've tried ... */ +if (!helo_verified) helo_verify_failed = TRUE; /* We've tried ... */ return yield; } +/************************************************* +* Send user response message * +*************************************************/ + +/* This function is passed a default response code and a user message. It calls +smtp_message_code() to check and possibly modify the response code, and then +calls smtp_respond() to transmit the response. I put this into a function +just to avoid a lot of repetition. + +Arguments: + code the response code + user_msg the user message + +Returns: nothing +*/ + +static void +smtp_user_msg(uschar *code, uschar *user_msg) +{ +int len = 3; +smtp_message_code(&code, &len, &user_msg, NULL); +smtp_respond(code, len, TRUE, user_msg); +} + + + + /************************************************* * Initialize for SMTP incoming message * *************************************************/ @@ -2210,7 +2503,8 @@ while (done <= 0) uschar *etrn_command; uschar *etrn_serialize_key; uschar *errmess; - uschar *user_msg, *log_msg; + uschar *log_msg, *smtp_code; + uschar *user_msg = NULL; uschar *recipient = NULL; uschar *hello = NULL; uschar *set_id = NULL; @@ -2241,6 +2535,7 @@ while (done <= 0) AUTHS will eventually hit the nonmail threshold. */ case AUTH_CMD: + HAD(SCH_AUTH); authentication_failed = TRUE; cmd_list[CMD_LIST_AUTH].is_mail_cmd = FALSE; @@ -2277,8 +2572,8 @@ while (done <= 0) /* Find the name of the requested authentication mechanism. */ - s = smtp_cmd_argument; - while ((c = *smtp_cmd_argument) != 0 && !isspace(c)) + s = smtp_cmd_data; + while ((c = *smtp_cmd_data) != 0 && !isspace(c)) { if (!isalnum(c) && c != '-' && c != '_') { @@ -2286,16 +2581,16 @@ while (done <= 0) US"invalid character in authentication mechanism name"); goto COMMAND_LOOP; } - smtp_cmd_argument++; + smtp_cmd_data++; } /* If not at the end of the line, we must be at white space. Terminate the name and move the pointer on to any data that may be present. */ - if (*smtp_cmd_argument != 0) + if (*smtp_cmd_data != 0) { - *smtp_cmd_argument++ = 0; - while (isspace(*smtp_cmd_argument)) smtp_cmd_argument++; + *smtp_cmd_data++ = 0; + while (isspace(*smtp_cmd_data)) smtp_cmd_data++; } /* Search for an authentication mechanism which is configured for use @@ -2331,7 +2626,7 @@ while (done <= 0) expand_nmax = 0; expand_nlength[0] = 0; /* $0 contains nothing */ - c = (au->info->servercode)(au, smtp_cmd_argument); + c = (au->info->servercode)(au, smtp_cmd_data); if (au->set_id != NULL) set_id = expand_string(au->set_id); expand_nmax = -1; /* Reset numeric variables */ for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL; /* Reset $auth */ @@ -2433,11 +2728,13 @@ while (done <= 0) it did the reset first. */ case HELO_CMD: + HAD(SCH_HELO); hello = US"HELO"; esmtp = FALSE; goto HELO_EHLO; case EHLO_CMD: + HAD(SCH_EHLO); hello = US"EHLO"; esmtp = TRUE; @@ -2448,7 +2745,7 @@ while (done <= 0) /* 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_argument)) + if (!check_helo(smtp_cmd_data)) { smtp_printf("501 Syntactically invalid %s argument(s)\r\n", hello); @@ -2478,7 +2775,7 @@ while (done <= 0) if (!sender_host_unknown) { BOOL old_helo_verified = helo_verified; - uschar *p = smtp_cmd_argument; + uschar *p = smtp_cmd_data; while (*p != 0 && !isspace(*p)) { *p = tolower(*p); p++; } *p = 0; @@ -2533,7 +2830,8 @@ while (done <= 0) spf_init(sender_helo_name, sender_host_address); #endif - /* Apply an ACL check if one is defined */ + /* Apply an ACL check if one is defined; afterwards, recheck + synchronization in case the client started sending in a delay. */ if (acl_smtp_helo != NULL) { @@ -2545,28 +2843,14 @@ while (done <= 0) host_build_sender_fullhost(); /* Rebuild */ break; } + else if (!check_sync()) goto SYNC_FAILURE; } - /* The EHLO/HELO command is acceptable. Reset the protocol and the state, - abandoning any previous message. */ - - received_protocol = (esmtp? - protocols[pextend + - ((sender_host_authenticated != NULL)? pauthed : 0) + - ((tls_active >= 0)? pcrpted : 0)] - : - protocols[pnormal + ((tls_active >= 0)? pcrpted : 0)]) - + - ((sender_host_address != NULL)? pnlocal : 0); - - smtp_reset(reset_point); - toomany = FALSE; - - /* Generate an OK reply, including the ident if present, and also - the IP address if present. Reflecting back the ident is intended - as a deterrent to mail forgers. For maximum efficiency, and also - because some broken systems expect each response to be in a single - packet, arrange that it is sent in one write(). */ + /* Generate an OK reply. The default string includes the ident if present, + and also the IP address if present. Reflecting back the ident is intended + as a deterrent to mail forgers. For maximum efficiency, and also because + some broken systems expect each response to be in a single packet, arrange + that the entire reply is sent in one write(). */ auth_advertised = FALSE; pipelining_advertised = FALSE; @@ -2574,21 +2858,46 @@ while (done <= 0) tls_advertised = FALSE; #endif - s = string_sprintf("250 %s Hello %s%s%s", - smtp_active_hostname, - (sender_ident == NULL)? US"" : sender_ident, - (sender_ident == NULL)? US"" : US" at ", - (sender_host_name == NULL)? sender_helo_name : sender_host_name); + smtp_code = US"250 "; /* Default response code plus space*/ + if (user_msg == NULL) + { + s = string_sprintf("%.3s %s Hello %s%s%s", + smtp_code, + smtp_active_hostname, + (sender_ident == NULL)? US"" : sender_ident, + (sender_ident == NULL)? US"" : US" at ", + (sender_host_name == NULL)? sender_helo_name : sender_host_name); + + ptr = Ustrlen(s); + size = ptr + 1; + + if (sender_host_address != NULL) + { + s = string_cat(s, &size, &ptr, US" [", 2); + s = string_cat(s, &size, &ptr, sender_host_address, + Ustrlen(sender_host_address)); + s = string_cat(s, &size, &ptr, US"]", 1); + } + } - ptr = Ustrlen(s); - size = ptr + 1; + /* A user-supplied EHLO greeting may not contain more than one line. Note + that the code returned by smtp_message_code() includes the terminating + whitespace character. */ - if (sender_host_address != NULL) + else { - s = string_cat(s, &size, &ptr, US" [", 2); - s = string_cat(s, &size, &ptr, sender_host_address, - Ustrlen(sender_host_address)); - s = string_cat(s, &size, &ptr, US"]", 1); + char *ss; + int codelen = 4; + smtp_message_code(&smtp_code, &codelen, &user_msg, NULL); + s = string_sprintf("%.*s%s", codelen, smtp_code, user_msg); + if ((ss = strpbrk(CS s, "\r\n")) != NULL) + { + log_write(0, LOG_MAIN|LOG_PANIC, "EHLO/HELO response must not contain " + "newlines: message truncated: %s", string_printing(s)); + *ss = 0; + } + ptr = Ustrlen(s); + size = ptr + 1; } s = string_cat(s, &size, &ptr, US"\r\n", 2); @@ -2608,12 +2917,14 @@ while (done <= 0) if (thismessage_size_limit > 0) { - sprintf(CS big_buffer, "250-SIZE %d\r\n", thismessage_size_limit); + sprintf(CS big_buffer, "%.3s-SIZE %d\r\n", smtp_code, + thismessage_size_limit); s = string_cat(s, &size, &ptr, big_buffer, Ustrlen(big_buffer)); } else { - s = string_cat(s, &size, &ptr, US"250-SIZE\r\n", 10); + s = string_cat(s, &size, &ptr, smtp_code, 3); + s = string_cat(s, &size, &ptr, US"-SIZE\r\n", 7); } /* Exim does not do protocol conversion or data conversion. It is 8-bit @@ -2624,14 +2935,18 @@ while (done <= 0) provided as an option. */ if (accept_8bitmime) - s = string_cat(s, &size, &ptr, US"250-8BITMIME\r\n", 14); + { + s = string_cat(s, &size, &ptr, smtp_code, 3); + s = string_cat(s, &size, &ptr, US"-8BITMIME\r\n", 11); + } /* Advertise ETRN if there's an ACL checking whether a host is permitted to issue it; a check is made when any host actually tries. */ if (acl_smtp_etrn != NULL) { - s = string_cat(s, &size, &ptr, US"250-ETRN\r\n", 10); + s = string_cat(s, &size, &ptr, smtp_code, 3); + s = string_cat(s, &size, &ptr, US"-ETRN\r\n", 7); } /* Advertise EXPN if there's an ACL checking whether a host is @@ -2639,15 +2954,18 @@ while (done <= 0) if (acl_smtp_expn != NULL) { - s = string_cat(s, &size, &ptr, US"250-EXPN\r\n", 10); + s = string_cat(s, &size, &ptr, smtp_code, 3); + s = string_cat(s, &size, &ptr, US"-EXPN\r\n", 7); } /* Exim is quite happy with pipelining, so let the other end know that it is safe to use it, unless advertising is disabled. */ - if (verify_check_host(&pipelining_advertise_hosts) == OK) + if (pipelining_enable && + verify_check_host(&pipelining_advertise_hosts) == OK) { - s = string_cat(s, &size, &ptr, US"250-PIPELINING\r\n", 16); + s = string_cat(s, &size, &ptr, smtp_code, 3); + s = string_cat(s, &size, &ptr, US"-PIPELINING\r\n", 13); sync_cmd_limit = NON_SYNC_CMD_PIPELINING; pipelining_advertised = TRUE; } @@ -2677,7 +2995,8 @@ while (done <= 0) int saveptr; if (first) { - s = string_cat(s, &size, &ptr, US"250-AUTH", 8); + s = string_cat(s, &size, &ptr, smtp_code, 3); + s = string_cat(s, &size, &ptr, US"-AUTH", 5); first = FALSE; auth_advertised = TRUE; } @@ -2703,14 +3022,16 @@ while (done <= 0) if (tls_active < 0 && verify_check_host(&tls_advertise_hosts) != FAIL) { - s = string_cat(s, &size, &ptr, US"250-STARTTLS\r\n", 14); + s = string_cat(s, &size, &ptr, smtp_code, 3); + s = string_cat(s, &size, &ptr, US"-STARTTLS\r\n", 11); tls_advertised = TRUE; } #endif /* Finish off the multiline reply with one that is always available. */ - s = string_cat(s, &size, &ptr, US"250 HELP\r\n", 10); + s = string_cat(s, &size, &ptr, smtp_code, 3); + s = string_cat(s, &size, &ptr, US" HELP\r\n", 7); } /* Terminate the string (for debug), write it, and note that HELO/EHLO @@ -2731,6 +3052,20 @@ while (done <= 0) debug_printf("SMTP>> %s", s); } helo_seen = TRUE; + + /* Reset the protocol and the state, abandoning any previous message. */ + + received_protocol = (esmtp? + protocols[pextend + + ((sender_host_authenticated != NULL)? pauthed : 0) + + ((tls_active >= 0)? pcrpted : 0)] + : + protocols[pnormal + ((tls_active >= 0)? pcrpted : 0)]) + + + ((sender_host_address != NULL)? pnlocal : 0); + + smtp_reset(reset_point); + toomany = FALSE; break; /* HELO/EHLO */ @@ -2741,6 +3076,7 @@ while (done <= 0) it is the canonical extracted address which is all that is kept. */ case MAIL_CMD: + HAD(SCH_MAIL); smtp_mailcmd_count++; /* Count for limit and ratelimit */ was_rej_mail = TRUE; /* Reset if accepted */ @@ -2759,7 +3095,7 @@ while (done <= 0) break; } - if (smtp_cmd_argument[0] == 0) + if (smtp_cmd_data[0] == 0) { done = synprot_error(L_smtp_protocol_error, 501, NULL, US"MAIL must have an address operand"); @@ -2918,8 +3254,8 @@ while (done <= 0) TRUE flag allows "<>" as a sender address. */ raw_sender = ((rewrite_existflags & rewrite_smtp) != 0)? - rewrite_one(smtp_cmd_argument, rewrite_smtp, NULL, FALSE, US"", - global_rewrite_rules) : smtp_cmd_argument; + rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"", + global_rewrite_rules) : smtp_cmd_data; /* rfc821_domains = TRUE; << no longer needed */ raw_sender = @@ -2929,7 +3265,7 @@ while (done <= 0) if (raw_sender == NULL) { - done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_argument, errmess); + done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess); break; } @@ -2989,7 +3325,7 @@ while (done <= 0) else { smtp_printf("501 %s: sender address must contain a domain\r\n", - smtp_cmd_argument); + smtp_cmd_data); log_write(L_smtp_syntax_error, LOG_MAIN|LOG_REJECT, "unqualified sender rejected: <%s> %s%s", @@ -3001,19 +3337,25 @@ while (done <= 0) } } - /* Apply an ACL check if one is defined, before responding */ + /* Apply an ACL check if one is defined, before responding. Afterwards, + when pipelining is not advertised, do another sync check in case the ACL + delayed and the client started sending in the meantime. */ - rc = (acl_smtp_mail == NULL)? OK : - acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg); + if (acl_smtp_mail == NULL) rc = OK; else + { + rc = acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg); + if (rc == OK && !pipelining_advertised && !check_sync()) + goto SYNC_FAILURE; + } if (rc == OK || rc == DISCARD) { - smtp_printf("250 OK\r\n"); + if (user_msg == NULL) smtp_printf("250 OK\r\n"); + else smtp_user_msg(US"250", user_msg); smtp_delay_rcpt = smtp_rlr_base; recipients_discarded = (rc == DISCARD); was_rej_mail = FALSE; } - else { done = smtp_handle_acl_fail(ACL_WHERE_MAIL, rc, user_msg, log_msg); @@ -3022,16 +3364,15 @@ while (done <= 0) break; - /* The RCPT command requires an address as an operand. All we do - here is to parse it for syntactic correctness. There may be any number - of RCPT commands, specifying multiple senders. We build them all into - a data structure that is in argc/argv format. The start/end values - given by parse_extract_address are not used, as we keep only the - extracted address. */ + /* The RCPT command requires an address as an operand. There may be any + number of RCPT commands, specifying multiple recipients. We build them all + into a data structure. The start/end values given by parse_extract_address + are not used, as we keep only the extracted address. */ case RCPT_CMD: + HAD(SCH_RCPT); rcpt_count++; - was_rcpt = TRUE; + was_rcpt = rcpt_in_progress = TRUE; /* There must be a sender address; if the sender was rejected and pipelining was advertised, we assume the client was pipelining, and do not @@ -3057,7 +3398,7 @@ while (done <= 0) /* Check for an operand */ - if (smtp_cmd_argument[0] == 0) + if (smtp_cmd_data[0] == 0) { done = synprot_error(L_smtp_syntax_error, 501, NULL, US"RCPT must have an address operand"); @@ -3069,8 +3410,8 @@ while (done <= 0) as a recipient address */ recipient = ((rewrite_existflags & rewrite_smtp) != 0)? - rewrite_one(smtp_cmd_argument, rewrite_smtp, NULL, FALSE, US"", - global_rewrite_rules) : smtp_cmd_argument; + rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"", + global_rewrite_rules) : smtp_cmd_data; /* rfc821_domains = TRUE; << no longer needed */ recipient = parse_extract_address(recipient, &errmess, &start, &end, @@ -3079,7 +3420,7 @@ while (done <= 0) if (recipient == NULL) { - done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_argument, errmess); + done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess); rcpt_fail_count++; break; } @@ -3109,7 +3450,7 @@ while (done <= 0) { rcpt_fail_count++; smtp_printf("501 %s: recipient address must contain a domain\r\n", - smtp_cmd_argument); + smtp_cmd_data); log_write(L_smtp_syntax_error, LOG_MAIN|LOG_REJECT, "unqualified recipient rejected: " "<%s> %s%s", recipient, host_and_ident(TRUE), @@ -3159,16 +3500,24 @@ while (done <= 0) } /* If the MAIL ACL discarded all the recipients, we bypass ACL checking - for them. Otherwise, check the access control list for this recipient. */ + for them. Otherwise, check the access control list for this recipient. As + there may be a delay in this, re-check for a synchronization error + afterwards, unless pipelining was advertised. */ - rc = recipients_discarded? DISCARD : - acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg, &log_msg); + if (recipients_discarded) rc = DISCARD; else + { + rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg, + &log_msg); + if (rc == OK && !pipelining_advertised && !check_sync()) + goto SYNC_FAILURE; + } /* The ACL was happy */ if (rc == OK) { - smtp_printf("250 Accepted\r\n"); + if (user_msg == NULL) smtp_printf("250 Accepted\r\n"); + else smtp_user_msg(US"250", user_msg); receive_add_recipient(recipient, -1); } @@ -3176,7 +3525,8 @@ while (done <= 0) else if (rc == DISCARD) { - smtp_printf("250 Accepted\r\n"); + if (user_msg == NULL) smtp_printf("250 Accepted\r\n"); + else smtp_user_msg(US"250", user_msg); rcpt_fail_count++; discarded = TRUE; log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> rejected RCPT %s: " @@ -3212,13 +3562,29 @@ while (done <= 0) DATA command. The example in the pipelining RFC 2920 uses 554, but I use 503 here - because it is the same whether pipelining is in use or not. */ + because it is the same whether pipelining is in use or not. + + If all the RCPT commands that precede DATA provoked the same error message + (often indicating some kind of system error), it is helpful to include it + with the DATA rejection (an idea suggested by Tony Finch). */ case DATA_CMD: + HAD(SCH_DATA); if (!discarded && recipients_count <= 0) { + if (rcpt_smtp_response_same && rcpt_smtp_response != NULL) + { + uschar *code = US"503"; + int len = Ustrlen(rcpt_smtp_response); + smtp_respond(code, 3, FALSE, 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); + } if (pipelining_advertised && last_was_rcpt) - smtp_printf("503 valid RCPT command must precede DATA\r\n"); + smtp_printf("503 Valid RCPT command must precede DATA\r\n"); else done = synprot_error(L_smtp_protocol_error, 503, NULL, US"valid RCPT command must precede DATA"); @@ -3233,17 +3599,23 @@ while (done <= 0) break; } + /* If there is an ACL, re-check the synchronization afterwards, since the + ACL may have delayed. */ + if (acl_smtp_predata == NULL) rc = OK; else { enable_dollar_recipients = TRUE; rc = acl_check(ACL_WHERE_PREDATA, NULL, acl_smtp_predata, &user_msg, &log_msg); enable_dollar_recipients = FALSE; + if (rc == OK && !check_sync()) goto SYNC_FAILURE; } if (rc == OK) { - smtp_printf("354 Enter message, ending with \".\" on a line by itself\r\n"); + if (user_msg == NULL) + smtp_printf("354 Enter message, ending with \".\" on a line by itself\r\n"); + else smtp_user_msg(US"354", user_msg); done = 3; message_ended = END_NOTENDED; /* Indicate in middle of data */ } @@ -3252,11 +3624,11 @@ while (done <= 0) else done = smtp_handle_acl_fail(ACL_WHERE_PREDATA, rc, user_msg, log_msg); - break; case VRFY_CMD: + HAD(SCH_VRFY); rc = acl_check(ACL_WHERE_VRFY, NULL, acl_smtp_vrfy, &user_msg, &log_msg); if (rc != OK) done = smtp_handle_acl_fail(ACL_WHERE_VRFY, rc, user_msg, log_msg); @@ -3266,7 +3638,7 @@ while (done <= 0) uschar *s = NULL; /* rfc821_domains = TRUE; << no longer needed */ - address = parse_extract_address(smtp_cmd_argument, &errmess, &start, &end, + address = parse_extract_address(smtp_cmd_data, &errmess, &start, &end, &recipient_domain, FALSE); /* rfc821_domains = FALSE; << no longer needed */ @@ -3304,6 +3676,7 @@ while (done <= 0) case EXPN_CMD: + HAD(SCH_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); @@ -3311,7 +3684,7 @@ while (done <= 0) { BOOL save_log_testing_mode = log_testing_mode; address_test_mode = log_testing_mode = TRUE; - (void) verify_address(deliver_make_addr(smtp_cmd_argument, FALSE), + (void) verify_address(deliver_make_addr(smtp_cmd_data, FALSE), smtp_out, vopt_is_recipient | vopt_qualify | vopt_expn, -1, -1, -1, NULL, NULL, NULL); address_test_mode = FALSE; @@ -3323,6 +3696,7 @@ while (done <= 0) #ifdef SUPPORT_TLS case STARTTLS_CMD: + HAD(SCH_STARTTLS); if (!tls_advertised) { done = synprot_error(L_smtp_protocol_error, 503, NULL, @@ -3361,7 +3735,8 @@ while (done <= 0) We must allow for an extra EHLO command and an extra AUTH command after STARTTLS that don't add to the nonmail command count. */ - if ((rc = tls_server_start(tls_require_ciphers)) == OK) + if ((rc = tls_server_start(tls_require_ciphers, gnutls_require_mac, + gnutls_require_kx, gnutls_require_proto)) == OK) { if (!tls_remember_esmtp) helo_seen = esmtp = auth_advertised = pipelining_advertised = FALSE; @@ -3436,6 +3811,7 @@ while (done <= 0) message. */ case QUIT_CMD: + HAD(SCH_QUIT); incomplete_transaction_log(US"QUIT"); if (acl_smtp_quit != NULL) @@ -3445,12 +3821,11 @@ while (done <= 0) log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s", log_msg); } - else user_msg = NULL; if (user_msg == NULL) smtp_printf("221 %s closing connection\r\n", smtp_active_hostname); else - smtp_printf("221 %s\r\n", user_msg); + smtp_respond(US"221", 3, TRUE, user_msg); #ifdef SUPPORT_TLS tls_close(TRUE); @@ -3463,6 +3838,7 @@ while (done <= 0) case RSET_CMD: + HAD(SCH_RSET); incomplete_transaction_log(US"RSET"); smtp_reset(reset_point); toomany = FALSE; @@ -3472,22 +3848,27 @@ while (done <= 0) case NOOP_CMD: + HAD(SCH_NOOP); smtp_printf("250 OK\r\n"); break; - /* Show ETRN/EXPN/VRFY if there's - an ACL for checking hosts; if actually used, a check will be done for - permitted hosts. */ + /* Show ETRN/EXPN/VRFY if there's an ACL for checking hosts; if actually + used, a check will be done for permitted hosts. Show STARTTLS only if not + already in a TLS session and if it would be advertised in the EHLO + response. */ case HELP_CMD: + HAD(SCH_HELP); smtp_printf("214-Commands supported:\r\n"); { uschar buffer[256]; buffer[0] = 0; Ustrcat(buffer, " AUTH"); #ifdef SUPPORT_TLS - Ustrcat(buffer, " STARTTLS"); + if (tls_active < 0 && + verify_check_host(&tls_advertise_hosts) != FAIL) + Ustrcat(buffer, " STARTTLS"); #endif Ustrcat(buffer, " HELO EHLO MAIL RCPT DATA"); Ustrcat(buffer, " NOOP QUIT RSET HELP"); @@ -3522,6 +3903,7 @@ while (done <= 0) case ETRN_CMD: + HAD(SCH_ETRN); if (sender_address != NULL) { done = synprot_error(L_smtp_protocol_error, 503, NULL, @@ -3541,7 +3923,7 @@ while (done <= 0) /* Compute the serialization key for this command. */ - etrn_serialize_key = string_sprintf("etrn-%s\n", smtp_cmd_argument); + etrn_serialize_key = string_sprintf("etrn-%s\n", smtp_cmd_data); /* If a command has been specified for running as a result of ETRN, we permit any argument to ETRN. If not, only the # standard form is permitted, @@ -3553,7 +3935,7 @@ while (done <= 0) uschar *error; BOOL rc; etrn_command = smtp_etrn_command; - deliver_domain = smtp_cmd_argument; + deliver_domain = smtp_cmd_data; rc = transport_set_up_command(&argv, smtp_etrn_command, TRUE, 0, NULL, US"ETRN processing", &error); deliver_domain = NULL; @@ -3570,7 +3952,7 @@ while (done <= 0) else { - if (*smtp_cmd_argument++ != '#') + if (*smtp_cmd_data++ != '#') { done = synprot_error(L_smtp_syntax_error, 501, NULL, US"argument must begin with #"); @@ -3578,7 +3960,7 @@ while (done <= 0) } etrn_command = US"exim -R"; argv = child_exec_exim(CEE_RETURN_ARGV, TRUE, NULL, TRUE, 2, US"-R", - smtp_cmd_argument); + smtp_cmd_data); } /* If we are host-testing, don't actually do anything. */ @@ -3590,7 +3972,8 @@ while (done <= 0) debug_printf("ETRN command is: %s\n", etrn_command); debug_printf("ETRN command execution skipped\n"); } - smtp_printf("250 OK\r\n"); + if (user_msg == NULL) smtp_printf("250 OK\r\n"); + else smtp_user_msg(US"250", user_msg); break; } @@ -3600,7 +3983,7 @@ while (done <= 0) if (smtp_etrn_serialize && !enq_start(etrn_serialize_key)) { - smtp_printf("458 Already processing %s\r\n", smtp_cmd_argument); + smtp_printf("458 Already processing %s\r\n", smtp_cmd_data); break; } @@ -3666,7 +4049,11 @@ while (done <= 0) smtp_printf("458 Unable to fork process\r\n"); if (smtp_etrn_serialize) enq_end(etrn_serialize_key); } - else smtp_printf("250 OK\r\n"); + else + { + if (user_msg == NULL) smtp_printf("250 OK\r\n"); + else smtp_user_msg(US"250", user_msg); + } signal(SIGCHLD, oldsignal); break; @@ -3688,6 +4075,7 @@ while (done <= 0) 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; @@ -3706,10 +4094,12 @@ while (done <= 0) case TOO_MANY_NONMAIL_CMD: + s = smtp_cmd_buffer; + while (*s != 0 && !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), - smtp_cmd_argument - smtp_cmd_buffer, smtp_cmd_buffer); + s - smtp_cmd_buffer, smtp_cmd_buffer); smtp_printf("554 Too many nonmail commands\r\n"); done = 1; /* Pretend eof - drops connection */ break;