X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/cf8b11a5399e64e50b6cd58f56a98197aca599d8..0a49a7a4f1090b6f1ce1d0f9d969804c9226b53e:/src/src/smtp_in.c diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index d0ac95967..8edfe8104 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.53 2007/01/30 11:45:20 ph10 Exp $ */ +/* $Cambridge: exim/src/src/smtp_in.c,v 1.66 2009/11/16 19:50:37 nm4 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2007 */ +/* Copyright (c) University of Cambridge 1995 - 2009 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions for handling an incoming SMTP call. */ @@ -120,12 +120,19 @@ 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 BOOL smtp_exit_function_called = 0; 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 @@ -257,6 +264,9 @@ if (smtp_inptr >= smtp_inend) else smtp_had_eof = 1; return EOF; } +#ifndef DISABLE_DKIM + dkim_exim_verify_feed(smtp_inbuffer, rc); +#endif smtp_inend = smtp_inbuffer + rc; smtp_inptr = smtp_inbuffer; } @@ -327,6 +337,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 * @@ -352,41 +379,69 @@ smtp_printf(char *format, ...) { va_list ap; +va_start(ap, format); +smtp_vprintf(format, ap); +va_end(ap); +} + +/* This is split off so that verify.c:respond_printf() can, in effect, call +smtp_printf(), bearing in mind that in C a vararg function can't directly +call another vararg function, only a function which accepts a va_list. + +Note also that repeated calls to va_start()/va_end() pairs is claimed to be +non-portable; meanwhile, va_copy() is also non-portable in that it's C99, so +we end up needing OS support to define it for us. */ + +void +smtp_vprintf(char *format, va_list ap) +{ +va_list ap_d; + DEBUG(D_receive) { uschar *cr, *end; - va_start(ap, format); - (void) string_vformat(big_buffer, big_buffer_size, format, ap); - va_end(ap); + va_copy(ap_d, ap); + (void) string_vformat(big_buffer, big_buffer_size, format, ap_d); end = big_buffer + Ustrlen(big_buffer); while ((cr = Ustrchr(big_buffer, '\r')) != NULL) /* lose CRs */ memmove(cr, cr + 1, (end--) - cr); debug_printf("SMTP>> %s", big_buffer); } -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); + } -/* 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; } @@ -434,9 +489,8 @@ log_write(L_lost_incoming_connection, host_and_ident(FALSE)); if (smtp_batched_input) moan_smtp_batch(NULL, "421 SMTP command timeout"); /* Does not return */ -smtp_printf("421 %s: SMTP command timeout - closing connection\r\n", - smtp_active_hostname); -mac_smtp_fflush(); +smtp_notquit_exit(US"command-timeout", US"421", + US"%s: SMTP command timeout - closing connection", smtp_active_hostname); exim_exit(EXIT_FAILURE); } @@ -459,13 +513,14 @@ sig = sig; /* Keep picky compilers happy */ log_write(0, LOG_MAIN, "%s closed after SIGTERM", smtp_get_connection_info()); if (smtp_batched_input) moan_smtp_batch(NULL, "421 SIGTERM received"); /* Does not return */ -smtp_printf("421 %s: Service not available - closing connection\r\n", - smtp_active_hostname); +smtp_notquit_exit(US"signal-exit", US"421", + US"%s: Service not available - closing connection", smtp_active_hostname); exim_exit(EXIT_FAILURE); } + /************************************************* * Read one command line * *************************************************/ @@ -552,11 +607,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 @@ -574,11 +634,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; } } @@ -596,6 +655,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 * *************************************************/ @@ -607,7 +720,9 @@ phase, sends the reply string, and gives an error to all subsequent commands except QUIT. The existence of an SMTP call is detected by the non-NULLness of smtp_in. -Argument: SMTP reply string to send, excluding the code +Arguments: + message SMTP reply string to send, excluding the code + Returns: nothing */ @@ -721,7 +836,8 @@ if ((log_extra_selector & LX_tls_certificate_verified) != 0 && 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"\""); + s = string_append(s, &size, &ptr, 3, US" DN=\"", + string_printing(tls_peerdn), US"\""); #endif sep = (smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE)? @@ -839,7 +955,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. @@ -854,11 +970,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; @@ -899,6 +1015,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 */ @@ -921,8 +1040,10 @@ authenticated_sender = NULL; bmi_run = 0; bmi_verdicts = NULL; #endif -#ifdef EXPERIMENTAL_DOMAINKEYS -dk_do_verify = 0; +#ifndef DISABLE_DKIM +dkim_signers = NULL; +dkim_disable_verify = FALSE; +dkim_collect_input = FALSE; #endif #ifdef EXPERIMENTAL_SPF spf_header_comment = NULL; @@ -1022,7 +1143,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: @@ -1042,7 +1163,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"); @@ -1053,8 +1174,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 */ @@ -1097,7 +1218,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"); @@ -1112,8 +1233,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, @@ -1246,6 +1367,7 @@ auth_advertised = FALSE; pipelining_advertised = FALSE; pipelining_enable = TRUE; sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING; +smtp_exit_function_called = FALSE; /* For avoiding loop in not-quit exit */ memset(sender_host_cache, 0, sizeof(sender_host_cache)); @@ -1264,12 +1386,13 @@ tls_advertised = FALSE; 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. */ @@ -1296,6 +1419,7 @@ 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; @@ -1752,30 +1876,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 */ @@ -1907,6 +2015,24 @@ if (codelen > 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'); @@ -2041,9 +2167,9 @@ 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; @@ -2076,6 +2202,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) @@ -2105,6 +2234,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 */ @@ -2159,12 +2290,98 @@ if (!drop) return 0; log_write(L_smtp_connection, LOG_MAIN, "%s closed by DROP in ACL", smtp_get_connection_info()); + +/* 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); return 2; } +/************************************************* +* Handle SMTP exit when QUIT is not given * +*************************************************/ + +/* This function provides a logging/statistics hook for when an SMTP connection +is dropped on the floor or the other end goes away. It's a global function +because it's called from receive.c as well as this module. As well as running +the NOTQUIT ACL, if there is one, this function also outputs a final SMTP +response, either with a custom message from the ACL, or using a default. There +is one case, however, when no message is output - after "drop". In that case, +the ACL that obeyed "drop" has already supplied the custom message, and NULL is +passed to this function. + +In case things go wrong while processing this function, causing an error that +may re-enter this funtion, there is a recursion check. + +Arguments: + reason What $smtp_notquit_reason will be set to in the ACL; + if NULL, the ACL is not run + code The error code to return as part of the response + defaultrespond The default message if there's no user_msg + +Returns: Nothing +*/ + +void +smtp_notquit_exit(uschar *reason, uschar *code, uschar *defaultrespond, ...) +{ +int rc; +uschar *user_msg = NULL; +uschar *log_msg = NULL; + +/* Check for recursive acll */ + +if (smtp_exit_function_called) + { + log_write(0, LOG_PANIC, "smtp_notquit_exit() called more than once (%s)", + reason); + return; + } +smtp_exit_function_called = TRUE; + +/* Call the not-QUIT ACL, if there is one, unless no reason is given. */ + +if (acl_smtp_notquit != NULL && reason != NULL) + { + smtp_notquit_reason = reason; + rc = acl_check(ACL_WHERE_NOTQUIT, NULL, acl_smtp_notquit, &user_msg, + &log_msg); + if (rc == ERROR) + log_write(0, LOG_MAIN|LOG_PANIC, "ACL for not-QUIT returned ERROR: %s", + log_msg); + } + +/* Write an SMTP response if we are expected to give one. As the default +responses are all internal, they should always fit in the buffer, but code a +warning, just in case. Note that string_vformat() still leaves a complete +string, even if it is incomplete. */ + +if (code != NULL && defaultrespond != NULL) + { + if (user_msg == NULL) + { + uschar buffer[128]; + va_list ap; + va_start(ap, defaultrespond); + if (!string_vformat(buffer, sizeof(buffer), CS defaultrespond, ap)) + log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_notquit_exit()"); + smtp_printf("%s %s\r\n", code, buffer); + va_end(ap); + } + else + smtp_respond(code, 3, TRUE, user_msg); + mac_smtp_fflush(); + } +} + + + + /************************************************* * Verify HELO argument * *************************************************/ @@ -2465,8 +2682,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 != '_') { @@ -2474,16 +2691,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 @@ -2519,7 +2736,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 */ @@ -2638,7 +2855,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); @@ -2668,7 +2885,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; @@ -2723,7 +2940,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) { @@ -2735,6 +2953,7 @@ while (done <= 0) host_build_sender_fullhost(); /* Rebuild */ break; } + else if (!check_sync()) goto SYNC_FAILURE; } /* Generate an OK reply. The default string includes the ident if present, @@ -2986,7 +3205,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"); @@ -3025,7 +3244,7 @@ while (done <= 0) in order to be able to log the sender address on failure. */ if (strcmpic(name, US"SIZE") == 0 && - ((size = (int)Ustrtoul(value, &end, 10)), *end == 0)) + ((size = Ustrtoul(value, &end, 10)), *end == 0)) { if ((size == ULONG_MAX && errno == ERANGE) || size > INT_MAX) size = INT_MAX; @@ -3145,8 +3364,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 = @@ -3156,7 +3375,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; } @@ -3216,7 +3435,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", @@ -3228,10 +3447,16 @@ 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) { @@ -3249,17 +3474,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 @@ -3285,7 +3508,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"); @@ -3297,8 +3520,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, @@ -3307,7 +3530,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; } @@ -3337,7 +3560,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), @@ -3387,10 +3610,17 @@ 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 */ @@ -3442,14 +3672,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"); @@ -3464,12 +3709,16 @@ 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) @@ -3485,7 +3734,6 @@ while (done <= 0) else done = smtp_handle_acl_fail(ACL_WHERE_PREDATA, rc, user_msg, log_msg); - break; @@ -3500,7 +3748,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 */ @@ -3546,7 +3794,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; @@ -3648,11 +3896,29 @@ while (done <= 0) case EOF_CMD: log_write(L_smtp_connection, LOG_MAIN, "%s closed by EOF", smtp_get_connection_info()); + smtp_notquit_exit(US"tls-failed", NULL, NULL); done = 2; break; + /* It is perhaps arguable as to which exit ACL should be called here, + but as it is probably a situtation that almost never arises, it + probably doesn't matter. We choose to call the real QUIT ACL, which in + some sense is perhaps "right". */ + case QUIT_CMD: - smtp_printf("221 %s closing connection\r\n", smtp_active_hostname); + user_msg = NULL; + if (acl_smtp_quit != NULL) + { + rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg, + &log_msg); + if (rc == ERROR) + log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s", + log_msg); + } + if (user_msg == NULL) + smtp_printf("221 %s closing connection\r\n", smtp_active_hostname); + else + smtp_respond(US"221", 3, TRUE, user_msg); log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT", smtp_get_connection_info()); done = 2; @@ -3675,15 +3941,13 @@ while (done <= 0) case QUIT_CMD: HAD(SCH_QUIT); incomplete_transaction_log(US"QUIT"); - if (acl_smtp_quit != NULL) { - rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit,&user_msg,&log_msg); + rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg, &log_msg); if (rc == ERROR) log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s", log_msg); } - if (user_msg == NULL) smtp_printf("221 %s closing connection\r\n", smtp_active_hostname); else @@ -3715,9 +3979,10 @@ while (done <= 0) 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); @@ -3727,7 +3992,9 @@ while (done <= 0) 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"); @@ -3741,7 +4008,8 @@ while (done <= 0) case EOF_CMD: incomplete_transaction_log(US"connection lost"); - smtp_printf("421 %s lost input connection\r\n", smtp_active_hostname); + smtp_notquit_exit(US"connection-lost", US"421", + US"%s lost input connection", smtp_active_hostname); /* Don't log by default unless in the middle of a message, as some mailers just drop the call rather than sending QUIT, and it clutters up the logs. @@ -3782,7 +4050,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, @@ -3794,7 +4062,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; @@ -3811,7 +4079,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 #"); @@ -3819,7 +4087,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. */ @@ -3842,7 +4110,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; } @@ -3934,6 +4202,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; @@ -3946,17 +4215,20 @@ while (done <= 0) pipelining_advertised? "" : " not", smtp_cmd_buffer, host_and_ident(TRUE), string_printing(smtp_inptr)); - smtp_printf("554 SMTP synchronization error\r\n"); + 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++; 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); - smtp_printf("554 Too many nonmail commands\r\n"); + s - smtp_cmd_buffer, smtp_cmd_buffer); + smtp_notquit_exit(US"bad-commands", US"554", US"Too many nonmail commands"); done = 1; /* Pretend eof - drops connection */ break; @@ -3969,7 +4241,8 @@ while (done <= 0) string_printing(smtp_cmd_buffer), host_and_ident(TRUE), US"unrecognized command"); incomplete_transaction_log(US"unrecognized command"); - smtp_printf("500 Too many unrecognized commands\r\n"); + smtp_notquit_exit(US"bad-commands", US"500", + US"Too many unrecognized commands"); done = 2; log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many " "unrecognized commands (last was \"%s\")", host_and_ident(FALSE),