* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for handling an incoming SMTP call. */
#include "exim.h"
+#include <assert.h>
/* Initialize for TCP wrappers if so configured. It appears that the macro
VRFY_CMD, EXPN_CMD, NOOP_CMD, /* RFC as requiring synchronization */
ETRN_CMD, /* This by analogy with TURN from the RFC */
STARTTLS_CMD, /* Required by the STARTTLS RFC */
+ TLS_AUTH_CMD, /* auto-command at start of SSL */
/* This is a dummy to identify the non-sync commands when pipelining */
forced TRUE, to allow for the re-authentication that can happen at that point.
QUIT is also "falsely" labelled as a mail command so that it doesn't up the
-count of non-mail commands and possibly provoke an error. */
+count of non-mail commands and possibly provoke an error.
+
+tls_auth is a pseudo-command, never expected in input. It is activated
+on TLS startup and looks for a tls authenticator. */
static smtp_cmd_list cmd_list[] = {
/* name len cmd has_arg is_mail_cmd */
{ "auth", sizeof("auth")-1, AUTH_CMD, TRUE, TRUE },
#ifdef SUPPORT_TLS
{ "starttls", sizeof("starttls")-1, STARTTLS_CMD, FALSE, FALSE },
+ { "tls_auth", 0, TLS_AUTH_CMD, FALSE, TRUE },
#endif
/* If you change anything above here, also fix the definitions below. */
#define CMD_LIST_EHLO 2
#define CMD_LIST_AUTH 3
#define CMD_LIST_STARTTLS 4
+#define CMD_LIST_TLS_AUTH 5
/* 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. */
/* Sanity check and validate optional args to MAIL FROM: envelope */
enum {
+ ENV_MAIL_OPT_NULL,
ENV_MAIL_OPT_SIZE, ENV_MAIL_OPT_BODY, ENV_MAIL_OPT_AUTH,
#ifndef DISABLE_PRDR
ENV_MAIL_OPT_PRDR,
#ifdef EXPERIMENTAL_INTERNATIONAL
ENV_MAIL_OPT_UTF8,
#endif
- ENV_MAIL_OPT_NULL
};
typedef struct {
uschar * name; /* option requested during MAIL cmd */
#ifdef EXPERIMENTAL_INTERNATIONAL
{ US"SMTPUTF8",ENV_MAIL_OPT_UTF8, FALSE }, /* rfc6531 */
#endif
- { US"NULL", ENV_MAIL_OPT_NULL, FALSE }
+ /* keep this the last entry */
+ { US"NULL", ENV_MAIL_OPT_NULL, FALSE },
};
/* When reading SMTP from a remote host, we have to use our own versions of the
continue;
}
#endif
- 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 ( p->len
+ && 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 */
if (is_inetd)
return string_sprintf("SMTP connection from %s (via inetd)", hostname);
-if ((log_extra_selector & LX_incoming_interface) != 0 &&
- interface_address != NULL)
+if (LOGGING(incoming_interface) && interface_address != NULL)
return string_sprintf("SMTP connection from %s I=[%s]:%d", hostname,
interface_address, interface_port);
int size = sizep ? *sizep : 0;
int ptr = ptrp ? *ptrp : 0;
- if ((log_extra_selector & LX_tls_cipher) != 0 && tls_in.cipher != NULL)
+ if (LOGGING(tls_cipher) && tls_in.cipher != NULL)
s = string_append(s, &size, &ptr, 2, US" X=", tls_in.cipher);
- if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
- tls_in.cipher != NULL)
+ if (LOGGING(tls_certificate_verified) && tls_in.cipher != NULL)
s = string_append(s, &size, &ptr, 2, US" CV=",
tls_in.certificate_verified? "yes":"no");
- if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_in.peerdn != NULL)
+ if (LOGGING(tls_peerdn) && tls_in.peerdn != NULL)
s = string_append(s, &size, &ptr, 3, US" DN=\"",
string_printing(tls_in.peerdn), US"\"");
- if ((log_extra_selector & LX_tls_sni) != 0 && tls_in.sni != NULL)
+ if (LOGGING(tls_sni) && tls_in.sni != NULL)
s = string_append(s, &size, &ptr, 3, US" SNI=\"",
string_printing(tls_in.sni), US"\"");
int size, ptr, i;
uschar *s, *sep;
-if (smtp_mailcmd_count > 0 || (log_extra_selector & LX_smtp_no_mail) == 0)
+if (smtp_mailcmd_count > 0 || !LOGGING(smtp_no_mail))
return;
s = NULL;
memset(sender_address_cache, 0, sizeof(sender_address_cache));
memset(sender_domain_cache, 0, sizeof(sender_domain_cache));
+#ifndef DISABLE_PRDR
prdr_requested = FALSE;
+#endif
/* Reset the DSN flags */
dsn_ret = 0;
it is the canonical extracted address which is all that is kept. */
case MAIL_CMD:
+ smtp_mailcmd_count++; /* Count for no-mail log */
if (sender_address != NULL)
/* The function moan_smtp_batch() does not return. */
moan_smtp_batch(smtp_cmd_buffer, "503 Sender already given");
incomplete_transaction_log(uschar *what)
{
if (sender_address == NULL || /* No transaction in progress */
- (log_write_selector & L_smtp_incomplete_transaction) == 0 /* Not logging */
- ) return;
+ !LOGGING(smtp_incomplete_transaction))
+ return;
/* Build list of recipients for logging */
setflag(sender_verified_failed, af_sverify_told);
- if (rc != FAIL || (log_extra_selector & LX_sender_verify_fail) != 0)
+ if (rc != FAIL || LOGGING(sender_verify_fail))
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",
+static int
+smtp_in_auth(auth_instance *au, uschar ** s, uschar ** ss)
+{
+const uschar *set_id = NULL;
+int rc, i;
+
+/* 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 */
+
+rc = (au->info->servercode)(au, smtp_cmd_data);
+if (au->set_id) 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
+normal use, it never will, but when playing around or testing, this error
+can (did) happen. To guard against this, ensure that the id contains only
+printing characters. */
+
+if (set_id) set_id = string_printing(set_id);
+
+/* For the non-OK cases, set up additional logging data if set_id
+is not empty. */
+
+if (rc != OK)
+ set_id = set_id && *set_id
+ ? string_sprintf(" (set_id=%s)", set_id) : US"";
+
+/* Switch on the result */
+
+switch(rc)
+ {
+ case OK:
+ if (!au->set_id || set_id) /* Complete success */
+ {
+ if (set_id) authenticated_id = string_copy_malloc(set_id);
+ sender_host_authenticated = au->name;
+ authentication_failed = FALSE;
+ authenticated_fail_id = NULL; /* Impossible to already be set? */
+
+ received_protocol =
+ (sender_host_address ? protocols : protocols_local)
+ [pextend + pauthed + (tls_in.active >= 0 ? pcrpted:0)];
+ *s = *ss = US"235 Authentication succeeded";
+ authenticated_by = au;
+ break;
+ }
+
+ /* Authentication succeeded, but we failed to expand the set_id string.
+ Treat this as a temporary error. */
+
+ auth_defer_msg = expand_string_message;
+ /* Fall through */
+
+ case DEFER:
+ if (set_id) authenticated_fail_id = string_copy_malloc(set_id);
+ *s = string_sprintf("435 Unable to authenticate at present%s",
+ auth_defer_user_msg);
+ *ss = string_sprintf("435 Unable to authenticate at present%s: %s",
+ set_id, auth_defer_msg);
+ break;
+
+ case BAD64:
+ *s = *ss = US"501 Invalid base64 data";
+ break;
+
+ case CANCELLED:
+ *s = *ss = US"501 Authentication cancelled";
+ break;
+
+ case UNEXPECTED:
+ *s = *ss = US"553 Initial data not expected";
+ break;
+
+ case FAIL:
+ if (set_id) authenticated_fail_id = string_copy_malloc(set_id);
+ *s = US"535 Incorrect authentication data";
+ *ss = string_sprintf("535 Incorrect authentication data%s", set_id);
+ break;
+
+ default:
+ if (set_id) authenticated_fail_id = string_copy_malloc(set_id);
+ *s = US"435 Internal error";
+ *ss = string_sprintf("435 Internal error%s: return %d from authentication "
+ "check", set_id, rc);
+ break;
+ }
+
+return rc;
+}
+
+
+
/*************************************************
* Initialize for SMTP incoming message *
*************************************************/
cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
#ifdef SUPPORT_TLS
cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = TRUE;
+cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
#endif
/* Set the local signal handler for SIGTERM - it tries to end off tidily */
uschar *user_msg = NULL;
uschar *recipient = NULL;
uschar *hello = NULL;
- const uschar *set_id = NULL;
uschar *s, *ss;
BOOL was_rej_mail = FALSE;
BOOL was_rcpt = FALSE;
pid_t pid;
int start, end, sender_domain, recipient_domain;
int ptr, size, rc;
- int c, i;
+ int c;
auth_instance *au;
uschar *orcpt = NULL;
int flags;
+#if defined(SUPPORT_TLS) && defined(AUTH_TLS)
+ /* Check once per STARTTLS or SSL-on-connect for a TLS AUTH */
+ if ( tls_in.active >= 0
+ && tls_in.peercert
+ && tls_in.certificate_verified
+ && cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd
+ )
+ {
+ cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = FALSE;
+ if (acl_smtp_auth)
+ {
+ rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth, &user_msg, &log_msg);
+ if (rc != OK)
+ {
+ done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
+ continue;
+ }
+ }
+
+ for (au = auths; au; au = au->next)
+ if (strcmpic(US"tls", au->driver_name) == 0)
+ {
+ smtp_cmd_data = NULL;
+
+ if (smtp_in_auth(au, &s, &ss) == OK)
+ DEBUG(D_auth) debug_printf("tls auth succeeded\n");
+ else
+ DEBUG(D_auth) debug_printf("tls auth not succeeded\n");
+ break;
+ }
+ }
+#endif
+
switch(smtp_read_command(TRUE))
{
/* The AUTH command is not permitted to occur inside a transaction, and may
US"AUTH command used when not advertised");
break;
}
- if (sender_host_authenticated != NULL)
+ if (sender_host_authenticated)
{
done = synprot_error(L_smtp_protocol_error, 503, NULL,
US"already authenticated");
break;
}
- if (sender_address != NULL)
+ if (sender_address)
{
done = synprot_error(L_smtp_protocol_error, 503, NULL,
US"not permitted in mail transaction");
/* Check the ACL */
- if (acl_smtp_auth != NULL)
+ if (acl_smtp_auth)
{
rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth, &user_msg, &log_msg);
if (rc != OK)
as a server and which has been advertised (unless, sigh, allow_auth_
unadvertised is set). */
- for (au = auths; au != NULL; au = au->next)
- {
+ for (au = auths; au; au = au->next)
if (strcmpic(s, au->public_name) == 0 && au->server &&
- (au->advertised || allow_auth_unadvertised)) break;
- }
+ (au->advertised || allow_auth_unadvertised))
+ break;
- if (au == NULL)
+ if (au)
{
- done = synprot_error(L_smtp_protocol_error, 504, NULL,
- string_sprintf("%s authentication mechanism not supported", s));
- break;
- }
+ c = smtp_in_auth(au, &s, &ss);
- /* 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_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
- normal use, it never will, but when playing around or testing, this error
- can (did) happen. To guard against this, ensure that the id contains only
- printing characters. */
-
- if (set_id != NULL) set_id = string_printing(set_id);
-
- /* For the non-OK cases, set up additional logging data if set_id
- is not empty. */
-
- if (c != OK)
- {
- if (set_id != NULL && *set_id != 0)
- set_id = string_sprintf(" (set_id=%s)", set_id);
- else set_id = US"";
- }
-
- /* Switch on the result */
-
- switch(c)
- {
- case OK:
- if (au->set_id == NULL || set_id != NULL) /* Complete success */
- {
- if (set_id != NULL) authenticated_id = string_copy_malloc(set_id);
- sender_host_authenticated = au->name;
- authentication_failed = FALSE;
- authenticated_fail_id = NULL; /* Impossible to already be set? */
-
- received_protocol =
- (sender_host_address ? protocols : protocols_local)
- [pextend + pauthed + (tls_in.active >= 0 ? pcrpted:0)];
- s = ss = US"235 Authentication succeeded";
- authenticated_by = au;
- break;
- }
-
- /* Authentication succeeded, but we failed to expand the set_id string.
- Treat this as a temporary error. */
-
- auth_defer_msg = expand_string_message;
- /* Fall through */
-
- case DEFER:
- if (set_id != NULL) authenticated_fail_id = string_copy_malloc(set_id);
- s = string_sprintf("435 Unable to authenticate at present%s",
- auth_defer_user_msg);
- ss = string_sprintf("435 Unable to authenticate at present%s: %s",
- set_id, auth_defer_msg);
- break;
-
- case BAD64:
- s = ss = US"501 Invalid base64 data";
- break;
-
- case CANCELLED:
- s = ss = US"501 Authentication cancelled";
- break;
-
- case UNEXPECTED:
- s = ss = US"553 Initial data not expected";
- break;
-
- case FAIL:
- if (set_id != NULL) authenticated_fail_id = string_copy_malloc(set_id);
- s = US"535 Incorrect authentication data";
- ss = string_sprintf("535 Incorrect authentication data%s", set_id);
- break;
-
- default:
- if (set_id != NULL) authenticated_fail_id = string_copy_malloc(set_id);
- s = US"435 Internal error";
- ss = string_sprintf("435 Internal error%s: return %d from authentication "
- "check", set_id, c);
- break;
+ smtp_printf("%s\r\n", s);
+ if (c != OK)
+ log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
+ au->name, host_and_ident(FALSE), ss);
}
-
- smtp_printf("%s\r\n", s);
- if (c != OK)
- log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
- au->name, host_and_ident(FALSE), ss);
+ else
+ done = synprot_error(L_smtp_protocol_error, 504, NULL,
+ string_sprintf("%s authentication mechanism not supported", s));
break; /* AUTH_CMD */
letters, so output the names in upper case, though we actually recognize
them in either case in the AUTH command. */
- if (auths != NULL)
- {
- if (verify_check_host(&auth_advertise_hosts) == OK)
- {
- auth_instance *au;
- BOOL first = TRUE;
- for (au = auths; au != NULL; au = au->next)
- {
- if (au->server && (au->advertise_condition == NULL ||
- expand_check_condition(au->advertise_condition, au->name,
- US"authenticator")))
- {
- int saveptr;
- if (first)
- {
- s = string_cat(s, &size, &ptr, smtp_code, 3);
- s = string_cat(s, &size, &ptr, US"-AUTH", 5);
- first = FALSE;
- auth_advertised = TRUE;
- }
- saveptr = ptr;
- s = string_cat(s, &size, &ptr, US" ", 1);
- s = string_cat(s, &size, &ptr, au->public_name,
- Ustrlen(au->public_name));
- while (++saveptr < ptr) s[saveptr] = toupper(s[saveptr]);
- au->advertised = TRUE;
- }
- else au->advertised = FALSE;
- }
- if (!first) s = string_cat(s, &size, &ptr, US"\r\n", 2);
- }
- }
+ if ( auths
+#if defined(SUPPORT_TLS) && defined(AUTH_TLS)
+ && !sender_host_authenticated
+#endif
+ && verify_check_host(&auth_advertise_hosts) == OK
+ )
+ {
+ auth_instance *au;
+ BOOL first = TRUE;
+ for (au = auths; au; au = au->next)
+ if (au->server && (au->advertise_condition == NULL ||
+ expand_check_condition(au->advertise_condition, au->name,
+ US"authenticator")))
+ {
+ int saveptr;
+ if (first)
+ {
+ s = string_cat(s, &size, &ptr, smtp_code, 3);
+ s = string_cat(s, &size, &ptr, US"-AUTH", 5);
+ first = FALSE;
+ auth_advertised = TRUE;
+ }
+ saveptr = ptr;
+ s = string_cat(s, &size, &ptr, US" ", 1);
+ s = string_cat(s, &size, &ptr, au->public_name,
+ Ustrlen(au->public_name));
+ while (++saveptr < ptr) s[saveptr] = toupper(s[saveptr]);
+ au->advertised = TRUE;
+ }
+ else
+ au->advertised = FALSE;
+
+ if (!first) s = string_cat(s, &size, &ptr, US"\r\n", 2);
+ }
/* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer)
if it has been included in the binary, and the host matches
if (!extract_option(&name, &value)) break;
for (mail_args = env_mail_type_list;
- (char *)mail_args < (char *)env_mail_type_list + sizeof(env_mail_type_list);
+ mail_args->value != ENV_MAIL_OPT_NULL;
mail_args++
)
if (strcmpic(name, mail_args->name) == 0)
}
break;
#endif
- /* Unknown option. Stick back the terminator characters and break
+ /* No valid option. Stick back the terminator characters and break
the loop. Do the name-terminator second as extract_option sets
- value==name when it found no equal-sign.
- An error for a malformed address will occur. */
- default:
+ value==name when it found no equal-sign.
+ An error for a malformed address will occur. */
+ case ENV_MAIL_OPT_NULL:
value[-1] = '=';
name[-1] = ' ';
arg_error = TRUE;
break;
+
+ default: assert(0);
}
/* Break out of for loop if switch() had bad argument or
when start of the email address is reached */
helo_seen = esmtp = auth_advertised = pipelining_advertised = FALSE;
cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
cmd_list[CMD_LIST_AUTH].is_mail_cmd = TRUE;
+ cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
if (sender_helo_name != NULL)
{
store_free(sender_helo_name);
/* If ETRN queue runs are to be serialized, check the database to
ensure one isn't already running. */
- if (smtp_etrn_serialize && !enq_start(etrn_serialize_key))
+ if (smtp_etrn_serialize && !enq_start(etrn_serialize_key, 1))
{
smtp_printf("458 Already processing %s\r\n", smtp_cmd_data);
break;