-/* $Cambridge: exim/src/src/smtp_in.c,v 1.26 2005/09/13 11:13:27 ph10 Exp $ */
+/* $Cambridge: exim/src/src/smtp_in.c,v 1.44 2006/09/25 10:14:20 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2006 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for handling an incoming SMTP call. */
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 */
/* 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);
}
-
-
/*************************************************
* 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. */
-for (i = 0; i < ACL_M_MAX; i++) acl_var[ACL_C_MAX + i] = NULL;
+/* Reset message ACL variables */
+
+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
smtp_start_session(void)
{
int size = 256;
-int i, ptr;
+int ptr;
uschar *p, *s, *ss;
-/* If we are running in the test harness, and the incoming call is from
-127.0.0.2 (sic), have a short delay. This makes it possible to test handling of
-input sent too soon (before the banner is output). */
-
-if (running_in_test_harness &&
- sender_host_address != NULL &&
- Ustrcmp(sender_host_address, "127.0.0.2") == 0)
- sleep(1);
-
/* Default values for certain variables */
helo_seen = esmtp = helo_accept_junk = FALSE;
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_C_MAX; i++) acl_var[i] = NULL;
+acl_var_c = NULL;
/* Allow for trailing 0 in the command buffer. */
/* 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: "
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
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; uf > 3 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 > 3)
+ {
+ 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++;
}
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;
+int ovector[3];
+uschar *smtp_code;
uschar *lognl;
uschar *sender_info = US"";
uschar *what =
if (drop) rc = FAIL;
+/* Set the default SMTP code */
+
+smtp_code = (rc != FAIL)? US"451" : acl_wherecodes[where];
+
+/* Check a user 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,
+use it instead of the default code. */
+
+if (user_msg != NULL)
+ {
+ int n = pcre_exec(regex_smtp_code, NULL, CS user_msg, Ustrlen(user_msg), 0,
+ PCRE_EOPT, ovector, sizeof(ovector)/sizeof(int));
+ if (n >= 0)
+ {
+ if (user_msg[0] != smtp_code[0])
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "configured error code starts with "
+ "incorrect digit (expected %c) in \"%s\"", smtp_code[0], user_msg);
+
+ /* If log_msg == user_msg (the default set in acl.c if no log message is
+ specified, we must adjust the log message to show the code that is
+ actually going to be used. */
+
+ if (log_msg == user_msg)
+ log_msg = string_sprintf("%s %s", smtp_code, log_msg + ovector[1]);
+ }
+ else
+ {
+ smtp_code = user_msg;
+ codelen = ovector[1]; /* Includes final space */
+ }
+ user_msg += ovector[1]; /* Chop the code off the message */
+ }
+ }
+
/* 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;
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
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");
}
/* 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);
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
#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;
break; /* HELO/EHLO */
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));