-/* $Cambridge: exim/src/src/smtp_in.c,v 1.48 2006/11/20 11:43:40 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 - 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. */
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 */
!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;
}
}
+/*************************************************
+* Log lack of MAIL if so configured *
+*************************************************/
+
+/* This function is called when an SMTP session ends. If the log selector
+smtp_no_mail is set, write a log line giving some details of what has happened
+in the SMTP session.
+
+Arguments: none
+Returns: nothing
+*/
+
+void
+smtp_log_no_mail(void)
+{
+int size, ptr, i;
+uschar *s, *sep;
+
+if (smtp_mailcmd_count > 0 || (log_extra_selector & LX_smtp_no_mail) == 0)
+ return;
+
+s = NULL;
+size = ptr = 0;
+
+if (sender_host_authenticated != NULL)
+ {
+ s = string_append(s, &size, &ptr, 2, US" A=", sender_host_authenticated);
+ if (authenticated_id != NULL)
+ s = string_append(s, &size, &ptr, 2, US":", authenticated_id);
+ }
+
+#ifdef SUPPORT_TLS
+if ((log_extra_selector & LX_tls_cipher) != 0 && tls_cipher != NULL)
+ s = string_append(s, &size, &ptr, 2, US" X=", tls_cipher);
+if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
+ tls_cipher != NULL)
+ s = string_append(s, &size, &ptr, 2, US" CV=",
+ tls_certificate_verified? "yes":"no");
+if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_peerdn != NULL)
+ s = string_append(s, &size, &ptr, 3, US" DN=\"", tls_peerdn, US"\"");
+#endif
+
+sep = (smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE)?
+ US" C=..." : US" C=";
+for (i = smtp_ch_index; i < SMTP_HBUFF_SIZE; i++)
+ {
+ if (smtp_connection_had[i] != SCH_NONE)
+ {
+ s = string_append(s, &size, &ptr, 2, sep,
+ smtp_names[smtp_connection_had[i]]);
+ sep = US",";
+ }
+ }
+
+for (i = 0; i < smtp_ch_index; i++)
+ {
+ s = string_append(s, &size, &ptr, 2, sep, smtp_names[smtp_connection_had[i]]);
+ sep = US",";
+ }
+
+if (s != NULL) s[ptr] = 0; else s = US"";
+log_write(0, LOG_MAIN, "no MAIL in SMTP connection from %s D=%s%s",
+ host_and_ident(FALSE),
+ readconf_printtime(time(NULL) - smtp_connection_start), s);
+}
+
+
+
/*************************************************
* Check HELO line and set sender_helo_name *
*************************************************/
* 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;
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,
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));
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. */
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
}
#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);
#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;
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;
/* 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
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> */
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;
/* 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, smtp_code, 3);
s = string_cat(s, &size, &ptr, US"-PIPELINING\r\n", 13);
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",
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),
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)
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 */
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)
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. */
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;
}
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;