-/* $Cambridge: exim/src/src/smtp_in.c,v 1.28 2005/12/14 10:00:05 ph10 Exp $ */
+/* $Cambridge: exim/src/src/smtp_in.c,v 1.54 2007/02/20 11:37:16 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2007 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for handling an incoming SMTP call. */
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 *
static int sync_cmd_limit;
static int smtp_write_error = 0;
+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
#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 */
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);
}
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 */
!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
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;
}
}
/* 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
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);
}
* 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.
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;
-
-
/*************************************************
* Reset for new message *
*************************************************/
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 */
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
/* 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)
{
case HELO_CMD:
case EHLO_CMD:
- check_helo(smtp_cmd_argument);
+ check_helo(smtp_cmd_data);
/* Fall through */
case RSET_CMD:
/* 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");
/* 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 */
/* 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");
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,
smtp_start_session(void)
{
int size = 256;
-int i, ptr;
+int ptr, esclen;
+uschar *user_msg, *log_msg;
+uschar *code, *esc;
uschar *p, *s, *ss;
+smtp_connection_start = time(NULL);
+for (smtp_ch_index = 0; smtp_ch_index < SMTP_HBUFF_SIZE; smtp_ch_index++)
+ smtp_connection_had[smtp_ch_index] = SCH_NONE;
+smtp_ch_index = 0;
+
/* Default values for certain variables */
helo_seen = esmtp = helo_accept_junk = FALSE;
+smtp_mailcmd_count = 0;
count_nonmail = TRUE_UNSET;
synprot_error_count = unknown_command_count = nonmail_command_count = 0;
smtp_delay_mail = smtp_rlm_base;
auth_advertised = FALSE;
pipelining_advertised = FALSE;
+pipelining_enable = TRUE;
sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING;
memset(sender_host_cache, 0, sizeof(sender_host_cache));
-sender_host_authenticated = NULL;
+/* If receiving by -bs from a trusted user, or testing with -bh, we allow
+authentication settings from -oMaa to remain in force. */
+
+if (!host_checking && !sender_host_notsocket) sender_host_authenticated = NULL;
authenticated_by = NULL;
#ifdef SUPPORT_TLS
/* Reset ACL connection variables */
-for (i = 0; i < ACL_CVARS; i++) acl_var[i] = NULL;
+acl_var_c = NULL;
-/* Allow for trailing 0 in the command buffer. */
+/* Allow for trailing 0 in the command and data buffers. */
-smtp_cmd_buffer = (uschar *)malloc(smtp_cmd_buffer_size + 1);
+smtp_cmd_buffer = (uschar *)malloc(2*smtp_cmd_buffer_size + 2);
if (smtp_cmd_buffer == NULL)
log_write(0, LOG_MAIN|LOG_PANIC_DIE,
"malloc() failed for SMTP command buffer");
+smtp_data_buffer = smtp_cmd_buffer + smtp_cmd_buffer_size + 1;
/* For batched input, the protocol setting can be overridden from the
command line by a trusted caller. */
/* 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: "
smtps port for use with older style SSL MTAs. */
#ifdef SUPPORT_TLS
- if (tls_on_connect && tls_server_start(tls_require_ciphers) != OK)
+ if (tls_on_connect &&
+ tls_server_start(tls_require_ciphers,
+ gnutls_require_mac, gnutls_require_kx, gnutls_require_proto) != OK)
return FALSE;
#endif
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
- /* Check for reserved slots. Note that the count value doesn't include
- this process, as it gets upped in the parent process. */
+ /* Check for reserved slots. The value of smtp_accept_count has already been
+ incremented to include this process. */
if (smtp_accept_max > 0 &&
- smtp_accept_count + 1 > smtp_accept_max - smtp_accept_reserve)
+ smtp_accept_count > smtp_accept_max - smtp_accept_reserve)
{
if ((rc = verify_check_host(&smtp_reserve_hosts)) != OK)
{
log_write(L_connection_reject,
LOG_MAIN, "temporarily refused connection from %s: not in "
"reserve list: connected=%d max=%d reserve=%d%s",
- host_and_ident(FALSE), smtp_accept_count, smtp_accept_max,
+ host_and_ident(FALSE), smtp_accept_count - 1, smtp_accept_max,
smtp_accept_reserve, (rc == DEFER)? " (lookup deferred)" : "");
smtp_printf("421 %s: Too many concurrent SMTP connections; "
"please try again later\r\n", smtp_active_hostname);
/* 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)
/* 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 */
{
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;
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
*/
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++;
}
+/*************************************************
+* 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 *
*************************************************/
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
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 =
#endif
(where == ACL_WHERE_PREDATA)? US"DATA" :
(where == ACL_WHERE_DATA)? US"after DATA" :
- (smtp_cmd_argument == NULL)?
+ (smtp_cmd_data == NULL)?
string_sprintf("%s in \"connect\" ACL", acl_wherenames[where]) :
- string_sprintf("%s %s", acl_wherenames[where], smtp_cmd_argument);
+ string_sprintf("%s %s", acl_wherenames[where], smtp_cmd_data);
if (drop) rc = FAIL;
+/* Set the default SMTP code, 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
/* 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"
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,
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;
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,
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;
}
}
-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 *
*************************************************/
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;
pid_t pid;
int start, end, sender_domain, recipient_domain;
int ptr, size, rc;
- int c;
+ int c, i;
auth_instance *au;
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");
/* 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 != '_')
{
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
- 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)
break;
}
- /* Run the checking code, passing the remainder of the command
- line as data. Initialize $0 empty. The authenticator may set up
- other numeric variables. Afterwards, have a go at expanding the set_id
- string, even if authentication failed - for bad passwords it can be useful
- to log the userid. On success, require set_id to expand and exist, and
- put it in authenticated_id. Save this in permanent store, as the working
- store gets reset at HELO, RSET, etc. */
+ /* Run the checking code, passing the remainder of the command line as
+ data. Initials the $auth<n> variables as empty. Initialize $0 empty and set
+ it as the only set numerical variable. The authenticator may set $auth<n>
+ and also set other numeric variables. The $auth<n> variables are preferred
+ nowadays; the numerical variables remain for backwards compatibility.
+
+ Afterwards, have a go at expanding the set_id string, even if
+ authentication failed - for bad passwords it can be useful to log the
+ userid. On success, require set_id to expand and exist, and put it in
+ authenticated_id. Save this in permanent store, as the working store gets
+ reset at HELO, RSET, etc. */
+ for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
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<n> */
/* The value of authenticated_id is stored in the spool file and printed in
log lines. It must not contain binary zeros or newline characters. In
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;
/* 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);
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;
}
}
- /* 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;
tls_advertised = FALSE;
#endif
- s = string_sprintf("250 %s Hello %s%s%s",
- smtp_active_hostname,
- (sender_ident == NULL)? US"" : sender_ident,
- (sender_ident == NULL)? US"" : US" at ",
- (sender_host_name == NULL)? sender_helo_name : sender_host_name);
+ smtp_code = US"250 "; /* Default response code plus space*/
+ if (user_msg == NULL)
+ {
+ s = string_sprintf("%.3s %s Hello %s%s%s",
+ smtp_code,
+ smtp_active_hostname,
+ (sender_ident == NULL)? US"" : sender_ident,
+ (sender_ident == NULL)? US"" : US" at ",
+ (sender_host_name == NULL)? sender_helo_name : sender_host_name);
+
+ ptr = Ustrlen(s);
+ size = ptr + 1;
+
+ if (sender_host_address != NULL)
+ {
+ s = string_cat(s, &size, &ptr, US" [", 2);
+ s = string_cat(s, &size, &ptr, sender_host_address,
+ Ustrlen(sender_host_address));
+ s = string_cat(s, &size, &ptr, US"]", 1);
+ }
+ }
- ptr = Ustrlen(s);
- size = ptr + 1;
+ /* A user-supplied EHLO greeting may not contain more than one line. Note
+ that the code returned by smtp_message_code() includes the terminating
+ whitespace character. */
- if (sender_host_address != NULL)
+ else
{
- s = string_cat(s, &size, &ptr, US" [", 2);
- s = string_cat(s, &size, &ptr, sender_host_address,
- Ustrlen(sender_host_address));
- s = string_cat(s, &size, &ptr, US"]", 1);
+ char *ss;
+ int codelen = 4;
+ smtp_message_code(&smtp_code, &codelen, &user_msg, NULL);
+ s = string_sprintf("%.*s%s", codelen, smtp_code, user_msg);
+ if ((ss = strpbrk(CS s, "\r\n")) != NULL)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "EHLO/HELO response must not contain "
+ "newlines: message truncated: %s", string_printing(s));
+ *ss = 0;
+ }
+ ptr = Ustrlen(s);
+ size = ptr + 1;
}
s = string_cat(s, &size, &ptr, US"\r\n", 2);
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
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
if (acl_smtp_expn != NULL)
{
- s = string_cat(s, &size, &ptr, US"250-EXPN\r\n", 10);
+ s = string_cat(s, &size, &ptr, smtp_code, 3);
+ s = string_cat(s, &size, &ptr, US"-EXPN\r\n", 7);
}
/* Exim is quite happy with pipelining, so let the other end know that
it is safe to use it, unless advertising is disabled. */
- if (verify_check_host(&pipelining_advertise_hosts) == OK)
+ if (pipelining_enable &&
+ verify_check_host(&pipelining_advertise_hosts) == OK)
{
- s = string_cat(s, &size, &ptr, US"250-PIPELINING\r\n", 16);
+ s = string_cat(s, &size, &ptr, smtp_code, 3);
+ s = string_cat(s, &size, &ptr, US"-PIPELINING\r\n", 13);
sync_cmd_limit = NON_SYNC_CMD_PIPELINING;
pipelining_advertised = TRUE;
}
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;
}
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
#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 */
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 */
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");
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 =
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;
}
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",
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);
extracted address. */
case RCPT_CMD:
+ HAD(SCH_RCPT);
rcpt_count++;
was_rcpt = TRUE;
/* 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");
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,
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;
}
{
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),
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);
}
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: "
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)
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 */
}
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);
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 */
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));
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);
{
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;
#ifdef SUPPORT_TLS
case STARTTLS_CMD:
+ HAD(SCH_STARTTLS);
if (!tls_advertised)
{
done = synprot_error(L_smtp_protocol_error, 503, NULL,
We must allow for an extra EHLO command and an extra AUTH command after
STARTTLS that don't add to the nonmail command count. */
- if ((rc = tls_server_start(tls_require_ciphers)) == OK)
+ if ((rc = tls_server_start(tls_require_ciphers, gnutls_require_mac,
+ gnutls_require_kx, gnutls_require_proto)) == OK)
{
if (!tls_remember_esmtp)
helo_seen = esmtp = auth_advertised = pipelining_advertised = FALSE;
message. */
case QUIT_CMD:
+ HAD(SCH_QUIT);
incomplete_transaction_log(US"QUIT");
if (acl_smtp_quit != NULL)
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);
case RSET_CMD:
+ HAD(SCH_RSET);
incomplete_transaction_log(US"RSET");
smtp_reset(reset_point);
toomany = FALSE;
case NOOP_CMD:
+ HAD(SCH_NOOP);
smtp_printf("250 OK\r\n");
break;
permitted hosts. */
case HELP_CMD:
+ HAD(SCH_HELP);
smtp_printf("214-Commands supported:\r\n");
{
uschar buffer[256];
case ETRN_CMD:
+ HAD(SCH_ETRN);
if (sender_address != NULL)
{
done = synprot_error(L_smtp_protocol_error, 503, NULL,
/* 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,
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;
else
{
- if (*smtp_cmd_argument++ != '#')
+ if (*smtp_cmd_data++ != '#')
{
done = synprot_error(L_smtp_syntax_error, 501, NULL,
US"argument must begin with #");
}
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. */
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;
}
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;
}
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;
case TOO_MANY_NONMAIL_CMD:
+ s = smtp_cmd_buffer;
+ while (*s != 0 && !isspace(*s)) s++;
incomplete_transaction_log(US"too many non-mail commands");
log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
"nonmail commands (last was \"%.*s\")", host_and_ident(FALSE),
- smtp_cmd_argument - smtp_cmd_buffer, smtp_cmd_buffer);
+ s - smtp_cmd_buffer, smtp_cmd_buffer);
smtp_printf("554 Too many nonmail commands\r\n");
done = 1; /* Pretend eof - drops connection */
break;