X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/f78eb7c6264c5f1a4ec2fb24c39060e0686f7714..b4ed4da0f525ab98c05797e15df0045e49ae3618:/src/src/smtp_in.c diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 04bffd017..b1a1eba3d 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.30 2006/02/10 14:25:43 ph10 Exp $ */ +/* $Cambridge: exim/src/src/smtp_in.c,v 1.50 2007/01/15 15:59:22 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 * @@ -165,6 +172,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 */ @@ -338,8 +354,13 @@ va_list 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); } @@ -518,7 +539,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 */ @@ -626,6 +650,8 @@ for (;;) /* This function is called when logging information about an SMTP connection. It sets up appropriate source information, depending on the type of connection. +If sender_fullhost is NULL, we are at a very early stage of the connection; +just use the IP address. Argument: none Returns: a string describing the connection @@ -634,21 +660,92 @@ Returns: a string describing the connection uschar * smtp_get_connection_info(void) { +uschar *hostname = (sender_fullhost == NULL)? + sender_host_address : sender_fullhost; + if (host_checking) - return string_sprintf("SMTP connection from %s", sender_fullhost); + return string_sprintf("SMTP connection from %s", hostname); if (sender_host_unknown || sender_host_notsocket) return string_sprintf("SMTP connection from %s", sender_ident); if (is_inetd) - return string_sprintf("SMTP connection from %s (via inetd)", sender_fullhost); + return string_sprintf("SMTP connection from %s (via inetd)", hostname); if ((log_extra_selector & LX_incoming_interface) != 0 && interface_address != NULL) - return string_sprintf("SMTP connection from %s I=[%s]:%d", sender_fullhost, + return string_sprintf("SMTP connection from %s I=[%s]:%d", hostname, interface_address, interface_port); -return string_sprintf("SMTP connection from %s", sender_fullhost); +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); } @@ -780,8 +877,6 @@ return TRUE; - - /************************************************* * Reset for new message * *************************************************/ @@ -796,16 +891,16 @@ 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 = raw_recipients_count = recipients_count = recipients_list_max = 0; message_linecount = 0; message_size = -1; -acl_warn_headers = NULL; +acl_added_headers = NULL; queue_only_policy = 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 */ #ifdef WITH_CONTENT_SCAN no_mbox_unspool = FALSE; /* Can be set by ACL */ @@ -841,9 +936,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 @@ -863,7 +958,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) { @@ -1130,12 +1225,20 @@ 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; @@ -1145,7 +1248,10 @@ 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 @@ -1155,7 +1261,7 @@ 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. */ @@ -1194,8 +1300,8 @@ 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: " @@ -1448,19 +1554,40 @@ if (!sender_host_unknown) return FALSE; } - /* Test with TCP Wrappers if so configured */ + /* Test with TCP Wrappers if so configured. There is a problem in that + hosts_ctl() returns 0 (deny) under a number of system failure circumstances, + such as disks dying. In these cases, it is desirable to reject with a 4xx + error instead of a 5xx error. There isn't a "right" way to detect such + problems. The following kludge is used: errno is zeroed before calling + hosts_ctl(). If the result is "reject", a 5xx error is given only if the + value of errno is 0 or ENOENT (which happens if /etc/hosts.{allow,deny} does + not exist). */ #ifdef USE_TCP_WRAPPERS + errno = 0; if (!hosts_ctl("exim", (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)) { - HDEBUG(D_receive) debug_printf("tcp wrappers rejection\n"); - log_write(L_connection_reject, - LOG_MAIN|LOG_REJECT, "refused connection from %s " - "(tcp wrappers)", host_and_ident(FALSE)); - smtp_printf("554 SMTP service not available\r\n"); + if (errno == 0 || errno == ENOENT) + { + HDEBUG(D_receive) debug_printf("tcp wrappers rejection\n"); + log_write(L_connection_reject, + LOG_MAIN|LOG_REJECT, "refused connection from %s " + "(tcp wrappers)", host_and_ident(FALSE)); + smtp_printf("554 SMTP service not available\r\n"); + } + else + { + int save_errno = errno; + HDEBUG(D_receive) debug_printf("tcp wrappers rejected with unexpected " + "errno value %d\n", save_errno); + log_write(L_connection_reject, + LOG_MAIN|LOG_REJECT, "temporarily refused connection from %s " + "(tcp wrappers errno=%d)", host_and_ident(FALSE), save_errno); + smtp_printf("451 Temporary local problem - please try later\r\n"); + } return FALSE; } #endif @@ -1536,10 +1663,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) @@ -1552,10 +1679,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 */ @@ -1580,16 +1725,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; @@ -1735,7 +1882,8 @@ responses. If no_multiline_responses is TRUE (it can be set from an ACL), we output nothing for non-final calls, and only the first line for anything else. Arguments: - code SMTP code + code SMTP code, may involve extended status codes + 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 @@ -1743,26 +1891,36 @@ Returns: nothing */ void -smtp_respond(int code, BOOL final, uschar *msg) +smtp_respond(uschar* code, int codelen, BOOL final, uschar *msg) { +int esclen = 0; +uschar *esc = US""; + if (!final && no_multiline_responses) return; +if (codelen > 4) + { + esc = code + 4; + esclen = codelen - 4; + } + for (;;) { uschar *nl = Ustrchr(msg, '\n'); if (nl == NULL) { - smtp_printf("%d%c%s\r\n", code, final? ' ':'-', msg); + smtp_printf("%.3s%c%.*s%s\r\n", code, final? ' ':'-', esclen, esc, msg); return; } else if (nl[1] == 0 || no_multiline_responses) { - smtp_printf("%d%c%.*s\r\n", code, final? ' ':'-', (int)(nl - msg), msg); + smtp_printf("%.3s%c%.*s%.*s\r\n", code, final? ' ':'-', esclen, esc, + (int)(nl - msg), msg); return; } else { - smtp_printf("%d-%.*s\r\n", code, (int)(nl - msg), msg); + smtp_printf("%.3s-%.*s%.*s\r\n", code, esclen, esc, (int)(nl - msg), msg); msg = nl + 1; while (isspace(*msg)) msg++; } @@ -1772,6 +1930,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 * *************************************************/ @@ -1782,13 +1999,18 @@ logging the incident, and sets up the error response. A message containing newlines is turned into a multiline SMTP response, but for logging, only the first line is used. -There's a table of the response codes to use in globals.c, along with the table -of names. VFRY is special. Despite RFC1123 it defaults disabled in Exim. -However, discussion in connection with RFC 821bis (aka RFC 2821) has concluded -that the response should be 252 in the disabled state, because there are broken -clients that try VRFY before RCPT. A 5xx response should be given only when the -address is positively known to be undeliverable. Sigh. Also, for ETRN, 458 is -given on refusal, and for AUTH, 503. +There's a table of default permanent failure response codes to use in +globals.c, along with the table of names. VFRY is special. Despite RFC1123 it +defaults disabled in Exim. However, discussion in connection with RFC 821bis +(aka RFC 2821) has concluded that the response should be 252 in the disabled +state, because there are broken clients that try VRFY before RCPT. A 5xx +response should be given only when the address is positively known to be +undeliverable. Sigh. Also, for ETRN, 458 is given on refusal, and for AUTH, +503. + +From Exim 4.63, it is possible to override the response code details by +providing a suitable response code string at the start of the message provided +in user_msg. The code's first digit is checked for validity. Arguments: where where the ACL was called from @@ -1805,8 +2027,9 @@ Returns: 0 in most cases int smtp_handle_acl_fail(int where, int rc, uschar *user_msg, uschar *log_msg) { -int code = acl_wherecodes[where]; BOOL drop = rc == FAIL_DROP; +int codelen = 3; +uschar *smtp_code; uschar *lognl; uschar *sender_info = US""; uschar *what = @@ -1821,6 +2044,11 @@ uschar *what = if (drop) rc = FAIL; +/* Set the default SMTP code, and allow a user message to change it. */ + +smtp_code = (rc != FAIL)? US"451" : acl_wherecodes[where]; +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 fixed, sender_address at this point became the rewritten address. I'm not sure @@ -1839,22 +2067,24 @@ if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA || where == ACL_WHERE_MIM /* If there's been a sender verification failure with a specific message, and we have not sent a response about it yet, do so now, as a preliminary line for -failures, but not defers. However, log it in both cases. */ +failures, but not defers. However, always log it for defer, and log it for fail +unless the sender_verify_fail log selector has been turned off. */ if (sender_verified_failed != NULL && !testflag(sender_verified_failed, af_sverify_told)) { setflag(sender_verified_failed, af_sverify_told); - log_write(0, LOG_MAIN|LOG_REJECT, "%s sender verify %s for <%s>%s", - host_and_ident(TRUE), - ((sender_verified_failed->special_action & 255) == DEFER)? "defer" : "fail", - sender_verified_failed->address, - (sender_verified_failed->message == NULL)? US"" : - string_sprintf(": %s", sender_verified_failed->message)); + if (rc != FAIL || (log_extra_selector & LX_sender_verify_fail) != 0) + log_write(0, LOG_MAIN|LOG_REJECT, "%s sender verify %s for <%s>%s", + host_and_ident(TRUE), + ((sender_verified_failed->special_action & 255) == DEFER)? "defer":"fail", + sender_verified_failed->address, + (sender_verified_failed->message == NULL)? US"" : + string_sprintf(": %s", sender_verified_failed->message)); if (rc == FAIL && sender_verified_failed->user_message != NULL) - smtp_respond(code, FALSE, string_sprintf( + smtp_respond(smtp_code, codelen, FALSE, string_sprintf( testflag(sender_verified_failed, af_verify_pmfail)? "Postmaster verification failed while checking <%s>\n%s\n" "Several RFCs state that you are required to have a postmaster\n" @@ -1884,7 +2114,7 @@ if (lognl != NULL) *lognl = 0; always a 5xx one - see comments at the start of this function. If the original rc was FAIL_DROP we drop the connection and yield 2. */ -if (rc == FAIL) smtp_respond(code, TRUE, (user_msg == NULL)? +if (rc == FAIL) smtp_respond(smtp_code, codelen, TRUE, (user_msg == NULL)? US"Administrative prohibition" : user_msg); /* Send temporary failure response to the command. Don't give any details, @@ -1903,20 +2133,24 @@ else sender_verified_failed != NULL && sender_verified_failed->message != NULL) { - smtp_respond(451, FALSE, sender_verified_failed->message); + smtp_respond(smtp_code, codelen, FALSE, sender_verified_failed->message); } - smtp_respond(451, TRUE, user_msg); + smtp_respond(smtp_code, codelen, TRUE, user_msg); } else - smtp_printf("451 Temporary local problem - please try later\r\n"); + smtp_respond(smtp_code, codelen, TRUE, + 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; @@ -1960,6 +2194,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, @@ -2025,7 +2269,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; @@ -2045,13 +2289,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 * *************************************************/ @@ -2122,7 +2393,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; @@ -2139,20 +2411,25 @@ while (done <= 0) switch(smtp_read_command(TRUE)) { /* The AUTH command is not permitted to occur inside a transaction, and may - occur successfully only once per connection, and then only when we've - advertised it. Actually, that isn't quite true. When TLS is started, all - previous information about a connection must be discarded, so a new AUTH is - permitted at that time. + occur successfully only once per connection. Actually, that isn't quite + true. When TLS is started, all previous information about a connection must + be discarded, so a new AUTH is permitted at that time. + + AUTH may only be used when it has been advertised. However, it seems that + there are clients that send AUTH when it hasn't been advertised, some of + them even doing this after HELO. And there are MTAs that accept this. Sigh. + So there's a get-out that allows this to happen. AUTH is initially labelled as a "nonmail command" so that one occurrence doesn't get counted. We change the label here so that multiple failing 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; - if (!auth_advertised) + if (!auth_advertised && !allow_auth_unadvertised) { done = synprot_error(L_smtp_protocol_error, 503, NULL, US"AUTH command used when not advertised"); @@ -2207,12 +2484,13 @@ while (done <= 0) } /* Search for an authentication mechanism which is configured for use - as a server and which has been advertised. */ + as a server and which has been advertised (unless, sigh, allow_auth_ + unadvertised is set). */ for (au = auths; au != NULL; au = au->next) { if (strcmpic(s, au->public_name) == 0 && au->server && - au->advertised) break; + (au->advertised || allow_auth_unadvertised)) break; } if (au == NULL) @@ -2340,11 +2618,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; @@ -2454,26 +2734,11 @@ while (done <= 0) } } - /* 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; @@ -2481,21 +2746,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; - 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); + } + } - if (sender_host_address != NULL) + /* 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. */ + + 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); @@ -2515,12 +2805,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 @@ -2531,14 +2823,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 @@ -2546,7 +2842,8 @@ 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 @@ -2554,7 +2851,8 @@ while (done <= 0) if (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; } @@ -2584,7 +2882,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; } @@ -2610,14 +2909,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 @@ -2630,8 +2931,28 @@ while (done <= 0) #endif (void)fwrite(s, 1, ptr, smtp_out); - DEBUG(D_receive) debug_printf("SMTP>> %s", s); + DEBUG(D_receive) + { + uschar *cr; + while ((cr = Ustrchr(s, '\r')) != NULL) /* lose CRs */ + memmove(cr, cr + 1, (ptr--) - (cr - s)); + 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 */ @@ -2642,6 +2963,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 */ @@ -2909,12 +3231,12 @@ while (done <= 0) 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); @@ -2931,6 +3253,7 @@ while (done <= 0) extracted address. */ case RCPT_CMD: + HAD(SCH_RCPT); rcpt_count++; was_rcpt = TRUE; @@ -3069,7 +3392,8 @@ while (done <= 0) 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); } @@ -3077,7 +3401,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: " @@ -3116,6 +3441,7 @@ while (done <= 0) because it is the same whether pipelining is in use or not. */ case DATA_CMD: + HAD(SCH_DATA); if (!discarded && recipients_count <= 0) { if (pipelining_advertised && last_was_rcpt) @@ -3144,7 +3470,9 @@ while (done <= 0) 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 */ } @@ -3158,6 +3486,7 @@ while (done <= 0) 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); @@ -3184,14 +3513,14 @@ while (done <= 0) break; case DEFER: - s = (addr->message != NULL)? - string_sprintf("451 <%s> %s", address, addr->message) : + s = (addr->user_message != NULL)? + string_sprintf("451 <%s> %s", address, addr->user_message) : string_sprintf("451 Cannot resolve <%s> at this time", address); break; case FAIL: - s = (addr->message != NULL)? - string_sprintf("550 <%s> %s", address, addr->message) : + s = (addr->user_message != NULL)? + string_sprintf("550 <%s> %s", address, addr->user_message) : string_sprintf("550 <%s> is not deliverable", address); log_write(0, LOG_MAIN, "VRFY failed for %s %s", smtp_cmd_argument, host_and_ident(TRUE)); @@ -3205,6 +3534,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); @@ -3224,6 +3554,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, @@ -3337,6 +3668,7 @@ while (done <= 0) message. */ case QUIT_CMD: + HAD(SCH_QUIT); incomplete_transaction_log(US"QUIT"); if (acl_smtp_quit != NULL) @@ -3346,12 +3678,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); @@ -3364,6 +3695,7 @@ while (done <= 0) case RSET_CMD: + HAD(SCH_RSET); incomplete_transaction_log(US"RSET"); smtp_reset(reset_point); toomany = FALSE; @@ -3373,6 +3705,7 @@ while (done <= 0) case NOOP_CMD: + HAD(SCH_NOOP); smtp_printf("250 OK\r\n"); break; @@ -3382,6 +3715,7 @@ while (done <= 0) permitted hosts. */ case HELP_CMD: + HAD(SCH_HELP); smtp_printf("214-Commands supported:\r\n"); { uschar buffer[256]; @@ -3423,6 +3757,7 @@ while (done <= 0) case ETRN_CMD: + HAD(SCH_ETRN); if (sender_address != NULL) { done = synprot_error(L_smtp_protocol_error, 503, NULL, @@ -3491,7 +3826,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; } @@ -3567,7 +3903,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;