X-Git-Url: https://git.exim.org/users/jgh/exim.git/blobdiff_plain/58eb016e585187a87ade7602b2aecb2208605320..fd98a5c6771f3a5a686e54370b0525dcc3dca2f9:/src/src/smtp_in.c diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index dc96a9aa1..cb1a86991 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -1,10 +1,8 @@ -/* $Cambridge: exim/src/src/smtp_in.c,v 1.57 2007/04/13 15:13:47 ph10 Exp $ */ - /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2007 */ +/* Copyright (c) University of Cambridge 1995 - 2012 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions for handling an incoming SMTP call. */ @@ -31,15 +29,21 @@ including that header, and restore its value afterwards. */ int allow_severity = LOG_INFO; int deny_severity = LOG_NOTICE; +uschar *tcp_wrappers_name; #endif /* Size of buffer for reading SMTP commands. We used to use 512, as defined by RFC 821. However, RFC 1869 specifies that this must be increased for SMTP commands that accept arguments, and this in particular applies to AUTH, where -the data can be quite long. */ +the data can be quite long. More recently this value was 2048 in Exim; +however, RFC 4954 (circa 2007) recommends 12288 bytes to handle AUTH. Clients +such as Thunderbird will send an AUTH with an initial-response for GSSAPI. +The maximum size of a Kerberos ticket under Windows 2003 is 12000 bytes, and +we need room to handle large base64-encoded AUTHs for GSSAPI. +*/ -#define smtp_cmd_buffer_size 2048 +#define smtp_cmd_buffer_size 16384 /* Size of buffer for reading SMTP incoming packets */ @@ -48,7 +52,7 @@ the data can be quite long. */ /* Structure for SMTP command list */ typedef struct { - char *name; + const char *name; int len; short int cmd; short int has_arg; @@ -123,6 +127,7 @@ 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; @@ -202,6 +207,30 @@ static uschar *protocols[] = { #define pauthed 2 /* added to pextend */ #define pnlocal 6 /* offset to remove "local" */ +/* Sanity check and validate optional args to MAIL FROM: envelope */ +enum { + ENV_MAIL_OPT_SIZE, ENV_MAIL_OPT_BODY, ENV_MAIL_OPT_AUTH, +#ifdef EXPERIMENTAL_PRDR + ENV_MAIL_OPT_PRDR, +#endif + ENV_MAIL_OPT_NULL + }; +typedef struct { + uschar * name; /* option requested during MAIL cmd */ + int value; /* enum type */ + BOOL need_value; /* TRUE requires value (name=value pair format) + FALSE is a singleton */ + } env_mail_type_t; +static env_mail_type_t env_mail_type_list[] = { + { US"SIZE", ENV_MAIL_OPT_SIZE, TRUE }, + { US"BODY", ENV_MAIL_OPT_BODY, TRUE }, + { US"AUTH", ENV_MAIL_OPT_AUTH, TRUE }, +#ifdef EXPERIMENTAL_PRDR + { US"PRDR", ENV_MAIL_OPT_PRDR, FALSE }, +#endif + { US"NULL", ENV_MAIL_OPT_NULL, FALSE } + }; + /* When reading SMTP from a remote host, we have to use our own versions of the C input-reading functions, in order to be able to flush the SMTP output only when about to read more data from the socket. This is the only way to get @@ -263,6 +292,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; } @@ -371,30 +403,44 @@ Returns: nothing */ void -smtp_printf(char *format, ...) +smtp_printf(const 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. */ + +void +smtp_vprintf(const char *format, va_list ap) +{ +BOOL yield; + +yield = string_vformat(big_buffer, big_buffer_size, format, ap); + DEBUG(D_receive) { - uschar *cr, *end; - va_start(ap, format); - (void) string_vformat(big_buffer, big_buffer_size, format, ap); - va_end(ap); - 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); + void *reset_point = store_get(0); + uschar *msg_copy, *cr, *end; + msg_copy = string_copy(big_buffer); + end = msg_copy + Ustrlen(msg_copy); + while ((cr = Ustrchr(msg_copy, '\r')) != NULL) /* lose CRs */ + memmove(cr, cr + 1, (end--) - cr); + debug_printf("SMTP>> %s", msg_copy); + store_reset(reset_point); } -va_start(ap, format); -if (!string_vformat(big_buffer, big_buffer_size, format, ap)) +if (!yield) { 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 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 @@ -415,9 +461,10 @@ if (rcpt_in_progress) /* Now write the string */ #ifdef SUPPORT_TLS -if (tls_active >= 0) +if (tls_in.active >= 0) { - if (tls_write(big_buffer, Ustrlen(big_buffer)) < 0) smtp_write_error = -1; + if (tls_write(TRUE, big_buffer, Ustrlen(big_buffer)) < 0) + smtp_write_error = -1; } else #endif @@ -443,7 +490,7 @@ Returns: 0 for no error; -1 after an error int smtp_fflush(void) { -if (tls_active < 0 && fflush(smtp_out) != 0) smtp_write_error = -1; +if (tls_in.active < 0 && fflush(smtp_out) != 0) smtp_write_error = -1; return smtp_write_error; } @@ -466,13 +513,12 @@ command_timeout_handler(int sig) sig = sig; /* Keep picky compilers happy */ log_write(L_lost_incoming_connection, LOG_MAIN, "SMTP command timeout on%s connection from %s", - (tls_active >= 0)? " TLS" : "", + (tls_in.active >= 0)? " TLS" : "", 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); } @@ -495,8 +541,8 @@ 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); } @@ -668,7 +714,7 @@ fd_set fds; struct timeval tzero; if (!smtp_enforce_sync || sender_host_address == NULL || - sender_host_notsocket || tls_active >= 0) + sender_host_notsocket || tls_in.active >= 0) return TRUE; fd = fileno(smtp_in); @@ -702,7 +748,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 */ @@ -809,14 +857,18 @@ if (sender_host_authenticated != NULL) } #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_cipher) != 0 && tls_in.cipher != NULL) + s = string_append(s, &size, &ptr, 2, US" X=", tls_in.cipher); if ((log_extra_selector & LX_tls_certificate_verified) != 0 && - tls_cipher != NULL) + tls_in.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"\""); + tls_in.certificate_verified? "yes":"no"); +if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_in.peerdn != NULL) + s = string_append(s, &size, &ptr, 3, US" DN=\"", + string_printing(tls_in.peerdn), US"\""); +if ((log_extra_selector & LX_tls_sni) != 0 && tls_in.sni != NULL) + s = string_append(s, &size, &ptr, 3, US" SNI=\"", + string_printing(tls_in.sni), US"\""); #endif sep = (smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE)? @@ -952,18 +1004,23 @@ uschar *n; uschar *v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1; while (isspace(*v)) v--; v[1] = 0; - while (v > smtp_cmd_data && *v != '=' && !isspace(*v)) v--; -if (*v != '=') return FALSE; n = v; -while(isalpha(n[-1])) n--; - -if (n[-1] != ' ') return FALSE; - -n[-1] = 0; -*name = n; +if (*v == '=') +{ + while(isalpha(n[-1])) n--; + /* RFC says SP, but TAB seen in wild and other major MTAs accept it */ + if (!isspace(n[-1])) return FALSE; + n[-1] = 0; +} +else +{ + n++; + if (v == smtp_cmd_data) return FALSE; +} *v++ = 0; +*name = n; *value = v; return TRUE; } @@ -990,9 +1047,11 @@ store_reset(reset_point); recipients_list = NULL; rcpt_count = rcpt_defer_count = rcpt_fail_count = raw_recipients_count = recipients_count = recipients_list_max = 0; +cancel_cutthrough_connection("smtp reset"); message_linecount = 0; message_size = -1; acl_added_headers = NULL; +acl_removed_headers = NULL; queue_only_policy = FALSE; rcpt_smtp_response = NULL; rcpt_smtp_response_same = TRUE; @@ -1004,7 +1063,7 @@ fake_response = OK; /* Can be set by ACL */ no_mbox_unspool = FALSE; /* Can be set by ACL */ #endif submission_mode = FALSE; /* Can be set by ACL */ -suppress_local_fixups = FALSE; /* Can be set by ACL */ +suppress_local_fixups = suppress_local_fixups_default; /* Can be set by ACL */ active_local_from_check = local_from_check; /* Can be set by ACL */ active_local_sender_retain = local_sender_retain; /* Can be set by ACL */ sender_address = NULL; @@ -1019,8 +1078,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; @@ -1344,6 +1405,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)); @@ -1354,7 +1416,7 @@ if (!host_checking && !sender_host_notsocket) sender_host_authenticated = NULL; authenticated_by = NULL; #ifdef SUPPORT_TLS -tls_cipher = tls_peerdn = NULL; +tls_in.cipher = tls_in.peerdn = NULL; tls_advertised = FALSE; #endif @@ -1368,6 +1430,7 @@ 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_cmd_buffer[0] = 0; smtp_data_buffer = smtp_cmd_buffer + smtp_cmd_buffer_size + 1; /* For batched input, the protocol setting can be overridden from the @@ -1641,9 +1704,7 @@ 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, - gnutls_require_mac, gnutls_require_kx, gnutls_require_proto) != OK) + if (tls_in.on_connect && tls_server_start(tls_require_ciphers) != OK) return FALSE; #endif @@ -1668,7 +1729,14 @@ if (!sender_host_unknown) #ifdef USE_TCP_WRAPPERS errno = 0; - if (!hosts_ctl("exim", + tcp_wrappers_name = expand_string(tcp_wrappers_daemon_name); + if (tcp_wrappers_name == NULL) + { + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" " + "(tcp_wrappers_name) failed: %s", string_printing(tcp_wrappers_name), + expand_string_message); + } + if (!hosts_ctl(tcp_wrappers_name, (sender_host_name == NULL)? STRING_UNKNOWN : CS sender_host_name, (sender_host_address == NULL)? STRING_UNKNOWN : CS sender_host_address, (sender_ident == NULL)? STRING_UNKNOWN : CS sender_ident)) @@ -2143,6 +2211,9 @@ uschar *what = #endif (where == ACL_WHERE_PREDATA)? US"DATA" : (where == ACL_WHERE_DATA)? US"after DATA" : +#ifdef EXPERIMENTAL_PRDR + (where == ACL_WHERE_PRDR)? US"after DATA PRDR" : +#endif (smtp_cmd_data == NULL)? string_sprintf("%s in \"connect\" ACL", acl_wherenames[where]) : string_sprintf("%s %s", acl_wherenames[where], smtp_cmd_data); @@ -2166,8 +2237,13 @@ if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA) if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA || where == ACL_WHERE_MIME) #endif { - sender_info = string_sprintf("F=<%s> ", (sender_address_unrewritten != NULL)? - sender_address_unrewritten : sender_address); + sender_info = string_sprintf("F=<%s>%s%s%s%s ", + sender_address_unrewritten ? sender_address_unrewritten : sender_address, + sender_host_authenticated ? US" A=" : US"", + sender_host_authenticated ? sender_host_authenticated : US"", + sender_host_authenticated && authenticated_id ? US":" : US"", + sender_host_authenticated && authenticated_id ? authenticated_id : US"" + ); } /* If there's been a sender verification failure with a specific message, and @@ -2266,12 +2342,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 * *************************************************/ @@ -2660,7 +2822,7 @@ while (done <= 0) sender_host_authenticated = au->name; authentication_failed = FALSE; received_protocol = - protocols[pextend + pauthed + ((tls_active >= 0)? pcrpted:0)] + + protocols[pextend + pauthed + ((tls_in.active >= 0)? pcrpted:0)] + ((sender_host_address != NULL)? pnlocal : 0); s = ss = US"235 Authentication succeeded"; authenticated_by = au; @@ -2794,7 +2956,7 @@ while (done <= 0) host_build_sender_fullhost(); /* Rebuild */ set_process_info("handling%s incoming connection from %s", - (tls_active >= 0)? " TLS" : "", host_and_ident(FALSE)); + (tls_in.active >= 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, @@ -2970,6 +3132,7 @@ while (done <= 0) pipelining_advertised = TRUE; } + /* If any server authentication mechanisms are configured, advertise them if the current host is in auth_advertise_hosts. The problem with advertising always is that some clients then require users to @@ -3019,7 +3182,7 @@ while (done <= 0) secure connection. */ #ifdef SUPPORT_TLS - if (tls_active < 0 && + if (tls_in.active < 0 && verify_check_host(&tls_advertise_hosts) != FAIL) { s = string_cat(s, &size, &ptr, smtp_code, 3); @@ -3028,6 +3191,14 @@ while (done <= 0) } #endif + #ifdef EXPERIMENTAL_PRDR + /* Per Recipient Data Response, draft by Eric A. Hall extending RFC */ + if (prdr_enable) { + s = string_cat(s, &size, &ptr, smtp_code, 3); + s = string_cat(s, &size, &ptr, US"-PRDR\r\n", 7); + } + #endif + /* Finish off the multiline reply with one that is always available. */ s = string_cat(s, &size, &ptr, smtp_code, 3); @@ -3040,10 +3211,12 @@ while (done <= 0) s[ptr] = 0; #ifdef SUPPORT_TLS - if (tls_active >= 0) (void)tls_write(s, ptr); else + if (tls_in.active >= 0) (void)tls_write(TRUE, s, ptr); else #endif - (void)fwrite(s, 1, ptr, smtp_out); + { + int i = fwrite(s, 1, ptr, smtp_out); i = i; /* compiler quietening */ + } DEBUG(D_receive) { uschar *cr; @@ -3058,9 +3231,9 @@ while (done <= 0) received_protocol = (esmtp? protocols[pextend + ((sender_host_authenticated != NULL)? pauthed : 0) + - ((tls_active >= 0)? pcrpted : 0)] + ((tls_in.active >= 0)? pcrpted : 0)] : - protocols[pnormal + ((tls_active >= 0)? pcrpted : 0)]) + protocols[pnormal + ((tls_in.active >= 0)? pcrpted : 0)]) + ((sender_host_address != NULL)? pnlocal : 0); @@ -3079,6 +3252,7 @@ while (done <= 0) HAD(SCH_MAIL); smtp_mailcmd_count++; /* Count for limit and ratelimit */ was_rej_mail = TRUE; /* Reset if accepted */ + env_mail_type_t * mail_args; /* Sanity check & validate args */ if (helo_required && !helo_seen) { @@ -3127,113 +3301,150 @@ while (done <= 0) { uschar *name, *value, *end; unsigned long int size; + BOOL arg_error = FALSE; if (!extract_option(&name, &value)) break; - /* Handle SIZE= by reading the value. We don't do the check till later, - 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)) + for (mail_args = env_mail_type_list; + (char *)mail_args < (char *)env_mail_type_list + sizeof(env_mail_type_list); + mail_args++ + ) { - if ((size == ULONG_MAX && errno == ERANGE) || size > INT_MAX) - size = INT_MAX; - message_size = (int)size; + if (strcmpic(name, mail_args->name) == 0) + break; } + if (mail_args->need_value && strcmpic(value, US"") == 0) + break; - /* If this session was initiated with EHLO and accept_8bitmime is set, - Exim will have indicated that it supports the BODY=8BITMIME option. In - fact, it does not support this according to the RFCs, in that it does not - take any special action for forwarding messages containing 8-bit - characters. That is why accept_8bitmime is not the default setting, but - some sites want the action that is provided. We recognize both "8BITMIME" - and "7BIT" as body types, but take no action. */ - - else if (accept_8bitmime && strcmpic(name, US"BODY") == 0 && - (strcmpic(value, US"8BITMIME") == 0 || - strcmpic(value, US"7BIT") == 0)) {} - - /* Handle the AUTH extension. If the value given is not "<>" and either - the ACL says "yes" or there is no ACL but the sending host is - authenticated, we set it up as the authenticated sender. However, if the - authenticator set a condition to be tested, we ignore AUTH on MAIL unless - the condition is met. The value of AUTH is an xtext, which means that +, - = and cntrl chars are coded in hex; however "<>" is unaffected by this - coding. */ - - else if (strcmpic(name, US"AUTH") == 0) + switch(mail_args->value) { - if (Ustrcmp(value, "<>") != 0) - { - int rc; - uschar *ignore_msg; - - if (auth_xtextdecode(value, &authenticated_sender) < 0) - { - /* Put back terminator overrides for error message */ - name[-1] = ' '; - value[-1] = '='; - done = synprot_error(L_smtp_syntax_error, 501, NULL, - US"invalid data for AUTH"); - goto COMMAND_LOOP; - } - - if (acl_smtp_mailauth == NULL) + /* Handle SIZE= by reading the value. We don't do the check till later, + in order to be able to log the sender address on failure. */ + case ENV_MAIL_OPT_SIZE: + if (((size = Ustrtoul(value, &end, 10)), *end == 0)) { - ignore_msg = US"client not authenticated"; - rc = (sender_host_authenticated != NULL)? OK : FAIL; + if ((size == ULONG_MAX && errno == ERANGE) || size > INT_MAX) + size = INT_MAX; + message_size = (int)size; } else - { - ignore_msg = US"rejected by ACL"; - rc = acl_check(ACL_WHERE_MAILAUTH, NULL, acl_smtp_mailauth, - &user_msg, &log_msg); + arg_error = TRUE; + break; + + /* If this session was initiated with EHLO and accept_8bitmime is set, + Exim will have indicated that it supports the BODY=8BITMIME option. In + fact, it does not support this according to the RFCs, in that it does not + take any special action for forwarding messages containing 8-bit + characters. That is why accept_8bitmime is not the default setting, but + some sites want the action that is provided. We recognize both "8BITMIME" + and "7BIT" as body types, but take no action. */ + case ENV_MAIL_OPT_BODY: + if (accept_8bitmime) { + if (strcmpic(value, US"8BITMIME") == 0) { + body_8bitmime = 8; + } else if (strcmpic(value, US"7BIT") == 0) { + body_8bitmime = 7; + } else { + body_8bitmime = 0; + done = synprot_error(L_smtp_syntax_error, 501, NULL, + US"invalid data for BODY"); + goto COMMAND_LOOP; } + DEBUG(D_receive) debug_printf("8BITMIME: %d\n", body_8bitmime); + break; + } + arg_error = TRUE; + break; - switch (rc) + /* Handle the AUTH extension. If the value given is not "<>" and either + the ACL says "yes" or there is no ACL but the sending host is + authenticated, we set it up as the authenticated sender. However, if the + authenticator set a condition to be tested, we ignore AUTH on MAIL unless + the condition is met. The value of AUTH is an xtext, which means that +, + = and cntrl chars are coded in hex; however "<>" is unaffected by this + coding. */ + case ENV_MAIL_OPT_AUTH: + if (Ustrcmp(value, "<>") != 0) { - case OK: - if (authenticated_by == NULL || - authenticated_by->mail_auth_condition == NULL || - expand_check_condition(authenticated_by->mail_auth_condition, - authenticated_by->name, US"authenticator")) - break; /* Accept the AUTH */ - - ignore_msg = US"server_mail_auth_condition failed"; - if (authenticated_id != NULL) - ignore_msg = string_sprintf("%s: authenticated ID=\"%s\"", - ignore_msg, authenticated_id); - - /* Fall through */ - - case FAIL: - authenticated_sender = NULL; - log_write(0, LOG_MAIN, "ignoring AUTH=%s from %s (%s)", - value, host_and_ident(TRUE), ignore_msg); - break; + int rc; + uschar *ignore_msg; - /* Should only get DEFER or ERROR here. Put back terminator - overrides for error message */ - - default: - name[-1] = ' '; - value[-1] = '='; - (void)smtp_handle_acl_fail(ACL_WHERE_MAILAUTH, rc, user_msg, - log_msg); - goto COMMAND_LOOP; + if (auth_xtextdecode(value, &authenticated_sender) < 0) + { + /* Put back terminator overrides for error message */ + value[-1] = '='; + name[-1] = ' '; + done = synprot_error(L_smtp_syntax_error, 501, NULL, + US"invalid data for AUTH"); + goto COMMAND_LOOP; + } + if (acl_smtp_mailauth == NULL) + { + ignore_msg = US"client not authenticated"; + rc = (sender_host_authenticated != NULL)? OK : FAIL; + } + else + { + ignore_msg = US"rejected by ACL"; + rc = acl_check(ACL_WHERE_MAILAUTH, NULL, acl_smtp_mailauth, + &user_msg, &log_msg); + } + + switch (rc) + { + case OK: + if (authenticated_by == NULL || + authenticated_by->mail_auth_condition == NULL || + expand_check_condition(authenticated_by->mail_auth_condition, + authenticated_by->name, US"authenticator")) + break; /* Accept the AUTH */ + + ignore_msg = US"server_mail_auth_condition failed"; + if (authenticated_id != NULL) + ignore_msg = string_sprintf("%s: authenticated ID=\"%s\"", + ignore_msg, authenticated_id); + + /* Fall through */ + + case FAIL: + authenticated_sender = NULL; + log_write(0, LOG_MAIN, "ignoring AUTH=%s from %s (%s)", + value, host_and_ident(TRUE), ignore_msg); + break; + + /* Should only get DEFER or ERROR here. Put back terminator + overrides for error message */ + + default: + value[-1] = '='; + name[-1] = ' '; + (void)smtp_handle_acl_fail(ACL_WHERE_MAILAUTH, rc, user_msg, + log_msg); + goto COMMAND_LOOP; + } } - } - } + break; - /* Unknown option. Stick back the terminator characters and break - the loop. An error for a malformed address will occur. */ +#ifdef EXPERIMENTAL_PRDR + case ENV_MAIL_OPT_PRDR: + if ( prdr_enable ) + prdr_requested = TRUE; + break; +#endif - else - { - name[-1] = ' '; - value[-1] = '='; - break; + /* Unknown option. Stick back the terminator characters and break + the loop. Do the name-terminator second as extract_option sets + value==name when it found no equal-sign. + An error for a malformed address will occur. */ + default: + value[-1] = '='; + name[-1] = ' '; + arg_error = TRUE; + break; } + /* Break out of for loop if switch() had bad argument or + when start of the email address is reached */ + if (arg_error) break; } /* If we have passed the threshold for rate limiting, apply the current @@ -3350,8 +3561,21 @@ while (done <= 0) if (rc == OK || rc == DISCARD) { - if (user_msg == NULL) smtp_printf("250 OK\r\n"); - else smtp_user_msg(US"250", user_msg); + if (user_msg == NULL) + smtp_printf("%s%s%s", US"250 OK", + #ifdef EXPERIMENTAL_PRDR + prdr_requested == TRUE ? US", PRDR Requested" : + #endif + US"", + US"\r\n"); + else + { + #ifdef EXPERIMENTAL_PRDR + if ( prdr_requested == TRUE ) + user_msg = string_sprintf("%s%s", user_msg, US", PRDR Requested"); + #endif + smtp_user_msg(US"250",user_msg); + } smtp_delay_rcpt = smtp_rlr_base; recipients_discarded = (rc == DISCARD); was_rej_mail = FALSE; @@ -3600,12 +3824,14 @@ while (done <= 0) } /* If there is an ACL, re-check the synchronization afterwards, since the - ACL may have delayed. */ + ACL may have delayed. To handle cutthrough delivery enforce a dummy call + to get the DATA command sent. */ - if (acl_smtp_predata == NULL) rc = OK; else + if (acl_smtp_predata == NULL && cutthrough_fd < 0) rc = OK; else { + uschar * acl= acl_smtp_predata ? acl_smtp_predata : US"accept"; enable_dollar_recipients = TRUE; - rc = acl_check(ACL_WHERE_PREDATA, NULL, acl_smtp_predata, &user_msg, + rc = acl_check(ACL_WHERE_PREDATA, NULL, acl, &user_msg, &log_msg); enable_dollar_recipients = FALSE; if (rc == OK && !check_sync()) goto SYNC_FAILURE; @@ -3613,9 +3839,11 @@ while (done <= 0) if (rc == OK) { + uschar * code; + code = US"354"; 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); + smtp_printf("%s Enter message, ending with \".\" on a line by itself\r\n", code); + else smtp_user_msg(code, user_msg); done = 3; message_ended = END_NOTENDED; /* Indicate in middle of data */ } @@ -3727,6 +3955,32 @@ while (done <= 0) toomany = FALSE; cmd_list[CMD_LIST_STARTTLS].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 + when used afterwards; we use segregated input buffers, so are not + vulnerable, but we want to note when it happens and, for sheer paranoia, + ensure that the buffer is "wiped". + Pipelining sync checks will normally have protected us too, unless disabled + by configuration. */ + + if (receive_smtp_buffered()) + { + DEBUG(D_any) + debug_printf("Non-empty input buffer after STARTTLS; naive attack?"); + if (tls_in.active < 0) + smtp_inend = smtp_inptr = smtp_inbuffer; + /* and if TLS is already active, tls_server_start() should fail */ + } + + /* There is nothing we value in the input buffer and if TLS is succesfully + negotiated, we won't use this buffer again; if TLS fails, we'll just read + fresh content into it. The buffer contains arbitrary content from an + untrusted remote source; eg: NOOP \r\nSTARTTLS\r\n + It seems safest to just wipe away the content rather than leave it as a + target to jump to. */ + + memset(smtp_inbuffer, 0, in_buffer_size); + /* Attempt to start up a TLS session, and if successful, discard all knowledge that was obtained previously. At least, that's what the RFC says, and that's what happens by default. However, in order to work round YAEB, @@ -3735,8 +3989,7 @@ 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, gnutls_require_mac, - gnutls_require_kx, gnutls_require_proto)) == OK) + if ((rc = tls_server_start(tls_require_ciphers)) == OK) { if (!tls_remember_esmtp) helo_seen = esmtp = auth_advertised = pipelining_advertised = FALSE; @@ -3775,7 +4028,7 @@ while (done <= 0) } /* Hard failure. Reject everything except QUIT or closed connection. One - cause for failure is a nested STARTTLS, in which case tls_active remains + cause for failure is a nested STARTTLS, in which case tls_in.active remains set, but we must still reject all incoming commands. */ DEBUG(D_tls) debug_printf("TLS failed to start\n"); @@ -3786,11 +4039,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 situation 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; @@ -3801,7 +4072,7 @@ while (done <= 0) break; } } - tls_close(TRUE); + tls_close(TRUE, TRUE); break; #endif @@ -3813,22 +4084,20 @@ 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 smtp_respond(US"221", 3, TRUE, user_msg); #ifdef SUPPORT_TLS - tls_close(TRUE); + tls_close(TRUE, TRUE); #endif done = 2; @@ -3853,9 +4122,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); @@ -3865,7 +4135,9 @@ while (done <= 0) buffer[0] = 0; Ustrcat(buffer, " AUTH"); #ifdef SUPPORT_TLS - Ustrcat(buffer, " STARTTLS"); + if (tls_in.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"); @@ -3879,7 +4151,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. @@ -4085,7 +4358,8 @@ 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; @@ -4096,8 +4370,8 @@ while (done <= 0) 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), - s - smtp_cmd_buffer, smtp_cmd_buffer); - smtp_printf("554 Too many nonmail commands\r\n"); + (int)(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; @@ -4110,7 +4384,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),