"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "src/smtp_in.c" between
exim-4.91.tar.xz and exim-4.92.tar.xz

About: Exim is a message transfer agent (MTA).

smtp_in.c  (exim-4.91.tar.xz):smtp_in.c  (exim-4.92.tar.xz)
skipping to change at line 129 skipping to change at line 129
to the circular buffer that holds a list of the last n received. */ to the circular buffer that holds a list of the last n received. */
#define HAD(n) \ #define HAD(n) \
smtp_connection_had[smtp_ch_index++] = n; \ smtp_connection_had[smtp_ch_index++] = n; \
if (smtp_ch_index >= SMTP_HBUFF_SIZE) smtp_ch_index = 0 if (smtp_ch_index >= SMTP_HBUFF_SIZE) smtp_ch_index = 0
/************************************************* /*************************************************
* Local static variables * * Local static variables *
*************************************************/ *************************************************/
static auth_instance *authenticated_by; static struct {
static BOOL auth_advertised; BOOL auth_advertised :1;
#ifdef SUPPORT_TLS #ifdef SUPPORT_TLS
static BOOL tls_advertised; BOOL tls_advertised :1;
# ifdef EXPERIMENTAL_REQUIRETLS
BOOL requiretls_advertised :1;
# endif
#endif #endif
static BOOL dsn_advertised; BOOL dsn_advertised :1;
static BOOL esmtp; BOOL esmtp :1;
static BOOL helo_required = FALSE; BOOL helo_required :1;
static BOOL helo_verify = FALSE; BOOL helo_verify :1;
static BOOL helo_seen; BOOL helo_seen :1;
static BOOL helo_accept_junk; BOOL helo_accept_junk :1;
static BOOL count_nonmail; #ifdef EXPERIMENTAL_PIPE_CONNECT
static BOOL pipelining_advertised; BOOL pipe_connect_acceptable :1;
static BOOL rcpt_smtp_response_same; #endif
static BOOL rcpt_in_progress; BOOL rcpt_smtp_response_same :1;
static int nonmail_command_count; BOOL rcpt_in_progress :1;
static BOOL smtp_exit_function_called = 0; BOOL smtp_exit_function_called :1;
#ifdef SUPPORT_I18N #ifdef SUPPORT_I18N
static BOOL smtputf8_advertised; BOOL smtputf8_advertised :1;
#endif #endif
} fl = {
.helo_required = FALSE,
.helo_verify = FALSE,
.smtp_exit_function_called = FALSE,
};
static auth_instance *authenticated_by;
static int count_nonmail;
static int nonmail_command_count;
static int synprot_error_count; static int synprot_error_count;
static int unknown_command_count; static int unknown_command_count;
static int sync_cmd_limit; static int sync_cmd_limit;
static int smtp_write_error = 0; static int smtp_write_error = 0;
static uschar *rcpt_smtp_response; static uschar *rcpt_smtp_response;
static uschar *smtp_data_buffer; static uschar *smtp_data_buffer;
static uschar *smtp_cmd_data; static uschar *smtp_cmd_data;
/* We need to know the position of RSET, HELO, EHLO, AUTH, and STARTTLS. Their /* We need to know the position of RSET, HELO, EHLO, AUTH, and STARTTLS. Their
skipping to change at line 183 skipping to change at line 195
tls_auth is a pseudo-command, never expected in input. It is activated tls_auth is a pseudo-command, never expected in input. It is activated
on TLS startup and looks for a tls authenticator. */ on TLS startup and looks for a tls authenticator. */
static smtp_cmd_list cmd_list[] = { static smtp_cmd_list cmd_list[] = {
/* name len cmd has_arg is_mail_cmd */ /* name len cmd has_arg is_mail_cmd */
{ "rset", sizeof("rset")-1, RSET_CMD, FALSE, FALSE }, /* First */ { "rset", sizeof("rset")-1, RSET_CMD, FALSE, FALSE }, /* First */
{ "helo", sizeof("helo")-1, HELO_CMD, TRUE, FALSE }, { "helo", sizeof("helo")-1, HELO_CMD, TRUE, FALSE },
{ "ehlo", sizeof("ehlo")-1, EHLO_CMD, TRUE, FALSE }, { "ehlo", sizeof("ehlo")-1, EHLO_CMD, TRUE, FALSE },
{ "auth", sizeof("auth")-1, AUTH_CMD, TRUE, TRUE }, { "auth", sizeof("auth")-1, AUTH_CMD, TRUE, TRUE },
#ifdef SUPPORT_TLS #ifdef SUPPORT_TLS
{ "starttls", sizeof("starttls")-1, STARTTLS_CMD, FALSE, FALSE }, { "starttls", sizeof("starttls")-1, STARTTLS_CMD, FALSE, FALSE },
{ "tls_auth", 0, TLS_AUTH_CMD, FALSE, FALSE }, { "tls_auth", 0, TLS_AUTH_CMD, FALSE, FALSE },
#endif #endif
/* If you change anything above here, also fix the definitions below. */ /* If you change anything above here, also fix the definitions below. */
{ "mail from:", sizeof("mail from:")-1, MAIL_CMD, TRUE, TRUE }, { "mail from:", sizeof("mail from:")-1, MAIL_CMD, TRUE, TRUE },
{ "rcpt to:", sizeof("rcpt to:")-1, RCPT_CMD, TRUE, TRUE }, { "rcpt to:", sizeof("rcpt to:")-1, RCPT_CMD, TRUE, TRUE },
{ "data", sizeof("data")-1, DATA_CMD, FALSE, TRUE }, { "data", sizeof("data")-1, DATA_CMD, FALSE, TRUE },
{ "bdat", sizeof("bdat")-1, BDAT_CMD, TRUE, TRUE }, { "bdat", sizeof("bdat")-1, BDAT_CMD, TRUE, TRUE },
{ "quit", sizeof("quit")-1, QUIT_CMD, FALSE, TRUE }, { "quit", sizeof("quit")-1, QUIT_CMD, FALSE, TRUE },
{ "noop", sizeof("noop")-1, NOOP_CMD, TRUE, FALSE }, { "noop", sizeof("noop")-1, NOOP_CMD, TRUE, FALSE },
{ "etrn", sizeof("etrn")-1, ETRN_CMD, TRUE, FALSE }, { "etrn", sizeof("etrn")-1, ETRN_CMD, TRUE, FALSE },
skipping to change at line 254 skipping to change at line 266
enum { enum {
ENV_MAIL_OPT_NULL, ENV_MAIL_OPT_NULL,
ENV_MAIL_OPT_SIZE, ENV_MAIL_OPT_BODY, ENV_MAIL_OPT_AUTH, ENV_MAIL_OPT_SIZE, ENV_MAIL_OPT_BODY, ENV_MAIL_OPT_AUTH,
#ifndef DISABLE_PRDR #ifndef DISABLE_PRDR
ENV_MAIL_OPT_PRDR, ENV_MAIL_OPT_PRDR,
#endif #endif
ENV_MAIL_OPT_RET, ENV_MAIL_OPT_ENVID, ENV_MAIL_OPT_RET, ENV_MAIL_OPT_ENVID,
#ifdef SUPPORT_I18N #ifdef SUPPORT_I18N
ENV_MAIL_OPT_UTF8, ENV_MAIL_OPT_UTF8,
#endif #endif
#ifdef EXPERIMENTAL_REQUIRETLS
ENV_MAIL_OPT_REQTLS,
#endif
}; };
typedef struct { typedef struct {
uschar * name; /* option requested during MAIL cmd */ uschar * name; /* option requested during MAIL cmd */
int value; /* enum type */ int value; /* enum type */
BOOL need_value; /* TRUE requires value (name=value pair format) BOOL need_value; /* TRUE requires value (name=value pair format)
FALSE is a singleton */ FALSE is a singleton */
} env_mail_type_t; } env_mail_type_t;
static env_mail_type_t env_mail_type_list[] = { static env_mail_type_t env_mail_type_list[] = {
{ US"SIZE", ENV_MAIL_OPT_SIZE, TRUE }, { US"SIZE", ENV_MAIL_OPT_SIZE, TRUE },
{ US"BODY", ENV_MAIL_OPT_BODY, TRUE }, { US"BODY", ENV_MAIL_OPT_BODY, TRUE },
{ US"AUTH", ENV_MAIL_OPT_AUTH, TRUE }, { US"AUTH", ENV_MAIL_OPT_AUTH, TRUE },
#ifndef DISABLE_PRDR #ifndef DISABLE_PRDR
{ US"PRDR", ENV_MAIL_OPT_PRDR, FALSE }, { US"PRDR", ENV_MAIL_OPT_PRDR, FALSE },
#endif #endif
{ US"RET", ENV_MAIL_OPT_RET, TRUE }, { US"RET", ENV_MAIL_OPT_RET, TRUE },
{ US"ENVID", ENV_MAIL_OPT_ENVID, TRUE }, { US"ENVID", ENV_MAIL_OPT_ENVID, TRUE },
#ifdef SUPPORT_I18N #ifdef SUPPORT_I18N
{ US"SMTPUTF8",ENV_MAIL_OPT_UTF8, FALSE }, /* rfc6531 */ { US"SMTPUTF8",ENV_MAIL_OPT_UTF8, FALSE }, /* rfc6531 */
#endif #endif
#ifdef EXPERIMENTAL_REQUIRETLS
/* https://tools.ietf.org/html/draft-ietf-uta-smtp-require-tls-03 */
{ US"REQUIRETLS",ENV_MAIL_OPT_REQTLS, FALSE },
#endif
/* keep this the last entry */ /* keep this the last entry */
{ US"NULL", ENV_MAIL_OPT_NULL, FALSE }, { US"NULL", ENV_MAIL_OPT_NULL, FALSE },
}; };
/* When reading SMTP from a remote host, we have to use our own versions of the /* When reading SMTP from a remote host, we have to use our own versions of the
C input-reading functions, in order to be able to flush the SMTP output only C input-reading functions, in order to be able to flush the SMTP output only
when about to read more data from the socket. This is the only way to get when about to read more data from the socket. This is the only way to get
optimal performance when the client is using pipelining. Flushing for every optimal performance when the client is using pipelining. Flushing for every
command causes a separate packet and reply packet each time; saving all the command causes a separate packet and reply packet each time; saving all the
responses up (when pipelining) combines them into one packet and one response. responses up (when pipelining) combines them into one packet and one response.
skipping to change at line 337 skipping to change at line 356
*/ */
static BOOL static BOOL
wouldblock_reading(void) wouldblock_reading(void)
{ {
int fd, rc; int fd, rc;
fd_set fds; fd_set fds;
struct timeval tzero; struct timeval tzero;
#ifdef SUPPORT_TLS #ifdef SUPPORT_TLS
if (tls_in.active >= 0) if (tls_in.active.sock >= 0)
return !tls_could_read(); return !tls_could_read();
#endif #endif
if (smtp_inptr < smtp_inend) if (smtp_inptr < smtp_inend)
return FALSE; return FALSE;
fd = fileno(smtp_in); fd = fileno(smtp_in);
FD_ZERO(&fds); FD_ZERO(&fds);
FD_SET(fd, &fds); FD_SET(fd, &fds);
tzero.tv_sec = 0; tzero.tv_sec = 0;
skipping to change at line 362 skipping to change at line 381
rc = smtp_getc(GETC_BUFFER_UNLIMITED); rc = smtp_getc(GETC_BUFFER_UNLIMITED);
if (rc < 0) return TRUE; /* End of file or error */ if (rc < 0) return TRUE; /* End of file or error */
smtp_ungetc(rc); smtp_ungetc(rc);
return FALSE; return FALSE;
} }
static BOOL static BOOL
check_sync(void) check_sync(void)
{ {
if (!smtp_enforce_sync || !sender_host_address || sender_host_notsocket) if (!smtp_enforce_sync || !sender_host_address || f.sender_host_notsocket)
return TRUE; return TRUE;
return wouldblock_reading(); return wouldblock_reading();
} }
/* If there's input waiting (and we're doing pipelineing) then we can pipeline /* If there's input waiting (and we're doing pipelineing) then we can pipeline
a reponse with the one following. */ a reponse with the one following. */
static BOOL static BOOL
pipeline_response(void) pipeline_response(void)
{ {
if ( !smtp_enforce_sync || !sender_host_address if ( !smtp_enforce_sync || !sender_host_address
|| sender_host_notsocket || !pipelining_advertised) || f.sender_host_notsocket || !f.smtp_in_pipelining_advertised)
return FALSE;
if (wouldblock_reading()) return FALSE;
f.smtp_in_pipelining_used = TRUE;
return TRUE;
}
#ifdef EXPERIMENTAL_PIPE_CONNECT
static BOOL
pipeline_connect_sends(void)
{
if (!sender_host_address || f.sender_host_notsocket || !fl.pipe_connect_acceptab
le)
return FALSE; return FALSE;
return !wouldblock_reading(); if (wouldblock_reading()) return FALSE;
f.smtp_in_early_pipe_used = TRUE;
return TRUE;
} }
#endif
/************************************************* /*************************************************
* Log incomplete transactions * * Log incomplete transactions *
*************************************************/ *************************************************/
/* This function is called after a transaction has been aborted by RSET, QUIT, /* This function is called after a transaction has been aborted by RSET, QUIT,
connection drops or other errors. It logs the envelope information received connection drops or other errors. It logs the envelope information received
so far in order to preserve address verification attempts. so far in order to preserve address verification attempts.
Argument: string to indicate what aborted the transaction Argument: string to indicate what aborted the transaction
skipping to change at line 415 skipping to change at line 449
raw_recipients = store_get(recipients_count * sizeof(uschar *)); raw_recipients = store_get(recipients_count * sizeof(uschar *));
for (i = 0; i < recipients_count; i++) for (i = 0; i < recipients_count; i++)
raw_recipients[i] = recipients_list[i].address; raw_recipients[i] = recipients_list[i].address;
raw_recipients_count = recipients_count; raw_recipients_count = recipients_count;
} }
log_write(L_smtp_incomplete_transaction, LOG_MAIN|LOG_SENDER|LOG_RECIPIENTS, log_write(L_smtp_incomplete_transaction, LOG_MAIN|LOG_SENDER|LOG_RECIPIENTS,
"%s incomplete transaction (%s)", host_and_ident(TRUE), what); "%s incomplete transaction (%s)", host_and_ident(TRUE), what);
} }
void
smtp_command_timeout_exit(void)
{
log_write(L_lost_incoming_connection,
LOG_MAIN, "SMTP command timeout on%s connection from %s",
tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE));
if (smtp_batched_input)
moan_smtp_batch(NULL, "421 SMTP command timeout"); /* Does not return */
smtp_notquit_exit(US"command-timeout", US"421",
US"%s: SMTP command timeout - closing connection",
smtp_active_hostname);
exim_exit(EXIT_FAILURE, US"receiving");
}
void
smtp_command_sigterm_exit(void)
{
log_write(0, LOG_MAIN, "%s closed after SIGTERM", smtp_get_connection_info());
if (smtp_batched_input)
moan_smtp_batch(NULL, "421 SIGTERM received"); /* Does not return */
smtp_notquit_exit(US"signal-exit", US"421",
US"%s: Service not available - closing connection", smtp_active_hostname);
exim_exit(EXIT_FAILURE, US"receiving");
}
void
smtp_data_timeout_exit(void)
{
log_write(L_lost_incoming_connection,
LOG_MAIN, "SMTP data timeout (message abandoned) on connection from %s F=<%s>"
,
sender_fullhost ? sender_fullhost : US"local process", sender_address);
receive_bomb_out(US"data-timeout", US"SMTP incoming data timeout");
/* Does not return */
}
void
smtp_data_sigint_exit(void)
{
log_write(0, LOG_MAIN, "%s closed after %s",
smtp_get_connection_info(), had_data_sigint == SIGTERM ? "SIGTERM":"SIGINT");
receive_bomb_out(US"signal-exit",
US"Service not available - SIGTERM or SIGINT received");
/* Does not return */
}
/* Refill the buffer, and notify DKIM verification code. /* Refill the buffer, and notify DKIM verification code.
Return false for error or EOF. Return false for error or EOF.
*/ */
static BOOL static BOOL
smtp_refill(unsigned lim) smtp_refill(unsigned lim)
{ {
int rc, save_errno; int rc, save_errno;
if (!smtp_out) return FALSE; if (!smtp_out) return FALSE;
fflush(smtp_out); fflush(smtp_out);
if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout); if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
/* Limit amount read, so non-message data is not fed to DKIM. /* Limit amount read, so non-message data is not fed to DKIM.
Take care to not touch the safety NUL at the end of the buffer. */ Take care to not touch the safety NUL at the end of the buffer. */
rc = read(fileno(smtp_in), smtp_inbuffer, MIN(IN_BUFFER_SIZE-1, lim)); rc = read(fileno(smtp_in), smtp_inbuffer, MIN(IN_BUFFER_SIZE-1, lim));
save_errno = errno; save_errno = errno;
alarm(0); if (smtp_receive_timeout > 0) ALARM_CLR(0);
if (rc <= 0) if (rc <= 0)
{ {
/* Must put the error text in fixed store, because this might be during /* Must put the error text in fixed store, because this might be during
header reading, where it releases unused store above the header. */ header reading, where it releases unused store above the header. */
if (rc < 0) if (rc < 0)
{ {
if (had_command_timeout) /* set by signal handler */
smtp_command_timeout_exit(); /* does not return */
if (had_command_sigterm)
smtp_command_sigterm_exit();
if (had_data_timeout)
smtp_data_timeout_exit();
if (had_data_sigint)
smtp_data_sigint_exit();
smtp_had_error = save_errno; smtp_had_error = save_errno;
smtp_read_error = string_copy_malloc( smtp_read_error = string_copy_malloc(
string_sprintf(" (error: %s)", strerror(save_errno))); string_sprintf(" (error: %s)", strerror(save_errno)));
} }
else smtp_had_eof = 1; else
smtp_had_eof = 1;
return FALSE; return FALSE;
} }
#ifndef DISABLE_DKIM #ifndef DISABLE_DKIM
dkim_exim_verify_feed(smtp_inbuffer, rc); dkim_exim_verify_feed(smtp_inbuffer, rc);
#endif #endif
smtp_inend = smtp_inbuffer + rc; smtp_inend = smtp_inbuffer + rc;
smtp_inptr = smtp_inbuffer; smtp_inptr = smtp_inbuffer;
return TRUE; return TRUE;
} }
skipping to change at line 528 skipping to change at line 617
int int
bdat_getc(unsigned lim) bdat_getc(unsigned lim)
{ {
uschar * user_msg = NULL; uschar * user_msg = NULL;
uschar * log_msg; uschar * log_msg;
for(;;) for(;;)
{ {
#ifndef DISABLE_DKIM #ifndef DISABLE_DKIM
BOOL dkim_save; unsigned dkim_save;
#endif #endif
if (chunking_data_left > 0) if (chunking_data_left > 0)
return lwr_receive_getc(chunking_data_left--); return lwr_receive_getc(chunking_data_left--);
receive_getc = lwr_receive_getc; receive_getc = lwr_receive_getc;
receive_getbuf = lwr_receive_getbuf; receive_getbuf = lwr_receive_getbuf;
receive_ungetc = lwr_receive_ungetc; receive_ungetc = lwr_receive_ungetc;
#ifndef DISABLE_DKIM #ifndef DISABLE_DKIM
dkim_save = dkim_collect_input; dkim_save = dkim_collect_input;
dkim_collect_input = FALSE; dkim_collect_input = 0;
#endif #endif
/* Unless PIPELINING was offered, there should be no next command /* Unless PIPELINING was offered, there should be no next command
until after we ack that chunk */ until after we ack that chunk */
if (!pipelining_advertised && !check_sync()) if (!f.smtp_in_pipelining_advertised && !check_sync())
{ {
unsigned n = smtp_inend - smtp_inptr; unsigned n = smtp_inend - smtp_inptr;
if (n > 32) n = 32; if (n > 32) n = 32;
incomplete_transaction_log(US"sync failure"); incomplete_transaction_log(US"sync failure");
log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error " log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
"(next input sent too soon: pipelining was not advertised): " "(next input sent too soon: pipelining was not advertised): "
"rejected \"%s\" %s next input=\"%s\"%s", "rejected \"%s\" %s next input=\"%s\"%s",
smtp_cmd_buffer, host_and_ident(TRUE), smtp_cmd_buffer, host_and_ident(TRUE),
string_printing(string_copyn(smtp_inptr, n)), string_printing(string_copyn(smtp_inptr, n)),
skipping to change at line 800 skipping to change at line 889
va_end(ap); va_end(ap);
} }
/* This is split off so that verify.c:respond_printf() can, in effect, call /* This is split off so that verify.c:respond_printf() can, in effect, call
smtp_printf(), bearing in mind that in C a vararg function can't directly smtp_printf(), bearing in mind that in C a vararg function can't directly
call another vararg function, only a function which accepts a va_list. */ call another vararg function, only a function which accepts a va_list. */
void void
smtp_vprintf(const char *format, BOOL more, va_list ap) smtp_vprintf(const char *format, BOOL more, va_list ap)
{ {
gstring gs = { .size = big_buffer_size, .ptr = 0, .s = big_buffer };
BOOL yield; BOOL yield;
yield = string_vformat(big_buffer, big_buffer_size, format, ap); yield = !! string_vformat(&gs, FALSE, format, ap);
string_from_gstring(&gs);
DEBUG(D_receive) DEBUG(D_receive)
{ {
void *reset_point = store_get(0); void *reset_point = store_get(0);
uschar *msg_copy, *cr, *end; uschar *msg_copy, *cr, *end;
msg_copy = string_copy(big_buffer); msg_copy = string_copy(gs.s);
end = msg_copy + Ustrlen(msg_copy); end = msg_copy + gs.ptr;
while ((cr = Ustrchr(msg_copy, '\r')) != NULL) /* lose CRs */ while ((cr = Ustrchr(msg_copy, '\r')) != NULL) /* lose CRs */
memmove(cr, cr + 1, (end--) - cr); memmove(cr, cr + 1, (end--) - cr);
debug_printf("SMTP>> %s", msg_copy); debug_printf("SMTP>> %s", msg_copy);
store_reset(reset_point); store_reset(reset_point);
} }
if (!yield) if (!yield)
{ {
log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_printf()"); log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_printf()");
smtp_closedown(US"Unexpected error"); smtp_closedown(US"Unexpected error");
exim_exit(EXIT_FAILURE, NULL); exim_exit(EXIT_FAILURE, NULL);
} }
/* If this is the first output for a (non-batch) RCPT command, see if all RCPTs /* If this is the first output for a (non-batch) RCPT command, see if all RCPTs
have had the same. Note: this code is also present in smtp_respond(). It would have had the same. Note: this code is also present in smtp_respond(). It would
be tidier to have it only in one place, but when it was added, it was easier to be tidier to have it only in one place, but when it was added, it was easier to
do it that way, so as not to have to mess with the code for the RCPT command, do it that way, so as not to have to mess with the code for the RCPT command,
which sometimes uses smtp_printf() and sometimes smtp_respond(). */ which sometimes uses smtp_printf() and sometimes smtp_respond(). */
if (rcpt_in_progress) if (fl.rcpt_in_progress)
{ {
if (rcpt_smtp_response == NULL) if (rcpt_smtp_response == NULL)
rcpt_smtp_response = string_copy(big_buffer); rcpt_smtp_response = string_copy(big_buffer);
else if (rcpt_smtp_response_same && else if (fl.rcpt_smtp_response_same &&
Ustrcmp(rcpt_smtp_response, big_buffer) != 0) Ustrcmp(rcpt_smtp_response, big_buffer) != 0)
rcpt_smtp_response_same = FALSE; fl.rcpt_smtp_response_same = FALSE;
rcpt_in_progress = FALSE; fl.rcpt_in_progress = FALSE;
} }
/* Now write the string */ /* Now write the string */
#ifdef SUPPORT_TLS #ifdef SUPPORT_TLS
if (tls_in.active >= 0) if (tls_in.active.sock >= 0)
{ {
if (tls_write(TRUE, big_buffer, Ustrlen(big_buffer), more) < 0) if (tls_write(NULL, gs.s, gs.ptr, more) < 0)
smtp_write_error = -1; smtp_write_error = -1;
} }
else else
#endif #endif
if (fprintf(smtp_out, "%s", big_buffer) < 0) smtp_write_error = -1; if (fprintf(smtp_out, "%s", gs.s) < 0) smtp_write_error = -1;
} }
/************************************************* /*************************************************
* Flush SMTP out and check for error * * Flush SMTP out and check for error *
*************************************************/ *************************************************/
/* This function isn't currently used within Exim (it detects errors when it /* This function isn't currently used within Exim (it detects errors when it
tries to read the next SMTP input), but is available for use in local_scan(). tries to read the next SMTP input), but is available for use in local_scan().
For non-TLS connections, it flushes the output and checks for errors. For For non-TLS connections, it flushes the output and checks for errors. For
TLS-connections, it checks for a previously-detected TLS write error. TLS-connections, it checks for a previously-detected TLS write error.
Arguments: none Arguments: none
Returns: 0 for no error; -1 after an error Returns: 0 for no error; -1 after an error
*/ */
int int
smtp_fflush(void) smtp_fflush(void)
{ {
if (tls_in.active < 0 && fflush(smtp_out) != 0) smtp_write_error = -1; if (tls_in.active.sock < 0 && fflush(smtp_out) != 0) smtp_write_error = -1;
return smtp_write_error; return smtp_write_error;
} }
/************************************************* /*************************************************
* SMTP command read timeout * * SMTP command read timeout *
*************************************************/ *************************************************/
/* Signal handler for timing out incoming SMTP commands. This attempts to /* Signal handler for timing out incoming SMTP commands. This attempts to
finish off tidily. finish off tidily.
Argument: signal number (SIGALRM) Argument: signal number (SIGALRM)
Returns: nothing Returns: nothing
*/ */
static void static void
command_timeout_handler(int sig) command_timeout_handler(int sig)
{ {
sig = sig; /* Keep picky compilers happy */ had_command_timeout = sig;
log_write(L_lost_incoming_connection,
LOG_MAIN, "SMTP command timeout on%s connection from %s",
(tls_in.active >= 0)? " TLS" : "",
host_and_ident(FALSE));
if (smtp_batched_input)
moan_smtp_batch(NULL, "421 SMTP command timeout"); /* Does not return */
smtp_notquit_exit(US"command-timeout", US"421",
US"%s: SMTP command timeout - closing connection", smtp_active_hostname);
exim_exit(EXIT_FAILURE, US"receiving");
} }
/************************************************* /*************************************************
* SIGTERM received * * SIGTERM received *
*************************************************/ *************************************************/
/* Signal handler for handling SIGTERM. Again, try to finish tidily. /* Signal handler for handling SIGTERM. Again, try to finish tidily.
Argument: signal number (SIGTERM) Argument: signal number (SIGTERM)
Returns: nothing Returns: nothing
*/ */
static void static void
command_sigterm_handler(int sig) command_sigterm_handler(int sig)
{ {
sig = sig; /* Keep picky compilers happy */ had_command_sigterm = sig;
log_write(0, LOG_MAIN, "%s closed after SIGTERM", smtp_get_connection_info());
if (smtp_batched_input)
moan_smtp_batch(NULL, "421 SIGTERM received"); /* Does not return */
smtp_notquit_exit(US"signal-exit", US"421",
US"%s: Service not available - closing connection", smtp_active_hostname);
exim_exit(EXIT_FAILURE, US"receiving");
} }
#ifdef SUPPORT_PROXY #ifdef SUPPORT_PROXY
/************************************************* /*************************************************
* Restore socket timeout to previous value * * Restore socket timeout to previous value *
*************************************************/ *************************************************/
/* If the previous value was successfully retrieved, restore /* If the previous value was successfully retrieved, restore
it before returning control to the non-proxy routines it before returning control to the non-proxy routines
Arguments: fd - File descriptor for input Arguments: fd - File descriptor for input
skipping to change at line 1434 skipping to change at line 1510
bad: bad:
if (yield) if (yield)
{ {
sender_host_name = NULL; sender_host_name = NULL;
(void) host_name_lookup(); (void) host_name_lookup();
host_build_sender_fullhost(); host_build_sender_fullhost();
} }
else else
{ {
proxy_session_failed = TRUE; f.proxy_session_failed = TRUE;
DEBUG(D_receive) DEBUG(D_receive)
debug_printf("Failure to extract proxied host, only QUIT allowed\n"); debug_printf("Failure to extract proxied host, only QUIT allowed\n");
} }
return; return;
} }
#endif #endif
/************************************************* /*************************************************
* Read one command line * * Read one command line *
skipping to change at line 1474 skipping to change at line 1550
*/ */
static int static int
smtp_read_command(BOOL check_sync, unsigned buffer_lim) smtp_read_command(BOOL check_sync, unsigned buffer_lim)
{ {
int c; int c;
int ptr = 0; int ptr = 0;
smtp_cmd_list *p; smtp_cmd_list *p;
BOOL hadnull = FALSE; BOOL hadnull = FALSE;
had_command_timeout = 0;
os_non_restarting_signal(SIGALRM, command_timeout_handler); os_non_restarting_signal(SIGALRM, command_timeout_handler);
while ((c = (receive_getc)(buffer_lim)) != '\n' && c != EOF) while ((c = (receive_getc)(buffer_lim)) != '\n' && c != EOF)
{ {
if (ptr >= SMTP_CMD_BUFFER_SIZE) if (ptr >= SMTP_CMD_BUFFER_SIZE)
{ {
os_non_restarting_signal(SIGALRM, sigalrm_handler); os_non_restarting_signal(SIGALRM, sigalrm_handler);
return OTHER_CMD; return OTHER_CMD;
} }
if (c == 0) if (c == 0)
skipping to change at line 1519 skipping to change at line 1596
if (hadnull) return BADCHAR_CMD; if (hadnull) return BADCHAR_CMD;
/* Scan command list and return identity, having set the data pointer /* Scan command list and return identity, having set the data pointer
to the start of the actual data characters. Check for SMTP synchronization to the start of the actual data characters. Check for SMTP synchronization
if required. */ if required. */
for (p = cmd_list; p < cmd_list_end; p++) for (p = cmd_list; p < cmd_list_end; p++)
{ {
#ifdef SUPPORT_PROXY #ifdef SUPPORT_PROXY
/* Only allow QUIT command if Proxy Protocol parsing failed */ /* Only allow QUIT command if Proxy Protocol parsing failed */
if (proxy_session && proxy_session_failed && p->cmd != QUIT_CMD) if (proxy_session && f.proxy_session_failed && p->cmd != QUIT_CMD)
continue; continue;
#endif #endif
if ( p->len if ( p->len
&& strncmpic(smtp_cmd_buffer, US p->name, p->len) == 0 && 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-1] == ':' /* "mail from:" or "rcpt to:" */
|| smtp_cmd_buffer[p->len] == 0 || smtp_cmd_buffer[p->len] == 0
|| smtp_cmd_buffer[p->len] == ' ' || smtp_cmd_buffer[p->len] == ' '
) ) ) )
{ {
if (smtp_inptr < smtp_inend && /* Outstanding input */ if (smtp_inptr < smtp_inend && /* Outstanding input */
p->cmd < sync_cmd_limit && /* Command should sync */ p->cmd < sync_cmd_limit && /* Command should sync */
check_sync && /* Local flag set */ check_sync && /* Local flag set */
smtp_enforce_sync && /* Global flag set */ smtp_enforce_sync && /* Global flag set */
sender_host_address != NULL && /* Not local input */ sender_host_address != NULL && /* Not local input */
!sender_host_notsocket) /* Really is a socket */ !f.sender_host_notsocket) /* Really is a socket * /
return BADSYN_CMD; return BADSYN_CMD;
/* The variables $smtp_command and $smtp_command_argument point into the /* The variables $smtp_command and $smtp_command_argument point into the
unmodified input buffer. A copy of the latter is taken for actual 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, 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 for example, when processing a MAIL command options such as SIZE that can
follow the sender address. */ follow the sender address. */
smtp_cmd_argument = smtp_cmd_buffer + p->len; smtp_cmd_argument = smtp_cmd_buffer + p->len;
while (isspace(*smtp_cmd_argument)) smtp_cmd_argument++; while (isspace(*smtp_cmd_argument)) smtp_cmd_argument++;
skipping to change at line 1573 skipping to change at line 1650
/* If there is data for a command that does not expect it, generate the /* If there is data for a command that does not expect it, generate the
error here. */ error here. */
return (p->has_arg || *smtp_cmd_data == 0)? p->cmd : BADARG_CMD; return (p->has_arg || *smtp_cmd_data == 0)? p->cmd : BADARG_CMD;
} }
} }
#ifdef SUPPORT_PROXY #ifdef SUPPORT_PROXY
/* Only allow QUIT command if Proxy Protocol parsing failed */ /* Only allow QUIT command if Proxy Protocol parsing failed */
if (proxy_session && proxy_session_failed) if (proxy_session && f.proxy_session_failed)
return PROXY_FAIL_IGNORE_CMD; return PROXY_FAIL_IGNORE_CMD;
#endif #endif
/* Enforce synchronization for unknown commands */ /* Enforce synchronization for unknown commands */
if ( smtp_inptr < smtp_inend /* Outstanding input */ if ( smtp_inptr < smtp_inend /* Outstanding input */
&& check_sync /* Local flag set */ && check_sync /* Local flag set */
&& smtp_enforce_sync /* Global flag set */ && smtp_enforce_sync /* Global flag set */
&& sender_host_address /* Not local input */ && sender_host_address /* Not local input */
&& !sender_host_notsocket) /* Really is a socket */ && !f.sender_host_notsocket) /* Really is a socket */
return BADSYN_CMD; return BADSYN_CMD;
return OTHER_CMD; return OTHER_CMD;
} }
/************************************************* /*************************************************
* Forced closedown of call * * Forced closedown of call *
*************************************************/ *************************************************/
/* This function is called from log.c when Exim is dying because of a serious /* This function is called from log.c when Exim is dying because of a serious
skipping to change at line 1655 skipping to change at line 1732
uschar * uschar *
smtp_get_connection_info(void) smtp_get_connection_info(void)
{ {
const uschar * hostname = sender_fullhost const uschar * hostname = sender_fullhost
? sender_fullhost : sender_host_address; ? sender_fullhost : sender_host_address;
if (host_checking) if (host_checking)
return string_sprintf("SMTP connection from %s", hostname); return string_sprintf("SMTP connection from %s", hostname);
if (sender_host_unknown || sender_host_notsocket) if (f.sender_host_unknown || f.sender_host_notsocket)
return string_sprintf("SMTP connection from %s", sender_ident); return string_sprintf("SMTP connection from %s", sender_ident);
if (is_inetd) if (f.is_inetd)
return string_sprintf("SMTP connection from %s (via inetd)", hostname); return string_sprintf("SMTP connection from %s (via inetd)", hostname);
if (LOGGING(incoming_interface) && interface_address != NULL) if (LOGGING(incoming_interface) && interface_address)
return string_sprintf("SMTP connection from %s I=[%s]:%d", hostname, return string_sprintf("SMTP connection from %s I=[%s]:%d", hostname,
interface_address, interface_port); interface_address, interface_port);
return string_sprintf("SMTP connection from %s", hostname); return string_sprintf("SMTP connection from %s", hostname);
} }
#ifdef SUPPORT_TLS #ifdef SUPPORT_TLS
/* Append TLS-related information to a log line /* Append TLS-related information to a log line
Arguments: Arguments:
skipping to change at line 1741 skipping to change at line 1818
for (i = 0; i < smtp_ch_index; i++) for (i = 0; i < smtp_ch_index; i++)
{ {
g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]); g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]);
sep = US","; sep = US",";
} }
if (!(s = string_from_gstring(g))) s = US""; if (!(s = string_from_gstring(g))) s = US"";
log_write(0, LOG_MAIN, "no MAIL in %sSMTP connection from %s D=%s%s", log_write(0, LOG_MAIN, "no MAIL in %sSMTP connection from %s D=%s%s",
tcp_in_fastopen ? US"TFO " : US"", f.tcp_in_fastopen ? f.tcp_in_fastopen_data ? US"TFO* " : US"TFO " : US"",
host_and_ident(FALSE), string_timesince(&smtp_connection_start), s); host_and_ident(FALSE), string_timesince(&smtp_connection_start), s);
} }
/* Return list of recent smtp commands */ /* Return list of recent smtp commands */
uschar * uschar *
smtp_cmd_hist(void) smtp_cmd_hist(void)
{ {
int i; int i;
gstring * list = NULL; gstring * list = NULL;
skipping to change at line 1788 skipping to change at line 1865
s the data portion of the line (already past any white space) s the data portion of the line (already past any white space)
Returns: TRUE or FALSE Returns: TRUE or FALSE
*/ */
static BOOL static BOOL
check_helo(uschar *s) check_helo(uschar *s)
{ {
uschar *start = s; uschar *start = s;
uschar *end = s + Ustrlen(s); uschar *end = s + Ustrlen(s);
BOOL yield = helo_accept_junk; BOOL yield = fl.helo_accept_junk;
/* Discard any previous helo name */ /* Discard any previous helo name */
if (sender_helo_name) if (sender_helo_name)
{ {
store_free(sender_helo_name); store_free(sender_helo_name);
sender_helo_name = NULL; sender_helo_name = NULL;
} }
/* Skip tests if junk is permitted. */ /* Skip tests if junk is permitted. */
skipping to change at line 1909 skipping to change at line 1986
void void
smtp_reset(void *reset_point) smtp_reset(void *reset_point)
{ {
recipients_list = NULL; recipients_list = NULL;
rcpt_count = rcpt_defer_count = rcpt_fail_count = rcpt_count = rcpt_defer_count = rcpt_fail_count =
raw_recipients_count = recipients_count = recipients_list_max = 0; raw_recipients_count = recipients_count = recipients_list_max = 0;
message_linecount = 0; message_linecount = 0;
message_size = -1; message_size = -1;
acl_added_headers = NULL; acl_added_headers = NULL;
acl_removed_headers = NULL; acl_removed_headers = NULL;
queue_only_policy = FALSE; f.queue_only_policy = FALSE;
rcpt_smtp_response = NULL; rcpt_smtp_response = NULL;
rcpt_smtp_response_same = TRUE; fl.rcpt_smtp_response_same = TRUE;
rcpt_in_progress = FALSE; fl.rcpt_in_progress = FALSE;
deliver_freeze = FALSE; /* Can be set by ACL */ f.deliver_freeze = FALSE; /* Can be set by ACL */
freeze_tell = freeze_tell_config; /* Can be set by ACL */ freeze_tell = freeze_tell_config; /* Can be set by ACL */
fake_response = OK; /* Can be set by ACL */ fake_response = OK; /* Can be set by ACL */
#ifdef WITH_CONTENT_SCAN #ifdef WITH_CONTENT_SCAN
no_mbox_unspool = FALSE; /* Can be set by ACL */ f.no_mbox_unspool = FALSE; /* Can be set by ACL */
#endif #endif
submission_mode = FALSE; /* Can be set by ACL */ f.submission_mode = FALSE; /* Can be set by ACL */
suppress_local_fixups = suppress_local_fixups_default; /* Can be set by ACL */ f.suppress_local_fixups = f.suppress_local_fixups_default; /* Can be set by ACL
active_local_from_check = local_from_check; /* Can be set by ACL */ */
active_local_sender_retain = local_sender_retain; /* Can be set by ACL */ f.active_local_from_check = local_from_check; /* Can be set by ACL */
f.active_local_sender_retain = local_sender_retain; /* Can be set by ACL */
sending_ip_address = NULL; sending_ip_address = NULL;
return_path = sender_address = NULL; return_path = sender_address = NULL;
sender_data = NULL; /* Can be set by ACL */ sender_data = NULL; /* Can be set by ACL */
deliver_localpart_parent = deliver_localpart_orig = NULL; deliver_localpart_parent = deliver_localpart_orig = NULL;
deliver_domain_parent = deliver_domain_orig = NULL; deliver_domain_parent = deliver_domain_orig = NULL;
callout_address = NULL; callout_address = NULL;
submission_name = NULL; /* Can be set by ACL */ submission_name = NULL; /* Can be set by ACL */
raw_sender = NULL; /* After SMTP rewrite, before qualifying */ raw_sender = NULL; /* After SMTP rewrite, before qualifying */
sender_address_unrewritten = NULL; /* Set only after verify rewrite */ sender_address_unrewritten = NULL; /* Set only after verify rewrite */
sender_verified_list = NULL; /* No senders verified */ sender_verified_list = NULL; /* No senders verified */
memset(sender_address_cache, 0, sizeof(sender_address_cache)); memset(sender_address_cache, 0, sizeof(sender_address_cache));
memset(sender_domain_cache, 0, sizeof(sender_domain_cache)); memset(sender_domain_cache, 0, sizeof(sender_domain_cache));
authenticated_sender = NULL; authenticated_sender = NULL;
#ifdef EXPERIMENTAL_BRIGHTMAIL #ifdef EXPERIMENTAL_BRIGHTMAIL
bmi_run = 0; bmi_run = 0;
bmi_verdicts = NULL; bmi_verdicts = NULL;
#endif #endif
dnslist_domain = dnslist_matched = NULL; dnslist_domain = dnslist_matched = NULL;
#ifdef SUPPORT_SPF
spf_header_comment = spf_received = spf_result = spf_smtp_comment = NULL;
spf_result_guessed = FALSE;
#endif
#ifndef DISABLE_DKIM #ifndef DISABLE_DKIM
dkim_cur_signer = dkim_signers = dkim_cur_signer = dkim_signers =
dkim_signing_domain = dkim_signing_selector = NULL; dkim_signing_domain = dkim_signing_selector = dkim_signatures = NULL;
dkim_cur_signer = dkim_signers = dkim_signing_domain = dkim_signing_selector = N ULL; dkim_cur_signer = dkim_signers = dkim_signing_domain = dkim_signing_selector = N ULL;
dkim_disable_verify = dkim_collect_input = FALSE; f.dkim_disable_verify = FALSE;
dkim_collect_input = 0;
dkim_verify_overall = dkim_verify_status = dkim_verify_reason = NULL; dkim_verify_overall = dkim_verify_status = dkim_verify_reason = NULL;
dkim_key_length = 0; dkim_key_length = 0;
dkim_verify_signers = US"$dkim_signers"; #endif
#ifdef EXPERIMENTAL_DMARC
f.dmarc_has_been_checked = f.dmarc_disable_verify = f.dmarc_enable_forensic = FA
LSE;
dmarc_domain_policy = dmarc_status = dmarc_status_text =
dmarc_used_domain = NULL;
#endif
#ifdef EXPERIMENTAL_ARC
arc_state = arc_state_reason = NULL;
#endif #endif
dsn_ret = 0; dsn_ret = 0;
dsn_envid = NULL; dsn_envid = NULL;
deliver_host = deliver_host_address = NULL; /* Can be set by ACL */ deliver_host = deliver_host_address = NULL; /* Can be set by ACL */
#ifndef DISABLE_PRDR #ifndef DISABLE_PRDR
prdr_requested = FALSE; prdr_requested = FALSE;
#endif #endif
#ifdef SUPPORT_SPF
spf_header_comment = spf_received = spf_result = spf_smtp_comment = NULL;
spf_result_guessed = FALSE;
#endif
#ifdef EXPERIMENTAL_DMARC
dmarc_has_been_checked = dmarc_disable_verify = dmarc_enable_forensic = FALSE;
dmarc_domain_policy = dmarc_status = dmarc_status_text = dmarc_used_domain = NUL
L;
#endif
#ifdef EXPERIMENTAL_ARC
arc_state = arc_state_reason = NULL;
#endif
#ifdef SUPPORT_I18N #ifdef SUPPORT_I18N
message_smtputf8 = FALSE; message_smtputf8 = FALSE;
#endif #endif
body_linecount = body_zerocount = 0; body_linecount = body_zerocount = 0;
sender_rate = sender_rate_limit = sender_rate_period = NULL; sender_rate = sender_rate_limit = sender_rate_period = NULL;
ratelimiters_mail = NULL; /* Updated by ratelimit ACL condition */ ratelimiters_mail = NULL; /* Updated by ratelimit ACL condition */
/* Note that ratelimiters_conn persists across resets. */ /* Note that ratelimiters_conn persists across resets. */
/* Reset message ACL variables */ /* Reset message ACL variables */
skipping to change at line 2113 skipping to change at line 2191
if (!raw_sender) if (!raw_sender)
/* The function moan_smtp_batch() does not return. */ /* The function moan_smtp_batch() does not return. */
moan_smtp_batch(smtp_cmd_buffer, "501 %s", errmess); moan_smtp_batch(smtp_cmd_buffer, "501 %s", errmess);
sender_address = string_copy(raw_sender); sender_address = string_copy(raw_sender);
/* Qualify unqualified sender addresses if permitted to do so. */ /* Qualify unqualified sender addresses if permitted to do so. */
if ( !sender_domain if ( !sender_domain
&& sender_address[0] != 0 && sender_address[0] != '@') && sender_address[0] != 0 && sender_address[0] != '@')
if (allow_unqualified_sender) if (f.allow_unqualified_sender)
{ {
sender_address = rewrite_address_qualify(sender_address, FALSE); sender_address = rewrite_address_qualify(sender_address, FALSE);
DEBUG(D_receive) debug_printf("unqualified address %s accepted " DEBUG(D_receive) debug_printf("unqualified address %s accepted "
"and rewritten\n", raw_sender); "and rewritten\n", raw_sender);
} }
/* The function moan_smtp_batch() does not return. */ /* The function moan_smtp_batch() does not return. */
else else
moan_smtp_batch(smtp_cmd_buffer, "501 sender address must contain " moan_smtp_batch(smtp_cmd_buffer, "501 sender address must contain "
"a domain"); "a domain");
break; break;
skipping to change at line 2168 skipping to change at line 2246
&recipient_domain, FALSE); &recipient_domain, FALSE);
if (!recipient) if (!recipient)
/* The function moan_smtp_batch() does not return. */ /* The function moan_smtp_batch() does not return. */
moan_smtp_batch(smtp_cmd_buffer, "501 %s", errmess); moan_smtp_batch(smtp_cmd_buffer, "501 %s", errmess);
/* If the recipient address is unqualified, qualify it if permitted. Then /* If the recipient address is unqualified, qualify it if permitted. Then
add it to the list of recipients. */ add it to the list of recipients. */
if (!recipient_domain) if (!recipient_domain)
if (allow_unqualified_recipient) if (f.allow_unqualified_recipient)
{ {
DEBUG(D_receive) debug_printf("unqualified address %s accepted\n", DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
recipient); recipient);
recipient = rewrite_address_qualify(recipient, TRUE); recipient = rewrite_address_qualify(recipient, TRUE);
} }
/* The function moan_smtp_batch() does not return. */ /* The function moan_smtp_batch() does not return. */
else else
moan_smtp_batch(smtp_cmd_buffer, moan_smtp_batch(smtp_cmd_buffer,
"501 recipient address must contain a domain"); "501 recipient address must contain a domain");
skipping to change at line 2237 skipping to change at line 2315
default: default:
/* The function moan_smtp_batch() does not return. */ /* The function moan_smtp_batch() does not return. */
moan_smtp_batch(smtp_cmd_buffer, "500 Command unrecognized"); moan_smtp_batch(smtp_cmd_buffer, "500 Command unrecognized");
break; break;
} }
} }
return done - 2; /* Convert yield values */ return done - 2; /* Convert yield values */
} }
#ifdef SUPPORT_TLS
static BOOL static BOOL
smtp_log_tls_fail(uschar * errstr) smtp_log_tls_fail(uschar * errstr)
{ {
uschar * conn_info = smtp_get_connection_info(); uschar * conn_info = smtp_get_connection_info();
if (Ustrncmp(conn_info, US"SMTP ", 5) == 0) conn_info += 5; if (Ustrncmp(conn_info, US"SMTP ", 5) == 0) conn_info += 5;
/* I'd like to get separated H= here, but too hard for now */ /* I'd like to get separated H= here, but too hard for now */
log_write(0, LOG_MAIN, "TLS error on %s %s", conn_info, errstr); log_write(0, LOG_MAIN, "TLS error on %s %s", conn_info, errstr);
return FALSE; return FALSE;
} }
#endif
#ifdef TCP_FASTOPEN #ifdef TCP_FASTOPEN
static void static void
tfo_in_check(void) tfo_in_check(void)
{ {
# ifdef TCP_INFO # ifdef TCP_INFO
struct tcp_info tinfo; struct tcp_info tinfo;
socklen_t len = sizeof(tinfo); socklen_t len = sizeof(tinfo);
if ( getsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0 if (getsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0)
&& tinfo.tcpi_state == TCP_SYN_RECV #ifdef TCPI_OPT_SYN_DATA /* FreeBSD 11 does not seem to have this yet */
) if (tinfo.tcpi_options & TCPI_OPT_SYN_DATA)
{ {
DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (state TCP_SYN_REC DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (ACKd data-on-SY
V)\n"); N)\n");
tcp_in_fastopen = TRUE; f.tcp_in_fastopen_data = f.tcp_in_fastopen = TRUE;
} }
else
#endif
if (tinfo.tcpi_state == TCP_SYN_RECV)
{
DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (state TCP_SYN_R
ECV)\n");
f.tcp_in_fastopen = TRUE;
}
# endif # endif
} }
#endif #endif
/************************************************* /*************************************************
* Start an SMTP session * * Start an SMTP session *
*************************************************/ *************************************************/
/* This function is called at the start of an SMTP session. Thereafter, /* This function is called at the start of an SMTP session. Thereafter,
smtp_setup_msg() is called to initiate each separate message. This smtp_setup_msg() is called to initiate each separate message. This
skipping to change at line 2297 skipping to change at line 2384
uschar *p, *s; uschar *p, *s;
gstring * ss; gstring * ss;
gettimeofday(&smtp_connection_start, NULL); gettimeofday(&smtp_connection_start, NULL);
for (smtp_ch_index = 0; smtp_ch_index < SMTP_HBUFF_SIZE; smtp_ch_index++) for (smtp_ch_index = 0; smtp_ch_index < SMTP_HBUFF_SIZE; smtp_ch_index++)
smtp_connection_had[smtp_ch_index] = SCH_NONE; smtp_connection_had[smtp_ch_index] = SCH_NONE;
smtp_ch_index = 0; smtp_ch_index = 0;
/* Default values for certain variables */ /* Default values for certain variables */
helo_seen = esmtp = helo_accept_junk = FALSE; fl.helo_seen = fl.esmtp = fl.helo_accept_junk = FALSE;
smtp_mailcmd_count = 0; smtp_mailcmd_count = 0;
count_nonmail = TRUE_UNSET; count_nonmail = TRUE_UNSET;
synprot_error_count = unknown_command_count = nonmail_command_count = 0; synprot_error_count = unknown_command_count = nonmail_command_count = 0;
smtp_delay_mail = smtp_rlm_base; smtp_delay_mail = smtp_rlm_base;
auth_advertised = FALSE; fl.auth_advertised = FALSE;
pipelining_advertised = FALSE; f.smtp_in_pipelining_advertised = f.smtp_in_pipelining_used = FALSE;
pipelining_enable = TRUE; f.pipelining_enable = TRUE;
sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING; sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING;
smtp_exit_function_called = FALSE; /* For avoiding loop in not-quit exit */ fl.smtp_exit_function_called = FALSE; /* For avoiding loop in not-quit exit * /
/* If receiving by -bs from a trusted user, or testing with -bh, we allow /* If receiving by -bs from a trusted user, or testing with -bh, we allow
authentication settings from -oMaa to remain in force. */ authentication settings from -oMaa to remain in force. */
if (!host_checking && !sender_host_notsocket) if (!host_checking && !f.sender_host_notsocket)
sender_host_auth_pubname = sender_host_authenticated = NULL; sender_host_auth_pubname = sender_host_authenticated = NULL;
authenticated_by = NULL; authenticated_by = NULL;
#ifdef SUPPORT_TLS #ifdef SUPPORT_TLS
tls_in.cipher = tls_in.peerdn = NULL; tls_in.cipher = tls_in.peerdn = NULL;
tls_in.ourcert = tls_in.peercert = NULL; tls_in.ourcert = tls_in.peercert = NULL;
tls_in.sni = NULL; tls_in.sni = NULL;
tls_in.ocsp = OCSP_NOT_REQ; tls_in.ocsp = OCSP_NOT_REQ;
tls_advertised = FALSE; fl.tls_advertised = FALSE;
# ifdef EXPERIMENTAL_REQUIRETLS
fl.requiretls_advertised = FALSE;
# endif
#endif #endif
dsn_advertised = FALSE; fl.dsn_advertised = FALSE;
#ifdef SUPPORT_I18N #ifdef SUPPORT_I18N
smtputf8_advertised = FALSE; fl.smtputf8_advertised = FALSE;
#endif #endif
/* Reset ACL connection variables */ /* Reset ACL connection variables */
acl_var_c = NULL; acl_var_c = NULL;
/* Allow for trailing 0 in the command and data buffers. */ /* Allow for trailing 0 in the command and data buffers. */
if (!(smtp_cmd_buffer = US malloc(2*SMTP_CMD_BUFFER_SIZE + 2))) if (!(smtp_cmd_buffer = US malloc(2*SMTP_CMD_BUFFER_SIZE + 2)))
log_write(0, LOG_MAIN|LOG_PANIC_DIE, log_write(0, LOG_MAIN|LOG_PANIC_DIE,
skipping to change at line 2398 skipping to change at line 2488
/* When a message is input locally via the -bs or -bS options, sender_host_ /* When a message is input locally via the -bs or -bS options, sender_host_
unknown is set unless -oMa was used to force an IP address, in which case it unknown is set unless -oMa was used to force an IP address, in which case it
is checked like a real remote connection. When -bs is used from inetd, this is checked like a real remote connection. When -bs is used from inetd, this
flag is not set, causing the sending host to be checked. The code that deals flag is not set, causing the sending host to be checked. The code that deals
with IP source routing (if configured) is never required for -bs or -bS and with IP source routing (if configured) is never required for -bs or -bS and
the flag sender_host_notsocket is used to suppress it. the flag sender_host_notsocket is used to suppress it.
If smtp_accept_max and smtp_accept_reserve are set, keep some connections in If smtp_accept_max and smtp_accept_reserve are set, keep some connections in
reserve for certain hosts and/or networks. */ reserve for certain hosts and/or networks. */
if (!sender_host_unknown) if (!f.sender_host_unknown)
{ {
int rc; int rc;
BOOL reserved_host = FALSE; BOOL reserved_host = FALSE;
/* Look up IP options (source routing info) on the socket if this is not an /* Look up IP options (source routing info) on the socket if this is not an
-oMa "host", and if any are found, log them and drop the connection. -oMa "host", and if any are found, log them and drop the connection.
Linux (and others now, see below) is different to everyone else, so there Linux (and others now, see below) is different to everyone else, so there
has to be some conditional compilation here. Versions of Linux before 2.1.15 has to be some conditional compilation here. Versions of Linux before 2.1.15
used a structure whose name was "options". Somebody finally realized that used a structure whose name was "options". Somebody finally realized that
skipping to change at line 2428 skipping to change at line 2518
Mac OS 10.x (Darwin) is like the later glibc versions, but without the Mac OS 10.x (Darwin) is like the later glibc versions, but without the
setting of the __GLIBC__ macro, so we can't detect it automatically. There's setting of the __GLIBC__ macro, so we can't detect it automatically. There's
a special macro defined in the os.h file. a special macro defined in the os.h file.
Some DGUX versions on older hardware appear not to support IP options at Some DGUX versions on older hardware appear not to support IP options at
all, so there is now a general macro which can be set to cut out this all, so there is now a general macro which can be set to cut out this
support altogether. support altogether.
How to do this properly in IPv6 is not yet known. */ How to do this properly in IPv6 is not yet known. */
#if !HAVE_IPV6 && !defined(NO_IP_OPTIONS) #if !HAVE_IPV6 && !defined(NO_IP_OPTIONS)
#ifdef GLIBC_IP_OPTIONS #ifdef GLIBC_IP_OPTIONS
#if (!defined __GLIBC__) || (__GLIBC__ < 2) #if (!defined __GLIBC__) || (__GLIBC__ < 2)
#define OPTSTYLE 1 #define OPTSTYLE 1
#else #else
#define OPTSTYLE 2 #define OPTSTYLE 2
#endif #endif
#elif defined DARWIN_IP_OPTIONS #elif defined DARWIN_IP_OPTIONS
#define OPTSTYLE 2 #define OPTSTYLE 2
#else #else
#define OPTSTYLE 3 #define OPTSTYLE 3
#endif #endif
if (!host_checking && !sender_host_notsocket) if (!host_checking && !f.sender_host_notsocket)
{ {
#if OPTSTYLE == 1 #if OPTSTYLE == 1
EXIM_SOCKLEN_T optlen = sizeof(struct ip_options) + MAX_IPOPTLEN; EXIM_SOCKLEN_T optlen = sizeof(struct ip_options) + MAX_IPOPTLEN;
struct ip_options *ipopt = store_get(optlen); struct ip_options *ipopt = store_get(optlen);
#elif OPTSTYLE == 2 #elif OPTSTYLE == 2
struct ip_opts ipoptblock; struct ip_opts ipoptblock;
struct ip_opts *ipopt = &ipoptblock; struct ip_opts *ipopt = &ipoptblock;
EXIM_SOCKLEN_T optlen = sizeof(ipoptblock); EXIM_SOCKLEN_T optlen = sizeof(ipoptblock);
#else #else
struct ipoption ipoptblock; struct ipoption ipoptblock;
skipping to change at line 2583 skipping to change at line 2673
"connection from %s refused (IP options)", host_and_ident(FALSE)); "connection from %s refused (IP options)", host_and_ident(FALSE));
smtp_printf("554 SMTP service not available\r\n", FALSE); smtp_printf("554 SMTP service not available\r\n", FALSE);
return FALSE; return FALSE;
} }
/* Length of options = 0 => there are no options */ /* Length of options = 0 => there are no options */
else DEBUG(D_receive) debug_printf("no IP options found\n"); else DEBUG(D_receive) debug_printf("no IP options found\n");
} }
#endif /* HAVE_IPV6 && !defined(NO_IP_OPTIONS) */ #endif /* HAVE_IPV6 && !defined(NO_IP_OPTIONS) */
/* Set keep-alive in socket options. The option is on by default. This /* Set keep-alive in socket options. The option is on by default. This
setting is an attempt to get rid of some hanging connections that stick in setting is an attempt to get rid of some hanging connections that stick in
read() when the remote end (usually a dialup) goes away. */ read() when the remote end (usually a dialup) goes away. */
if (smtp_accept_keepalive && !sender_host_notsocket) if (smtp_accept_keepalive && !f.sender_host_notsocket)
ip_keepalive(fileno(smtp_out), sender_host_address, FALSE); ip_keepalive(fileno(smtp_out), sender_host_address, FALSE);
/* If the current host matches host_lookup, set the name by doing a /* If the current host matches host_lookup, set the name by doing a
reverse lookup. On failure, sender_host_name will be NULL and reverse lookup. On failure, sender_host_name will be NULL and
host_lookup_failed will be TRUE. This may or may not be serious - optional host_lookup_failed will be TRUE. This may or may not be serious - optional
checks later. */ checks later. */
if (verify_check_host(&host_lookup) == OK) if (verify_check_host(&host_lookup) == OK)
{ {
(void)host_name_lookup(); (void)host_name_lookup();
skipping to change at line 2720 skipping to change at line 2810
smtp_active_hostname); smtp_active_hostname);
return FALSE; return FALSE;
} }
/* Determine whether unqualified senders or recipients are permitted /* Determine whether unqualified senders or recipients are permitted
for this host. Unfortunately, we have to do this every time, in order to for this host. Unfortunately, we have to do this every time, in order to
set the flags so that they can be inspected when considering qualifying set the flags so that they can be inspected when considering qualifying
addresses in the headers. For a site that permits no qualification, this addresses in the headers. For a site that permits no qualification, this
won't take long, however. */ won't take long, however. */
allow_unqualified_sender = f.allow_unqualified_sender =
verify_check_host(&sender_unqualified_hosts) == OK; verify_check_host(&sender_unqualified_hosts) == OK;
allow_unqualified_recipient = f.allow_unqualified_recipient =
verify_check_host(&recipient_unqualified_hosts) == OK; verify_check_host(&recipient_unqualified_hosts) == OK;
/* Determine whether HELO/EHLO is required for this host. The requirement /* Determine whether HELO/EHLO is required for this host. The requirement
can be hard or soft. */ can be hard or soft. */
helo_required = verify_check_host(&helo_verify_hosts) == OK; fl.helo_required = verify_check_host(&helo_verify_hosts) == OK;
if (!helo_required) if (!fl.helo_required)
helo_verify = verify_check_host(&helo_try_verify_hosts) == OK; fl.helo_verify = verify_check_host(&helo_try_verify_hosts) == OK;
/* Determine whether this hosts is permitted to send syntactic junk /* Determine whether this hosts is permitted to send syntactic junk
after a HELO or EHLO command. */ after a HELO or EHLO command. */
helo_accept_junk = verify_check_host(&helo_accept_junk_hosts) == OK; fl.helo_accept_junk = verify_check_host(&helo_accept_junk_hosts) == OK;
} }
/* For batch SMTP input we are now done. */ /* For batch SMTP input we are now done. */
if (smtp_batched_input) return TRUE; if (smtp_batched_input) return TRUE;
/* If valid Proxy Protocol source is connecting, set up session. /* If valid Proxy Protocol source is connecting, set up session.
* Failure will not allow any SMTP function other than QUIT. */ * Failure will not allow any SMTP function other than QUIT. */
#ifdef SUPPORT_PROXY #ifdef SUPPORT_PROXY
proxy_session = FALSE; proxy_session = FALSE;
proxy_session_failed = FALSE; f.proxy_session_failed = FALSE;
if (check_proxy_protocol_host()) if (check_proxy_protocol_host())
setup_proxy_protocol_host(); setup_proxy_protocol_host();
#endif #endif
/* Start up TLS if tls_on_connect is set. This is for supporting the legacy /* Start up TLS if tls_on_connect is set. This is for supporting the legacy
smtps port for use with older style SSL MTAs. */ smtps port for use with older style SSL MTAs. */
#ifdef SUPPORT_TLS #ifdef SUPPORT_TLS
if (tls_in.on_connect) if (tls_in.on_connect)
{ {
skipping to change at line 2848 skipping to change at line 2938
ss = string_catn(ss, p, len); ss = string_catn(ss, p, len);
ss = string_catn(ss, US"\r\n", 2); ss = string_catn(ss, US"\r\n", 2);
p += len; p += len;
if (linebreak) p++; if (linebreak) p++;
} }
while (*p); while (*p);
/* Before we write the banner, check that there is no input pending, unless /* Before we write the banner, check that there is no input pending, unless
this synchronisation check is disabled. */ this synchronisation check is disabled. */
#ifdef EXPERIMENTAL_PIPE_CONNECT
fl.pipe_connect_acceptable =
sender_host_address && verify_check_host(&pipe_connect_advertise_hosts) == OK;
if (!check_sync()) if (!check_sync())
{ if (fl.pipe_connect_acceptable)
unsigned n = smtp_inend - smtp_inptr; f.smtp_in_early_pipe_used = TRUE;
if (n > 32) n = 32; else
#else
if (!check_sync())
#endif
{
unsigned n = smtp_inend - smtp_inptr;
if (n > 32) n = 32;
log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol " log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol "
"synchronization error (input sent without waiting for greeting): " "synchronization error (input sent without waiting for greeting): "
"rejected connection from %s input=\"%s\"", host_and_ident(TRUE), "rejected connection from %s input=\"%s\"", host_and_ident(TRUE),
string_printing(string_copyn(smtp_inptr, n))); string_printing(string_copyn(smtp_inptr, n)));
smtp_printf("554 SMTP synchronization error\r\n", FALSE); smtp_printf("554 SMTP synchronization error\r\n", FALSE);
return FALSE; return FALSE;
} }
/* Now output the banner */ /* Now output the banner */
/*XXX the ehlo-resp code does its own tls/nontls bit. Maybe subroutine that? */
smtp_printf("%s", FALSE, string_from_gstring(ss)); smtp_printf("%s",
#ifdef EXPERIMENTAL_PIPE_CONNECT
fl.pipe_connect_acceptable && pipeline_connect_sends(),
#else
FALSE,
#endif
string_from_gstring(ss));
/* Attempt to see if we sent the banner before the last ACK of the 3-way /* Attempt to see if we sent the banner before the last ACK of the 3-way
handshake arrived. If so we must have managed a TFO. */ handshake arrived. If so we must have managed a TFO. */
#ifdef TCP_FASTOPEN #ifdef TCP_FASTOPEN
tfo_in_check(); tfo_in_check();
#endif #endif
return TRUE; return TRUE;
} }
skipping to change at line 2946 skipping to change at line 3053
Returns: nothing Returns: nothing
*/ */
void void
smtp_respond(uschar* code, int codelen, BOOL final, uschar *msg) smtp_respond(uschar* code, int codelen, BOOL final, uschar *msg)
{ {
int esclen = 0; int esclen = 0;
uschar *esc = US""; uschar *esc = US"";
if (!final && no_multiline_responses) return; if (!final && f.no_multiline_responses) return;
if (codelen > 4) if (codelen > 4)
{ {
esc = code + 4; esc = code + 4;
esclen = codelen - 4; esclen = codelen - 4;
} }
/* If this is the first output for a (non-batch) RCPT command, see if all RCPTs /* If this is the first output for a (non-batch) RCPT command, see if all RCPTs
have had the same. Note: this code is also present in smtp_printf(). It would have had the same. Note: this code is also present in smtp_printf(). It would
be tidier to have it only in one place, but when it was added, it was easier to be tidier to have it only in one place, but when it was added, it was easier to
do it that way, so as not to have to mess with the code for the RCPT command, do it that way, so as not to have to mess with the code for the RCPT command,
which sometimes uses smtp_printf() and sometimes smtp_respond(). */ which sometimes uses smtp_printf() and sometimes smtp_respond(). */
if (rcpt_in_progress) if (fl.rcpt_in_progress)
{ {
if (rcpt_smtp_response == NULL) if (rcpt_smtp_response == NULL)
rcpt_smtp_response = string_copy(msg); rcpt_smtp_response = string_copy(msg);
else if (rcpt_smtp_response_same && else if (fl.rcpt_smtp_response_same &&
Ustrcmp(rcpt_smtp_response, msg) != 0) Ustrcmp(rcpt_smtp_response, msg) != 0)
rcpt_smtp_response_same = FALSE; fl.rcpt_smtp_response_same = FALSE;
rcpt_in_progress = FALSE; fl.rcpt_in_progress = FALSE;
} }
/* Now output the message, splitting it up into multiple lines if necessary. /* Now output the message, splitting it up into multiple lines if necessary.
We only handle pipelining these responses as far as nonfinal/final groups, We only handle pipelining these responses as far as nonfinal/final groups,
not the whole MAIL/RCPT/DATA response set. */ not the whole MAIL/RCPT/DATA response set. */
for (;;) for (;;)
{ {
uschar *nl = Ustrchr(msg, '\n'); uschar *nl = Ustrchr(msg, '\n');
if (nl == NULL) if (nl == NULL)
{ {
smtp_printf("%.3s%c%.*s%s\r\n", !final, code, final ? ' ':'-', esclen, esc, msg); smtp_printf("%.3s%c%.*s%s\r\n", !final, code, final ? ' ':'-', esclen, esc, msg);
return; return;
} }
else if (nl[1] == 0 || no_multiline_responses) else if (nl[1] == 0 || f.no_multiline_responses)
{ {
smtp_printf("%.3s%c%.*s%.*s\r\n", !final, code, final ? ' ':'-', esclen, esc , smtp_printf("%.3s%c%.*s%.*s\r\n", !final, code, final ? ' ':'-', esclen, esc ,
(int)(nl - msg), msg); (int)(nl - msg), msg);
return; return;
} }
else else
{ {
smtp_printf("%.3s-%.*s%.*s\r\n", TRUE, code, esclen, esc, (int)(nl - msg), m sg); smtp_printf("%.3s-%.*s%.*s\r\n", TRUE, code, esclen, esc, (int)(nl - msg), m sg);
msg = nl + 1; msg = nl + 1;
while (isspace(*msg)) msg++; while (isspace(*msg)) msg++;
skipping to change at line 3148 skipping to change at line 3255
} }
/* If there's been a sender verification failure with a specific message, and /* 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 we have not sent a response about it yet, do so now, as a preliminary line for
failures, but not defers. However, always log it for defer, and log it for fail 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. */ unless the sender_verify_fail log selector has been turned off. */
if (sender_verified_failed && if (sender_verified_failed &&
!testflag(sender_verified_failed, af_sverify_told)) !testflag(sender_verified_failed, af_sverify_told))
{ {
BOOL save_rcpt_in_progress = rcpt_in_progress; BOOL save_rcpt_in_progress = fl.rcpt_in_progress;
rcpt_in_progress = FALSE; /* So as not to treat these as the error */ fl.rcpt_in_progress = FALSE; /* So as not to treat these as the error */
setflag(sender_verified_failed, af_sverify_told); setflag(sender_verified_failed, af_sverify_told);
if (rc != FAIL || LOGGING(sender_verify_fail)) if (rc != FAIL || LOGGING(sender_verify_fail))
log_write(0, LOG_MAIN|LOG_REJECT, "%s sender verify %s for <%s>%s", log_write(0, LOG_MAIN|LOG_REJECT, "%s sender verify %s for <%s>%s",
host_and_ident(TRUE), host_and_ident(TRUE),
((sender_verified_failed->special_action & 255) == DEFER)? "defer":"fail", ((sender_verified_failed->special_action & 255) == DEFER)? "defer":"fail",
sender_verified_failed->address, sender_verified_failed->address,
(sender_verified_failed->message == NULL)? US"" : (sender_verified_failed->message == NULL)? US"" :
string_sprintf(": %s", sender_verified_failed->message)); string_sprintf(": %s", sender_verified_failed->message));
skipping to change at line 3181 skipping to change at line 3288
"The initial connection, or a HELO or MAIL FROM:<> command was\n" "The initial connection, or a HELO or MAIL FROM:<> command was\n"
"rejected. Refusing MAIL FROM:<> does not help fight spam, disregards\ n" "rejected. Refusing MAIL FROM:<> does not help fight spam, disregards\ n"
"RFC requirements, and stops you from receiving standard bounce\n" "RFC requirements, and stops you from receiving standard bounce\n"
"messages. This host does not accept mail from domains whose servers\n " "messages. This host does not accept mail from domains whose servers\n "
"refuse bounces." "refuse bounces."
: :
"Verification failed for <%s>\n%s", "Verification failed for <%s>\n%s",
sender_verified_failed->address, sender_verified_failed->address,
sender_verified_failed->user_message)); sender_verified_failed->user_message));
rcpt_in_progress = save_rcpt_in_progress; fl.rcpt_in_progress = save_rcpt_in_progress;
} }
/* Sort out text for logging */ /* Sort out text for logging */
log_msg = log_msg ? string_sprintf(": %s", log_msg) : US""; log_msg = log_msg ? string_sprintf(": %s", log_msg) : US"";
if ((lognl = Ustrchr(log_msg, '\n'))) *lognl = 0; if ((lognl = Ustrchr(log_msg, '\n'))) *lognl = 0;
/* Send permanent failure response to the command, but the code used isn't /* Send permanent failure response to the command, but the code used isn't
always a 5xx one - see comments at the start of this function. If the original 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. */ rc was FAIL_DROP we drop the connection and yield 2. */
skipping to change at line 3206 skipping to change at line 3313
/* Send temporary failure response to the command. Don't give any details, /* Send temporary failure response to the command. Don't give any details,
unless acl_temp_details is set. This is TRUE for a callout defer, a "defer" unless acl_temp_details is set. This is TRUE for a callout defer, a "defer"
verb, and for a header verify when smtp_return_error_details is set. verb, and for a header verify when smtp_return_error_details is set.
This conditional logic is all somewhat of a mess because of the odd This conditional logic is all somewhat of a mess because of the odd
interactions between temp_details and return_error_details. One day it should interactions between temp_details and return_error_details. One day it should
be re-implemented in a tidier fashion. */ be re-implemented in a tidier fashion. */
else else
if (acl_temp_details && user_msg) if (f.acl_temp_details && user_msg)
{ {
if ( smtp_return_error_details if ( smtp_return_error_details
&& sender_verified_failed && sender_verified_failed
&& sender_verified_failed->message && sender_verified_failed->message
) )
smtp_respond(smtp_code, codelen, FALSE, sender_verified_failed->message); smtp_respond(smtp_code, codelen, FALSE, sender_verified_failed->message);
smtp_respond(smtp_code, codelen, TRUE, user_msg); smtp_respond(smtp_code, codelen, TRUE, user_msg);
} }
else else
skipping to change at line 3289 skipping to change at line 3396
Returns: Nothing Returns: Nothing
*/ */
void void
smtp_notquit_exit(uschar *reason, uschar *code, uschar *defaultrespond, ...) smtp_notquit_exit(uschar *reason, uschar *code, uschar *defaultrespond, ...)
{ {
int rc; int rc;
uschar *user_msg = NULL; uschar *user_msg = NULL;
uschar *log_msg = NULL; uschar *log_msg = NULL;
/* Check for recursive acll */ /* Check for recursive call */
if (smtp_exit_function_called) if (fl.smtp_exit_function_called)
{ {
log_write(0, LOG_PANIC, "smtp_notquit_exit() called more than once (%s)", log_write(0, LOG_PANIC, "smtp_notquit_exit() called more than once (%s)",
reason); reason);
return; return;
} }
smtp_exit_function_called = TRUE; fl.smtp_exit_function_called = TRUE;
/* Call the not-QUIT ACL, if there is one, unless no reason is given. */ /* Call the not-QUIT ACL, if there is one, unless no reason is given. */
if (acl_smtp_notquit && reason) if (acl_smtp_notquit && reason)
{ {
smtp_notquit_reason = reason; smtp_notquit_reason = reason;
if ((rc = acl_check(ACL_WHERE_NOTQUIT, NULL, acl_smtp_notquit, &user_msg, if ((rc = acl_check(ACL_WHERE_NOTQUIT, NULL, acl_smtp_notquit, &user_msg,
&log_msg)) == ERROR) &log_msg)) == ERROR)
log_write(0, LOG_MAIN|LOG_PANIC, "ACL for not-QUIT returned ERROR: %s", log_write(0, LOG_MAIN|LOG_PANIC, "ACL for not-QUIT returned ERROR: %s",
log_msg); log_msg);
} }
/* If the connection was dropped, we certainly are no longer talking TLS */
tls_in.active.sock = -1;
/* Write an SMTP response if we are expected to give one. As the default /* Write an SMTP response if we are expected to give one. As the default
responses are all internal, they should always fit in the buffer, but code a responses are all internal, they should be reasonable size. */
warning, just in case. Note that string_vformat() still leaves a complete
string, even if it is incomplete. */
if (code && defaultrespond) if (code && defaultrespond)
{ {
if (user_msg) if (user_msg)
smtp_respond(code, 3, TRUE, user_msg); smtp_respond(code, 3, TRUE, user_msg);
else else
{ {
uschar buffer[128]; gstring * g;
va_list ap; va_list ap;
va_start(ap, defaultrespond); va_start(ap, defaultrespond);
if (!string_vformat(buffer, sizeof(buffer), CS defaultrespond, ap)) g = string_vformat(NULL, TRUE, CS defaultrespond, ap);
log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_notquit_exit()"
);
smtp_printf("%s %s\r\n", FALSE, code, buffer);
va_end(ap); va_end(ap);
smtp_printf("%s %s\r\n", FALSE, code, string_from_gstring(g));
} }
mac_smtp_fflush(); mac_smtp_fflush();
} }
} }
/************************************************* /*************************************************
* Verify HELO argument * * Verify HELO argument *
*************************************************/ *************************************************/
/* This function is called if helo_verify_hosts or helo_try_verify_hosts is /* This function is called if helo_verify_hosts or helo_try_verify_hosts is
skipping to change at line 3370 skipping to change at line 3478
if (sender_helo_name == NULL) if (sender_helo_name == NULL)
{ {
HDEBUG(D_receive) debug_printf("no EHLO/HELO command was issued\n"); HDEBUG(D_receive) debug_printf("no EHLO/HELO command was issued\n");
} }
/* Deal with the case of -bs without an IP address */ /* Deal with the case of -bs without an IP address */
else if (sender_host_address == NULL) else if (sender_host_address == NULL)
{ {
HDEBUG(D_receive) debug_printf("no client IP address: assume success\n"); HDEBUG(D_receive) debug_printf("no client IP address: assume success\n");
helo_verified = TRUE; f.helo_verified = TRUE;
} }
/* Deal with the more common case when there is a sending IP address */ /* Deal with the more common case when there is a sending IP address */
else if (sender_helo_name[0] == '[') else if (sender_helo_name[0] == '[')
{ {
helo_verified = Ustrncmp(sender_helo_name+1, sender_host_address, f.helo_verified = Ustrncmp(sender_helo_name+1, sender_host_address,
Ustrlen(sender_host_address)) == 0; Ustrlen(sender_host_address)) == 0;
#if HAVE_IPV6 #if HAVE_IPV6
if (!helo_verified) if (!f.helo_verified)
{ {
if (strncmpic(sender_host_address, US"::ffff:", 7) == 0) if (strncmpic(sender_host_address, US"::ffff:", 7) == 0)
helo_verified = Ustrncmp(sender_helo_name + 1, f.helo_verified = Ustrncmp(sender_helo_name + 1,
sender_host_address + 7, Ustrlen(sender_host_address) - 7) == 0; sender_host_address + 7, Ustrlen(sender_host_address) - 7) == 0;
} }
#endif #endif
HDEBUG(D_receive) HDEBUG(D_receive)
{ if (helo_verified) debug_printf("matched host address\n"); } { if (f.helo_verified) debug_printf("matched host address\n"); }
} }
/* Do a reverse lookup if one hasn't already given a positive or negative /* Do a reverse lookup if one hasn't already given a positive or negative
response. If that fails, or the name doesn't match, try checking with a forward response. If that fails, or the name doesn't match, try checking with a forward
lookup. */ lookup. */
else else
{ {
if (sender_host_name == NULL && !host_lookup_failed) if (sender_host_name == NULL && !host_lookup_failed)
yield = host_name_lookup() != DEFER; yield = host_name_lookup() != DEFER;
/* If a host name is known, check it and all its aliases. */ /* If a host name is known, check it and all its aliases. */
if (sender_host_name) if (sender_host_name)
if ((helo_verified = strcmpic(sender_host_name, sender_helo_name) == 0)) if ((f.helo_verified = strcmpic(sender_host_name, sender_helo_name) == 0))
{ {
sender_helo_dnssec = sender_host_dnssec; sender_helo_dnssec = sender_host_dnssec;
HDEBUG(D_receive) debug_printf("matched host name\n"); HDEBUG(D_receive) debug_printf("matched host name\n");
} }
else else
{ {
uschar **aliases = sender_host_aliases; uschar **aliases = sender_host_aliases;
while (*aliases) while (*aliases)
if ((helo_verified = strcmpic(*aliases++, sender_helo_name) == 0)) if ((f.helo_verified = strcmpic(*aliases++, sender_helo_name) == 0))
{ {
sender_helo_dnssec = sender_host_dnssec; sender_helo_dnssec = sender_host_dnssec;
break; break;
} }
HDEBUG(D_receive) if (helo_verified) HDEBUG(D_receive) if (f.helo_verified)
debug_printf("matched alias %s\n", *(--aliases)); debug_printf("matched alias %s\n", *(--aliases));
} }
/* Final attempt: try a forward lookup of the helo name */ /* Final attempt: try a forward lookup of the helo name */
if (!helo_verified) if (!f.helo_verified)
{ {
int rc; int rc;
host_item h; host_item h;
dnssec_domains d; dnssec_domains d;
host_item *hh; host_item *hh;
h.name = sender_helo_name; h.name = sender_helo_name;
h.address = NULL; h.address = NULL;
h.mx = MX_NONE; h.mx = MX_NONE;
h.next = NULL; h.next = NULL;
skipping to change at line 3448 skipping to change at line 3556
d.require = US""; d.require = US"";
HDEBUG(D_receive) debug_printf("getting IP address for %s\n", HDEBUG(D_receive) debug_printf("getting IP address for %s\n",
sender_helo_name); sender_helo_name);
rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A | HOST_FIND_BY_AAAA, rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A | HOST_FIND_BY_AAAA,
NULL, NULL, NULL, &d, NULL, NULL); NULL, NULL, NULL, &d, NULL, NULL);
if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL) if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL)
for (hh = &h; hh; hh = hh->next) for (hh = &h; hh; hh = hh->next)
if (Ustrcmp(hh->address, sender_host_address) == 0) if (Ustrcmp(hh->address, sender_host_address) == 0)
{ {
helo_verified = TRUE; f.helo_verified = TRUE;
if (h.dnssec == DS_YES) sender_helo_dnssec = TRUE; if (h.dnssec == DS_YES) sender_helo_dnssec = TRUE;
HDEBUG(D_receive) HDEBUG(D_receive)
{ {
debug_printf("IP address for %s matches calling address\n" debug_printf("IP address for %s matches calling address\n"
"Forward DNS security status: %sverified\n", "Forward DNS security status: %sverified\n",
sender_helo_name, sender_helo_dnssec ? "" : "un"); sender_helo_name, sender_helo_dnssec ? "" : "un");
} }
break; break;
} }
} }
} }
if (!helo_verified) helo_verify_failed = TRUE; /* We've tried ... */ if (!f.helo_verified) f.helo_verify_failed = TRUE; /* We've tried ... */
return yield; return yield;
} }
/************************************************* /*************************************************
* Send user response message * * Send user response message *
*************************************************/ *************************************************/
/* This function is passed a default response code and a user message. It calls /* 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 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 calls smtp_respond() to transmit the response. I put this into a function
skipping to change at line 3546 skipping to change at line 3654
if (!au->set_id || set_id) /* Complete success */ if (!au->set_id || set_id) /* Complete success */
{ {
if (set_id) authenticated_id = string_copy_malloc(set_id); if (set_id) authenticated_id = string_copy_malloc(set_id);
sender_host_authenticated = au->name; sender_host_authenticated = au->name;
sender_host_auth_pubname = au->public_name; sender_host_auth_pubname = au->public_name;
authentication_failed = FALSE; authentication_failed = FALSE;
authenticated_fail_id = NULL; /* Impossible to already be set? */ authenticated_fail_id = NULL; /* Impossible to already be set? */
received_protocol = received_protocol =
(sender_host_address ? protocols : protocols_local) (sender_host_address ? protocols : protocols_local)
[pextend + pauthed + (tls_in.active >= 0 ? pcrpted:0)]; [pextend + pauthed + (tls_in.active.sock >= 0 ? pcrpted:0)];
*s = *ss = US"235 Authentication succeeded"; *s = *ss = US"235 Authentication succeeded";
authenticated_by = au; authenticated_by = au;
break; break;
} }
/* Authentication succeeded, but we failed to expand the set_id string. /* Authentication succeeded, but we failed to expand the set_id string.
Treat this as a temporary error. */ Treat this as a temporary error. */
auth_defer_msg = expand_string_message; auth_defer_msg = expand_string_message;
/* Fall through */ /* Fall through */
skipping to change at line 3599 skipping to change at line 3707
break; break;
} }
return rc; return rc;
} }
static int static int
qualify_recipient(uschar ** recipient, uschar * smtp_cmd_data, uschar * tag) qualify_recipient(uschar ** recipient, uschar * smtp_cmd_data, uschar * tag)
{ {
int rd; int rd;
if (allow_unqualified_recipient || strcmpic(*recipient, US"postmaster") == 0) if (f.allow_unqualified_recipient || strcmpic(*recipient, US"postmaster") == 0)
{ {
DEBUG(D_receive) debug_printf("unqualified address %s accepted\n", DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
*recipient); *recipient);
rd = Ustrlen(recipient) + 1; rd = Ustrlen(recipient) + 1;
*recipient = rewrite_address_qualify(*recipient, TRUE); *recipient = rewrite_address_qualify(*recipient, TRUE);
return rd; return rd;
} }
smtp_printf("501 %s: recipient address must contain a domain\r\n", FALSE, smtp_printf("501 %s: recipient address must contain a domain\r\n", FALSE,
smtp_cmd_data); smtp_cmd_data);
log_write(L_smtp_syntax_error, log_write(L_smtp_syntax_error,
skipping to change at line 3633 skipping to change at line 3741
if (rc == ERROR) if (rc == ERROR)
log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s", log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
*log_msgp); *log_msgp);
} }
if (*user_msgp) if (*user_msgp)
smtp_respond(US"221", 3, TRUE, *user_msgp); smtp_respond(US"221", 3, TRUE, *user_msgp);
else else
smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname); smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
#ifdef SUPPORT_TLS #ifdef SUPPORT_TLS
tls_close(TRUE, TLS_SHUTDOWN_NOWAIT); tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
#endif #endif
log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT", log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
smtp_get_connection_info()); smtp_get_connection_info());
} }
static void static void
smtp_rset_handler(void) smtp_rset_handler(void)
{ {
HAD(SCH_RSET); HAD(SCH_RSET);
skipping to change at line 3695 skipping to change at line 3803
/* Reset for start of new message. We allow one RSET not to be counted as a /* Reset for start of new message. We allow one RSET not to be counted as a
nonmail command, for those MTAs that insist on sending it between every nonmail command, for those MTAs that insist on sending it between every
message. Ditto for EHLO/HELO and for STARTTLS, to allow for going in and out of message. Ditto for EHLO/HELO and for STARTTLS, to allow for going in and out of
TLS between messages (an Exim client may do this if it has messages queued up TLS between messages (an Exim client may do this if it has messages queued up
for the host). Note: we do NOT reset AUTH at this point. */ for the host). Note: we do NOT reset AUTH at this point. */
smtp_reset(reset_point); smtp_reset(reset_point);
message_ended = END_NOTSTARTED; message_ended = END_NOTSTARTED;
chunking_state = chunking_offered ? CHUNKING_OFFERED : CHUNKING_NOT_OFFERED; chunking_state = f.chunking_offered ? CHUNKING_OFFERED : CHUNKING_NOT_OFFERED;
cmd_list[CMD_LIST_RSET].is_mail_cmd = TRUE; cmd_list[CMD_LIST_RSET].is_mail_cmd = TRUE;
cmd_list[CMD_LIST_HELO].is_mail_cmd = TRUE; cmd_list[CMD_LIST_HELO].is_mail_cmd = TRUE;
cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE; cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
#ifdef SUPPORT_TLS #ifdef SUPPORT_TLS
cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = TRUE; cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = TRUE;
#endif #endif
/* Set the local signal handler for SIGTERM - it tries to end off tidily */ /* Set the local signal handler for SIGTERM - it tries to end off tidily */
had_command_sigterm = 0;
os_non_restarting_signal(SIGTERM, command_sigterm_handler); os_non_restarting_signal(SIGTERM, command_sigterm_handler);
/* Batched SMTP is handled in a different function. */ /* Batched SMTP is handled in a different function. */
if (smtp_batched_input) return smtp_setup_batch_msg(); if (smtp_batched_input) return smtp_setup_batch_msg();
/* Deal with SMTP commands. This loop is exited by setting done to a POSITIVE /* Deal with SMTP commands. This loop is exited by setting done to a POSITIVE
value. The values are 2 larger than the required yield of the function. */ value. The values are 2 larger than the required yield of the function. */
while (done <= 0) while (done <= 0)
skipping to change at line 3735 skipping to change at line 3844
uschar *s, *ss; uschar *s, *ss;
BOOL was_rej_mail = FALSE; BOOL was_rej_mail = FALSE;
BOOL was_rcpt = FALSE; BOOL was_rcpt = FALSE;
void (*oldsignal)(int); void (*oldsignal)(int);
pid_t pid; pid_t pid;
int start, end, sender_domain, recipient_domain; int start, end, sender_domain, recipient_domain;
int rc; int rc;
int c; int c;
auth_instance *au; auth_instance *au;
uschar *orcpt = NULL; uschar *orcpt = NULL;
int flags; int dsn_flags;
gstring * g; gstring * g;
#ifdef AUTH_TLS #ifdef AUTH_TLS
/* Check once per STARTTLS or SSL-on-connect for a TLS AUTH */ /* Check once per STARTTLS or SSL-on-connect for a TLS AUTH */
if ( tls_in.active >= 0 if ( tls_in.active.sock >= 0
&& tls_in.peercert && tls_in.peercert
&& tls_in.certificate_verified && tls_in.certificate_verified
&& cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd && cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd
) )
{ {
cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = FALSE; cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = FALSE;
for (au = auths; au; au = au->next) for (au = auths; au; au = au->next)
if (strcmpic(US"tls", au->driver_name) == 0) if (strcmpic(US"tls", au->driver_name) == 0)
{ {
skipping to change at line 3776 skipping to change at line 3885
} }
} }
#endif #endif
#ifdef TCP_QUICKACK #ifdef TCP_QUICKACK
if (smtp_in) /* Avoid pure-ACKs while in cmd pingpong phase */ if (smtp_in) /* Avoid pure-ACKs while in cmd pingpong phase */
(void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK, (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK,
US &off, sizeof(off)); US &off, sizeof(off));
#endif #endif
switch(smtp_read_command(TRUE, GETC_BUFFER_UNLIMITED)) switch(smtp_read_command(
#ifdef EXPERIMENTAL_PIPE_CONNECT
!fl.pipe_connect_acceptable,
#else
TRUE,
#endif
GETC_BUFFER_UNLIMITED))
{ {
/* The AUTH command is not permitted to occur inside a transaction, and may /* The AUTH command is not permitted to occur inside a transaction, and may
occur successfully only once per connection. Actually, that isn't quite occur successfully only once per connection. Actually, that isn't quite
true. When TLS is started, all previous information about a connection must true. When TLS is started, all previous information about a connection must
be discarded, so a new AUTH is permitted at that time. 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 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 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. 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. So there's a get-out that allows this to happen.
AUTH is initially labelled as a "nonmail command" so that one occurrence 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 doesn't get counted. We change the label here so that multiple failing
AUTHS will eventually hit the nonmail threshold. */ AUTHS will eventually hit the nonmail threshold. */
case AUTH_CMD: case AUTH_CMD:
HAD(SCH_AUTH); HAD(SCH_AUTH);
authentication_failed = TRUE; authentication_failed = TRUE;
cmd_list[CMD_LIST_AUTH].is_mail_cmd = FALSE; cmd_list[CMD_LIST_AUTH].is_mail_cmd = FALSE;
if (!auth_advertised && !allow_auth_unadvertised)
{
done = synprot_error(L_smtp_protocol_error, 503, NULL,
US"AUTH command used when not advertised");
break;
}
if (sender_host_authenticated)
{
done = synprot_error(L_smtp_protocol_error, 503, NULL,
US"already authenticated");
break;
}
if (sender_address)
{
done = synprot_error(L_smtp_protocol_error, 503, NULL,
US"not permitted in mail transaction");
break;
}
/* Check the ACL */ if (!fl.auth_advertised && !f.allow_auth_unadvertised)
{
done = synprot_error(L_smtp_protocol_error, 503, NULL,
US"AUTH command used when not advertised");
break;
}
if (sender_host_authenticated)
{
done = synprot_error(L_smtp_protocol_error, 503, NULL,
US"already authenticated");
break;
}
if (sender_address)
{
done = synprot_error(L_smtp_protocol_error, 503, NULL,
US"not permitted in mail transaction");
break;
}
if ( acl_smtp_auth /* Check the ACL */
&& (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
&user_msg, &log_msg)) != OK
)
{
done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
break;
}
/* Find the name of the requested authentication mechanism. */ if ( acl_smtp_auth
&& (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
&user_msg, &log_msg)) != OK
)
{
done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
break;
}
s = smtp_cmd_data; /* Find the name of the requested authentication mechanism. */
while ((c = *smtp_cmd_data) != 0 && !isspace(c))
{
if (!isalnum(c) && c != '-' && c != '_')
{
done = synprot_error(L_smtp_syntax_error, 501, NULL,
US"invalid character in authentication mechanism name");
goto COMMAND_LOOP;
}
smtp_cmd_data++;
}
/* If not at the end of the line, we must be at white space. Terminate the s = smtp_cmd_data;
name and move the pointer on to any data that may be present. */ while ((c = *smtp_cmd_data) != 0 && !isspace(c))
{
if (!isalnum(c) && c != '-' && c != '_')
{
done = synprot_error(L_smtp_syntax_error, 501, NULL,
US"invalid character in authentication mechanism name");
goto COMMAND_LOOP;
}
smtp_cmd_data++;
}
if (*smtp_cmd_data != 0) /* 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. */
*smtp_cmd_data++ = 0;
while (isspace(*smtp_cmd_data)) smtp_cmd_data++;
}
/* Search for an authentication mechanism which is configured for use if (*smtp_cmd_data != 0)
as a server and which has been advertised (unless, sigh, allow_auth_ {
unadvertised is set). */ *smtp_cmd_data++ = 0;
while (isspace(*smtp_cmd_data)) smtp_cmd_data++;
}
for (au = auths; au; au = au->next) /* Search for an authentication mechanism which is configured for use
if (strcmpic(s, au->public_name) == 0 && au->server && as a server and which has been advertised (unless, sigh, allow_auth_
(au->advertised || allow_auth_unadvertised)) unadvertised is set). */
break;
for (au = auths; au; au = au->next)
if (strcmpic(s, au->public_name) == 0 && au->server &&
(au->advertised || f.allow_auth_unadvertised))
break;
if (au) if (au)
{ {
c = smtp_in_auth(au, &s, &ss); c = smtp_in_auth(au, &s, &ss);
smtp_printf("%s\r\n", FALSE, s); smtp_printf("%s\r\n", FALSE, s);
if (c != OK) if (c != OK)
log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s", log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
au->name, host_and_ident(FALSE), ss); au->name, host_and_ident(FALSE), ss);
} }
else else
done = synprot_error(L_smtp_protocol_error, 504, NULL, done = synprot_error(L_smtp_protocol_error, 504, NULL,
string_sprintf("%s authentication mechanism not supported", s)); string_sprintf("%s authentication mechanism not supported", s));
break; /* AUTH_CMD */ break; /* AUTH_CMD */
/* The HELO/EHLO commands are permitted to appear in the middle of a /* The HELO/EHLO commands are permitted to appear in the middle of a
session as well as at the beginning. They have the effect of a reset in session as well as at the beginning. They have the effect of a reset in
addition to their other functions. Their absence at the start cannot be addition to their other functions. Their absence at the start cannot be
taken to be an error. taken to be an error.
RFC 2821 says: RFC 2821 says:
If the EHLO command is not acceptable to the SMTP server, 501, 500, If the EHLO command is not acceptable to the SMTP server, 501, 500,
or 502 failure replies MUST be returned as appropriate. The SMTP or 502 failure replies MUST be returned as appropriate. The SMTP
server MUST stay in the same state after transmitting these replies server MUST stay in the same state after transmitting these replies
that it was in before the EHLO was received. that it was in before the EHLO was received.
Therefore, we do not do the reset until after checking the command for Therefore, we do not do the reset until after checking the command for
acceptability. This change was made for Exim release 4.11. Previously acceptability. This change was made for Exim release 4.11. Previously
it did the reset first. */ it did the reset first. */
case HELO_CMD: case HELO_CMD:
HAD(SCH_HELO); HAD(SCH_HELO);
hello = US"HELO"; hello = US"HELO";
esmtp = FALSE; fl.esmtp = FALSE;
goto HELO_EHLO; goto HELO_EHLO;
case EHLO_CMD: case EHLO_CMD:
HAD(SCH_EHLO); HAD(SCH_EHLO);
hello = US"EHLO"; hello = US"EHLO";
esmtp = TRUE; fl.esmtp = TRUE;
HELO_EHLO: /* Common code for HELO and EHLO */ HELO_EHLO: /* Common code for HELO and EHLO */
cmd_list[CMD_LIST_HELO].is_mail_cmd = FALSE; cmd_list[CMD_LIST_HELO].is_mail_cmd = FALSE;
cmd_list[CMD_LIST_EHLO].is_mail_cmd = FALSE; cmd_list[CMD_LIST_EHLO].is_mail_cmd = FALSE;
/* Reject the HELO if its argument was invalid or non-existent. A /* Reject the HELO if its argument was invalid or non-existent. A
successful check causes the argument to be saved in malloc store. */ successful check causes the argument to be saved in malloc store. */
if (!check_helo(smtp_cmd_data)) if (!check_helo(smtp_cmd_data))
{ {
smtp_printf("501 Syntactically invalid %s argument(s)\r\n", FALSE, hello); smtp_printf("501 Syntactically invalid %s argument(s)\r\n", FALSE, hello)
;
log_write(0, LOG_MAIN|LOG_REJECT, "rejected %s from %s: syntactically " log_write(0, LOG_MAIN|LOG_REJECT, "rejected %s from %s: syntactically "
"invalid argument(s): %s", hello, host_and_ident(FALSE), "invalid argument(s): %s", hello, host_and_ident(FALSE),
(*smtp_cmd_argument == 0)? US"(no argument given)" : *smtp_cmd_argument == 0 ? US"(no argument given)" :
string_printing(smtp_cmd_argument)); string_printing(smtp_cmd_argument));
if (++synprot_error_count > smtp_max_synprot_errors) if (++synprot_error_count > smtp_max_synprot_errors)
{ {
log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many " log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many
"syntax or protocol errors (last command was \"%s\")", "
host_and_ident(FALSE), string_printing(smtp_cmd_buffer)); "syntax or protocol errors (last command was \"%s\")",
done = 1; host_and_ident(FALSE), string_printing(smtp_cmd_buffer));
} done = 1;
}
break; break;
} }
/* If sender_host_unknown is true, we have got here via the -bs interface, /* If sender_host_unknown is true, we have got here via the -bs interface,
not called from inetd. Otherwise, we are running an IP connection and the not called from inetd. Otherwise, we are running an IP connection and the
host address will be set. If the helo name is the primary name of this host address will be set. If the helo name is the primary name of this
host and we haven't done a reverse lookup, force one now. If helo_required host and we haven't done a reverse lookup, force one now. If helo_required
is set, ensure that the HELO name matches the actual host. If helo_verify is set, ensure that the HELO name matches the actual host. If helo_verify
is set, do the same check, but softly. */ is set, do the same check, but softly. */
if (!sender_host_unknown) if (!f.sender_host_unknown)
{ {
BOOL old_helo_verified = helo_verified; BOOL old_helo_verified = f.helo_verified;
uschar *p = smtp_cmd_data; uschar *p = smtp_cmd_data;
while (*p != 0 && !isspace(*p)) { *p = tolower(*p); p++; } while (*p != 0 && !isspace(*p)) { *p = tolower(*p); p++; }
*p = 0; *p = 0;
/* Force a reverse lookup if HELO quoted something in helo_lookup_domains /* Force a reverse lookup if HELO quoted something in helo_lookup_domains
because otherwise the log can be confusing. */ because otherwise the log can be confusing. */
if ( !sender_host_name if ( !sender_host_name
&& (deliver_domain = sender_helo_name, /* set $domain */ && match_isinlist(sender_helo_name, CUSS &helo_lookup_domains, 0,
match_isinlist(sender_helo_name, CUSS &helo_lookup_domains, 0, &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK)
&domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL)) == OK) (void)host_name_lookup();
(void)host_name_lookup();
/* Rebuild the fullhost info to include the HELO name (and the real name
/* Rebuild the fullhost info to include the HELO name (and the real name if it was looked up.) */
if it was looked up.) */
host_build_sender_fullhost(); /* Rebuild */
host_build_sender_fullhost(); /* Rebuild */ set_process_info("handling%s incoming connection from %s",
set_process_info("handling%s incoming connection from %s", tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE));
(tls_in.active >= 0)? " TLS" : "", host_and_ident(FALSE));
/* Verify if configured. This doesn't give much security, but it does
/* Verify if configured. This doesn't give much security, but it does make some people happy to be able to do it. If helo_required is set,
make some people happy to be able to do it. If helo_required is set, (host matches helo_verify_hosts) failure forces rejection. If helo_verify
(host matches helo_verify_hosts) failure forces rejection. If helo_verify is set (host matches helo_try_verify_hosts), it does not. This is perhaps
is set (host matches helo_try_verify_hosts), it does not. This is perhaps now obsolescent, since the verification can now be requested selectively
now obsolescent, since the verification can now be requested selectively at ACL time. */
at ACL time. */
helo_verified = helo_verify_failed = sender_helo_dnssec = FALSE; f.helo_verified = f.helo_verify_failed = sender_helo_dnssec = FALSE;
if (helo_required || helo_verify) if (fl.helo_required || fl.helo_verify)
{ {
BOOL tempfail = !smtp_verify_helo(); BOOL tempfail = !smtp_verify_helo();
if (!helo_verified) if (!f.helo_verified)
{ {
if (helo_required) if (fl.helo_required)
{ {
smtp_printf("%d %s argument does not match calling host\r\n", FALSE, smtp_printf("%d %s argument does not match calling host\r\n", FALSE
tempfail? 451 : 550, hello); ,
log_write(0, LOG_MAIN|LOG_REJECT, "%srejected \"%s %s\" from %s", tempfail? 451 : 550, hello);
tempfail? "temporarily " : "", log_write(0, LOG_MAIN|LOG_REJECT, "%srejected \"%s %s\" from %s",
hello, sender_helo_name, host_and_ident(FALSE)); tempfail? "temporarily " : "",
helo_verified = old_helo_verified; hello, sender_helo_name, host_and_ident(FALSE));
break; /* End of HELO/EHLO processing */ f.helo_verified = old_helo_verified;
} break; /* End of HELO/EHLO processing */
HDEBUG(D_all) debug_printf("%s verification failed but host is in " }
"helo_try_verify_hosts\n", hello); HDEBUG(D_all) debug_printf("%s verification failed but host is in "
} "helo_try_verify_hosts\n", hello);
} }
} }
}
#ifdef SUPPORT_SPF #ifdef SUPPORT_SPF
/* set up SPF context */ /* set up SPF context */
spf_init(sender_helo_name, sender_host_address); spf_init(sender_helo_name, sender_host_address);
#endif #endif
/* Apply an ACL check if one is defined; afterwards, recheck /* Apply an ACL check if one is defined; afterwards, recheck
synchronization in case the client started sending in a delay. */ synchronization in case the client started sending in a delay. */
if (acl_smtp_helo) if (acl_smtp_helo)
if ((rc = acl_check(ACL_WHERE_HELO, NULL, acl_smtp_helo, if ((rc = acl_check(ACL_WHERE_HELO, NULL, acl_smtp_helo,
&user_msg, &log_msg)) != OK) &user_msg, &log_msg)) != OK)
{
done = smtp_handle_acl_fail(ACL_WHERE_HELO, rc, user_msg, log_msg);
if (sender_helo_name)
{ {
store_free(sender_helo_name); done = smtp_handle_acl_fail(ACL_WHERE_HELO, rc, user_msg, log_msg);
sender_helo_name = NULL; if (sender_helo_name)
{
store_free(sender_helo_name);
sender_helo_name = NULL;
}
host_build_sender_fullhost(); /* Rebuild */
break;
} }
host_build_sender_fullhost(); /* Rebuild */ #ifdef EXPERIMENTAL_PIPE_CONNECT
break; else if (!fl.pipe_connect_acceptable && !check_sync())
} #else
else if (!check_sync()) goto SYNC_FAILURE; else if (!check_sync())
#endif
goto SYNC_FAILURE;
/* Generate an OK reply. The default string includes the ident if present, /* 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 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 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 some broken systems expect each response to be in a single packet, arrange
that the entire reply is sent in one write(). */ that the entire reply is sent in one write(). */
auth_advertised = FALSE; fl.auth_advertised = FALSE;
pipelining_advertised = FALSE; f.smtp_in_pipelining_advertised = FALSE;
#ifdef SUPPORT_TLS #ifdef SUPPORT_TLS
tls_advertised = FALSE; fl.tls_advertised = FALSE;
# ifdef EXPERIMENTAL_REQUIRETLS
fl.requiretls_advertised = FALSE;
# endif
#endif #endif
dsn_advertised = FALSE; fl.dsn_advertised = FALSE;
#ifdef SUPPORT_I18N #ifdef SUPPORT_I18N
smtputf8_advertised = FALSE; fl.smtputf8_advertised = FALSE;
#endif #endif
smtp_code = US"250 "; /* Default response code plus space*/ smtp_code = US"250 "; /* Default response code plus space*/
if (!user_msg) if (!user_msg)
{ {
s = string_sprintf("%.3s %s Hello %s%s%s", g = string_fmt_append(NULL, "%.3s %s Hello %s%s%s",
smtp_code, smtp_code,
smtp_active_hostname, smtp_active_hostname,
sender_ident ? sender_ident : US"", sender_ident ? sender_ident : US"",
sender_ident ? US" at " : US"", sender_ident ? US" at " : US"",
sender_host_name ? sender_host_name : sender_helo_name); sender_host_name ? sender_host_name : sender_helo_name);
g = string_cat(NULL, s);
if (sender_host_address) if (sender_host_address)
{ g = string_fmt_append(g, " [%s]", sender_host_address);
g = string_catn(g, US" [", 2); }
g = string_cat (g, sender_host_address);
g = string_catn(g, US"]", 1);
}
}
/* A user-supplied EHLO greeting may not contain more than one line. Note /* A user-supplied EHLO greeting may not contain more than one line. Note
that the code returned by smtp_message_code() includes the terminating that the code returned by smtp_message_code() includes the terminating
whitespace character. */ whitespace character. */
else else
{ {
char *ss; char *ss;
int codelen = 4; int codelen = 4;
smtp_message_code(&smtp_code, &codelen, &user_msg, NULL, TRUE); smtp_message_code(&smtp_code, &codelen, &user_msg, NULL, TRUE);
s = string_sprintf("%.*s%s", codelen, smtp_code, user_msg); s = string_sprintf("%.*s%s", codelen, smtp_code, user_msg);
if ((ss = strpbrk(CS s, "\r\n")) != NULL) if ((ss = strpbrk(CS s, "\r\n")) != NULL)
{ {
log_write(0, LOG_MAIN|LOG_PANIC, "EHLO/HELO response must not contain " log_write(0, LOG_MAIN|LOG_PANIC, "EHLO/HELO response must not contain "
"newlines: message truncated: %s", string_printing(s)); "newlines: message truncated: %s", string_printing(s));
*ss = 0; *ss = 0;
} }
g = string_cat(NULL, s); g = string_cat(NULL, s);
} }
g = string_catn(g, US"\r\n", 2); g = string_catn(g, US"\r\n", 2);
/* If we received EHLO, we must create a multiline response which includes /* If we received EHLO, we must create a multiline response which includes
the functions supported. */ the functions supported. */
if (esmtp) if (fl.esmtp)
{ {
g->s[3] = '-'; g->s[3] = '-';
/* I'm not entirely happy with this, as an MTA is supposed to check /* I'm not entirely happy with this, as an MTA is supposed to check
that it has enough room to accept a message of maximum size before that it has enough room to accept a message of maximum size before
it sends this. However, there seems little point in not sending it. it sends this. However, there seems little point in not sending it.
The actual size check happens later at MAIL FROM time. By postponing it The actual size check happens later at MAIL FROM time. By postponing it
till then, VRFY and EXPN can be used after EHLO when space is short. */ till then, VRFY and EXPN can be used after EHLO when space is short. */
if (thismessage_size_limit > 0)
g = string_fmt_append(g, "%.3s-SIZE %d\r\n", smtp_code,
thismessage_size_limit);
else
{
g = string_catn(g, smtp_code, 3);
g = string_catn(g, US"-SIZE\r\n", 7);
}
if (thismessage_size_limit > 0) /* Exim does not do protocol conversion or data conversion. It is 8-bit
{ clean; if it has an 8-bit character in its hand, it just sends it. It
sprintf(CS big_buffer, "%.3s-SIZE %d\r\n", smtp_code, cannot therefore specify 8BITMIME and remain consistent with the RFCs.
thismessage_size_limit); However, some users want this option simply in order to stop MUAs
g = string_cat(g, big_buffer); mangling messages that contain top-bit-set characters. It is therefore
} provided as an option. */
else
{
g = string_catn(g, smtp_code, 3);
g = string_catn(g, US"-SIZE\r\n", 7);
}
/* Exim does not do protocol conversion or data conversion. It is 8-bit if (accept_8bitmime)
clean; if it has an 8-bit character in its hand, it just sends it. It {
cannot therefore specify 8BITMIME and remain consistent with the RFCs. g = string_catn(g, smtp_code, 3);
However, some users want this option simply in order to stop MUAs g = string_catn(g, US"-8BITMIME\r\n", 11);
mangling messages that contain top-bit-set characters. It is therefore }
provided as an option. */
if (accept_8bitmime) /* Advertise DSN support if configured to do so. */
{ if (verify_check_host(&dsn_advertise_hosts) != FAIL)
g = string_catn(g, smtp_code, 3); {
g = string_catn(g, US"-8BITMIME\r\n", 11); g = string_catn(g, smtp_code, 3);
} g = string_catn(g, US"-DSN\r\n", 6);
fl.dsn_advertised = TRUE;
}
/* Advertise DSN support if configured to do so. */ /* Advertise ETRN/VRFY/EXPN if there's are ACL checking whether a host is
if (verify_check_host(&dsn_advertise_hosts) != FAIL) permitted to issue them; a check is made when any host actually tries. */
{
g = string_catn(g, smtp_code, 3);
g = string_catn(g, US"-DSN\r\n", 6);
dsn_advertised = TRUE;
}
/* Advertise ETRN/VRFY/EXPN if there's are ACL checking whether a host is if (acl_smtp_etrn)
permitted to issue them; a check is made when any host actually tries. */ {
g = string_catn(g, smtp_code, 3);
g = string_catn(g, US"-ETRN\r\n", 7);
}
if (acl_smtp_vrfy)
{
g = string_catn(g, smtp_code, 3);
g = string_catn(g, US"-VRFY\r\n", 7);
}
if (acl_smtp_expn)
{
g = string_catn(g, smtp_code, 3);
g = string_catn(g, US"-EXPN\r\n", 7);
}
if (acl_smtp_etrn) /* Exim is quite happy with pipelining, so let the other end know that
{ it is safe to use it, unless advertising is disabled. */
g = string_catn(g, smtp_code, 3);
g = string_catn(g, US"-ETRN\r\n", 7);
}
if (acl_smtp_vrfy)
{
g = string_catn(g, smtp_code, 3);
g = string_catn(g, US"-VRFY\r\n", 7);
}
if (acl_smtp_expn)
{
g = string_catn(g, smtp_code, 3);
g = string_catn(g, US"-EXPN\r\n", 7);
}
/* Exim is quite happy with pipelining, so let the other end know that if ( f.pipelining_enable
it is safe to use it, unless advertising is disabled. */ && verify_check_host(&pipelining_advertise_hosts) == OK)
{
g = string_catn(g, smtp_code, 3);
g = string_catn(g, US"-PIPELINING\r\n", 13);
sync_cmd_limit = NON_SYNC_CMD_PIPELINING;
f.smtp_in_pipelining_advertised = TRUE;
if (pipelining_enable && #ifdef EXPERIMENTAL_PIPE_CONNECT
verify_check_host(&pipelining_advertise_hosts) == OK) if (fl.pipe_connect_acceptable)
{ {
g = string_catn(g, smtp_code, 3); f.smtp_in_early_pipe_advertised = TRUE;
g = string_catn(g, US"-PIPELINING\r\n", 13); g = string_catn(g, smtp_code, 3);
sync_cmd_limit = NON_SYNC_CMD_PIPELINING; g = string_catn(g, US"-" EARLY_PIPE_FEATURE_NAME "\r\n", EARLY_PIPE_F
pipelining_advertised = TRUE; EATURE_LEN+3);
} }
#endif
}
/* If any server authentication mechanisms are configured, advertise /* If any server authentication mechanisms are configured, advertise
them if the current host is in auth_advertise_hosts. The problem with them if the current host is in auth_advertise_hosts. The problem with
advertising always is that some clients then require users to advertising always is that some clients then require users to
authenticate (and aren't configurable otherwise) even though it may not authenticate (and aren't configurable otherwise) even though it may not
be necessary (e.g. if the host is in host_accept_relay). be necessary (e.g. if the host is in host_accept_relay).
RFC 2222 states that SASL mechanism names contain only upper case RFC 2222 states that SASL mechanism names contain only upper case
letters, so output the names in upper case, though we actually recognize letters, so output the names in upper case, though we actually recognize
them in either case in the AUTH command. */ them in either case in the AUTH command. */
if ( auths if ( auths
#ifdef AUTH_TLS #ifdef AUTH_TLS
&& !sender_host_authenticated && !sender_host_authenticated
#endif #endif
&& verify_check_host(&auth_advertise_hosts) == OK && verify_check_host(&auth_advertise_hosts) == OK
) )
{
auth_instance *au;
BOOL first = TRUE;
for (au = auths; au; au = au->next)
{ {
au->advertised = FALSE; auth_instance *au;
if (au->server) BOOL first = TRUE;
for (au = auths; au; au = au->next)
{ {
DEBUG(D_auth+D_expand) debug_printf_indent( au->advertised = FALSE;
"Evaluating advertise_condition for %s athenticator\n", if (au->server)
au->public_name);
if ( !au->advertise_condition
|| expand_check_condition(au->advertise_condition, au->name,
US"authenticator")
)
{ {
int saveptr; DEBUG(D_auth+D_expand) debug_printf_indent(
if (first) "Evaluating advertise_condition for %s athenticator\n",
au->public_name);
if ( !au->advertise_condition
|| expand_check_condition(au->advertise_condition, au->name,
US"authenticator")
)
{ {
g = string_catn(g, smtp_code, 3); int saveptr;
g = string_catn(g, US"-AUTH", 5); if (first)
first = FALSE; {
auth_advertised = TRUE; g = string_catn(g, smtp_code, 3);
g = string_catn(g, US"-AUTH", 5);
first = FALSE;
fl.auth_advertised = TRUE;
}
saveptr = g->ptr;
g = string_catn(g, US" ", 1);
g = string_cat (g, au->public_name);
while (++saveptr < g->ptr) g->s[saveptr] = toupper(g->s[saveptr])
;
au->advertised = TRUE;
} }
saveptr = g->ptr;
g = string_catn(g, US" ", 1);
g = string_cat (g, au->public_name);
while (++saveptr < g->ptr) g->s[saveptr] = toupper(g->s[saveptr]);
au->advertised = TRUE;
} }
} }
}
if (!first) g = string_catn(g, US"\r\n", 2); if (!first) g = string_catn(g, US"\r\n", 2);
} }
/* RFC 3030 CHUNKING */ /* RFC 3030 CHUNKING */
if (verify_check_host(&chunking_advertise_hosts) != FAIL) if (verify_check_host(&chunking_advertise_hosts) != FAIL)
{ {
g = string_catn(g, smtp_code, 3); g = string_catn(g, smtp_code, 3);
g = string_catn(g, US"-CHUNKING\r\n", 11); g = string_catn(g, US"-CHUNKING\r\n", 11);
chunking_offered = TRUE; f.chunking_offered = TRUE;
chunking_state = CHUNKING_OFFERED; chunking_state = CHUNKING_OFFERED;
} }
/* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer) /* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer)
if it has been included in the binary, and the host matches if it has been included in the binary, and the host matches
tls_advertise_hosts. We must *not* advertise if we are already in a tls_advertise_hosts. We must *not* advertise if we are already in a
secure connection. */ secure connection. */
#ifdef SUPPORT_TLS #ifdef SUPPORT_TLS
if (tls_in.active < 0 && if (tls_in.active.sock < 0 &&
verify_check_host(&tls_advertise_hosts) != FAIL) verify_check_host(&tls_advertise_hosts) != FAIL)
{ {
g = string_catn(g, smtp_code, 3); g = string_catn(g, smtp_code, 3);
g = string_catn(g, US"-STARTTLS\r\n", 11); g = string_catn(g, US"-STARTTLS\r\n", 11);
tls_advertised = TRUE; fl.tls_advertised = TRUE;
} }
# ifdef EXPERIMENTAL_REQUIRETLS
/* Advertise REQUIRETLS only once we are in a secure connection */
if ( tls_in.active.sock >= 0
&& verify_check_host(&tls_advertise_requiretls) != FAIL)
{
g = string_catn(g, smtp_code, 3);
g = string_catn(g, US"-REQUIRETLS\r\n", 13);
fl.requiretls_advertised = TRUE;
}
# endif
#endif #endif
#ifndef DISABLE_PRDR #ifndef DISABLE_PRDR
/* Per Recipient Data Response, draft by Eric A. Hall extending RFC */ /* Per Recipient Data Response, draft by Eric A. Hall extending RFC */
if (prdr_enable) if (prdr_enable)
{ {
g = string_catn(g, smtp_code, 3); g = string_catn(g, smtp_code, 3);
g = string_catn(g, US"-PRDR\r\n", 7); g = string_catn(g, US"-PRDR\r\n", 7);
} }
#endif #endif
#ifdef SUPPORT_I18N #ifdef SUPPORT_I18N
if ( accept_8bitmime if ( accept_8bitmime
&& verify_check_host(&smtputf8_advertise_hosts) != FAIL) && verify_check_host(&smtputf8_advertise_hosts) != FAIL)
{ {
g = string_catn(g, smtp_code, 3); g = string_catn(g, smtp_code, 3);
g = string_catn(g, US"-SMTPUTF8\r\n", 11); g = string_catn(g, US"-SMTPUTF8\r\n", 11);
smtputf8_advertised = TRUE; fl.smtputf8_advertised = TRUE;
} }
#endif #endif
/* Finish off the multiline reply with one that is always available. */ /* Finish off the multiline reply with one that is always available. */
g = string_catn(g, smtp_code, 3); g = string_catn(g, smtp_code, 3);
g = string_catn(g, US" HELP\r\n", 7); g = string_catn(g, US" HELP\r\n", 7);
} }
/* Terminate the string (for debug), write it, and note that HELO/EHLO /* Terminate the string (for debug), write it, and note that HELO/EHLO
has been seen. */ has been seen. */
#ifdef SUPPORT_TLS #ifdef SUPPORT_TLS
if (tls_in.active >= 0) (void)tls_write(TRUE, g->s, g->ptr, FALSE); else if (tls_in.active.sock >= 0)
(void)tls_write(NULL, g->s, g->ptr,
# ifdef EXPERIMENTAL_PIPE_CONNECT
fl.pipe_connect_acceptable && pipeline_connect_sends());
# else
FALSE);
# endif
else
#endif #endif
{ {
int i = fwrite(g->s, 1, g->ptr, smtp_out); i = i; /* compiler quietening * int i = fwrite(g->s, 1, g->ptr, smtp_out); i = i; /* compiler quietening
/ */
} }
DEBUG(D_receive) DEBUG(D_receive)
{ {
uschar *cr; uschar *cr;
(void) string_from_gstring(g); (void) string_from_gstring(g);
while ((cr = Ustrchr(g->s, '\r')) != NULL) /* lose CRs */ while ((cr = Ustrchr(g->s, '\r')) != NULL) /* lose CRs */
memmove(cr, cr + 1, (g->ptr--) - (cr - g->s)); memmove(cr, cr + 1, (g->ptr--) - (cr - g->s));
debug_printf("SMTP>> %s", g->s); debug_printf("SMTP>> %s", g->s);
} }
helo_seen = TRUE; fl.helo_seen = TRUE;
/* Reset the protocol and the state, abandoning any previous message. */ /* Reset the protocol and the state, abandoning any previous message. */
received_protocol = received_protocol =
(sender_host_address ? protocols : protocols_local) (sender_host_address ? protocols : protocols_local)
[ (esmtp [ (fl.esmtp
? pextend + (sender_host_authenticated ? pauthed : 0) ? pextend + (sender_host_authenticated ? pauthed : 0)
: pnormal) : pnormal)
+ (tls_in.active >= 0 ? pcrpted : 0) + (tls_in.active.sock >= 0 ? pcrpted : 0)
]; ];
cancel_cutthrough_connection(TRUE, US"sent EHLO response"); cancel_cutthrough_connection(TRUE, US"sent EHLO response");
smtp_reset(reset_point); smtp_reset(reset_point);
toomany = FALSE; toomany = FALSE;
break; /* HELO/EHLO */ break; /* HELO/EHLO */
/* The MAIL command requires an address as an operand. All we do /* The MAIL command requires an address as an operand. All we do
here is to parse it for syntactic correctness. The form "<>" is here is to parse it for syntactic correctness. The form "<>" is
a special case which converts into an empty string. The start/end a special case which converts into an empty string. The start/end
pointers in the original are not used further for this address, as pointers in the original are not used further for this address, as
it is the canonical extracted address which is all that is kept. */ it is the canonical extracted address which is all that is kept. */
case MAIL_CMD: case MAIL_CMD:
HAD(SCH_MAIL); HAD(SCH_MAIL);
smtp_mailcmd_count++; /* Count for limit and ratelimit */ smtp_mailcmd_count++; /* Count for limit and ratelimit */
was_rej_mail = TRUE; /* Reset if accepted */ was_rej_mail = TRUE; /* Reset if accepted */
env_mail_type_t * mail_args; /* Sanity check & validate args */ env_mail_type_t * mail_args; /* Sanity check & validate args */
if (helo_required && !helo_seen)
{
smtp_printf("503 HELO or EHLO required\r\n", FALSE);
log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no "
"HELO/EHLO given", host_and_ident(FALSE));
break;
}
if (sender_address != NULL) if (fl.helo_required && !fl.helo_seen)
{ {
done = synprot_error(L_smtp_protocol_error, 503, NULL, smtp_printf("503 HELO or EHLO required\r\n", FALSE);
US"sender already given"); log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no "
break; "HELO/EHLO given", host_and_ident(FALSE));
} break;
}
if (smtp_cmd_data[0] == 0) if (sender_address)
{ {
done = synprot_error(L_smtp_protocol_error, 501, NULL, done = synprot_error(L_smtp_protocol_error, 503, NULL,
US"MAIL must have an address operand"); US"sender already given");
break; break;
} }
/* Check to see if the limit for messages per connection would be if (!*smtp_cmd_data)
exceeded by accepting further messages. */ {
done = synprot_error(L_smtp_protocol_error, 501, NULL,
US"MAIL must have an address operand");
break;
}
if (smtp_accept_max_per_connection > 0 && /* Check to see if the limit for messages per connection would be
smtp_mailcmd_count > smtp_accept_max_per_connection) exceeded by accepting further messages. */
{
smtp_printf("421 too many messages in this connection\r\n", FALSE);
log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL command %s: too many "
"messages in one connection", host_and_ident(TRUE));
break;
}
/* Reset for start of message - even if this is going to fail, we if (smtp_accept_max_per_connection > 0 &&
obviously need to throw away any previous data. */ smtp_mailcmd_count > smtp_accept_max_per_connection)
{
smtp_printf("421 too many messages in this connection\r\n", FALSE);
log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL command %s: too many "
"messages in one connection", host_and_ident(TRUE));
break;
}
cancel_cutthrough_connection(TRUE, US"MAIL received"); /* Reset for start of message - even if this is going to fail, we
smtp_reset(reset_point); obviously need to throw away any previous data. */
toomany = FALSE;
sender_data = recipient_data = NULL;
/* Loop, checking for ESMTP additions to the MAIL FROM command. */ cancel_cutthrough_connection(TRUE, US"MAIL received");
smtp_reset(reset_point);
toomany = FALSE;
sender_data = recipient_data = NULL;
if (esmtp) for(;;) /* Loop, checking for ESMTP additions to the MAIL FROM command. */
{
uschar *name, *value, *end;
unsigned long int size;
BOOL arg_error = FALSE;
if (!extract_option(&name, &value)) break;
for (mail_args = env_mail_type_list;
mail_args->value != ENV_MAIL_OPT_NULL;
mail_args++
)
if (strcmpic(name, mail_args->name) == 0)
break;
if (mail_args->need_value && strcmpic(value, US"") == 0)
break;
switch(mail_args->value) if (fl.esmtp) for(;;)
{ {
/* Handle SIZE= by reading the value. We don't do the check till later, uschar *name, *value, *end;
in order to be able to log the sender address on failure. */ unsigned long int size;
case ENV_MAIL_OPT_SIZE: BOOL arg_error = FALSE;
if (((size = Ustrtoul(value, &end, 10)), *end == 0))
{ if (!extract_option(&name, &value)) break;
if ((size == ULONG_MAX && errno == ERANGE) || size > INT_MAX)
size = INT_MAX; for (mail_args = env_mail_type_list;
message_size = (int)size; mail_args->value != ENV_MAIL_OPT_NULL;
} mail_args++
else )
arg_error = TRUE; if (strcmpic(name, mail_args->name) == 0)
break; break;
if (mail_args->need_value && strcmpic(value, US"") == 0)
break;
/* If this session was initiated with EHLO and accept_8bitmime is set, switch(mail_args->value)
Exim will have indicated that it supports the BODY=8BITMIME option. In {
fact, it does not support this according to the RFCs, in that it does no /* Handle SIZE= by reading the value. We don't do the check till later,
t in order to be able to log the sender address on failure. */
take any special action for forwarding messages containing 8-bit case ENV_MAIL_OPT_SIZE:
characters. That is why accept_8bitmime is not the default setting, but if (((size = Ustrtoul(value, &end, 10)), *end == 0))
some sites want the action that is provided. We recognize both "8BITMIME
"
and "7BIT" as body types, but take no action. */
case ENV_MAIL_OPT_BODY:
if (accept_8bitmime) {
if (strcmpic(value, US"8BITMIME") == 0)
body_8bitmime = 8;
else if (strcmpic(value, US"7BIT") == 0)
body_8bitmime = 7;
else
{ {
body_8bitmime = 0; if ((size == ULONG_MAX && errno == ERANGE) || size > INT_MAX)
done = synprot_error(L_smtp_syntax_error, 501, NULL, size = INT_MAX;
US"invalid data for BODY"); message_size = (int)size;
goto COMMAND_LOOP; }
} else
DEBUG(D_receive) debug_printf("8BITMIME: %d\n", body_8bitmime); arg_error = TRUE;
break; break;
}
arg_error = TRUE;
break;
/* Handle the two DSN options, but only if configured to do so (which /* If this session was initiated with EHLO and accept_8bitmime is set,
will have caused "DSN" to be given in the EHLO response). The code itsel Exim will have indicated that it supports the BODY=8BITMIME option. In
f fact, it does not support this according to the RFCs, in that it does n
is included only if configured in at build time. */ ot
take any special action for forwarding messages containing 8-bit
characters. That is why accept_8bitmime is not the default setting, but
some sites want the action that is provided. We recognize both "8BITMIM
E"
and "7BIT" as body types, but take no action. */
case ENV_MAIL_OPT_BODY:
if (accept_8bitmime) {
if (strcmpic(value, US"8BITMIME") == 0)
body_8bitmime = 8;
else if (strcmpic(value, US"7BIT") == 0)
body_8bitmime = 7;
else
{
body_8bitmime = 0;
done = synprot_error(L_smtp_syntax_error, 501, NULL,
US"invalid data for BODY");
goto COMMAND_LOOP;
}
DEBUG(D_receive) debug_printf("8BITMIME: %d\n", body_8bitmime);
break;
}
arg_error = TRUE;
break;
case ENV_MAIL_OPT_RET: /* Handle the two DSN options, but only if configured to do so (which
if (dsn_advertised) will have caused "DSN" to be given in the EHLO response). The code itse
{ lf
/* Check if RET has already been set */ is included only if configured in at build time. */
if (dsn_ret > 0)
{ case ENV_MAIL_OPT_RET:
synprot_error(L_smtp_syntax_error, 501, NULL, if (fl.dsn_advertised)
US"RET can be specified once only");
goto COMMAND_LOOP;
}
dsn_ret = strcmpic(value, US"HDRS") == 0
? dsn_ret_hdrs
: strcmpic(value, US"FULL") == 0
? dsn_ret_full
: 0;
DEBUG(D_receive) debug_printf("DSN_RET: %d\n", dsn_ret);
/* Check for invalid invalid value, and exit with error */
if (dsn_ret == 0)
{ {
synprot_error(L_smtp_syntax_error, 501, NULL, /* Check if RET has already been set */
US"Value for RET is invalid"); if (dsn_ret > 0)
goto COMMAND_LOOP; {
done = synprot_error(L_smtp_syntax_error, 501, NULL,
US"RET can be specified once only");
goto COMMAND_LOOP;
}
dsn_ret = strcmpic(value, US"HDRS") == 0
? dsn_ret_hdrs
: strcmpic(value, US"FULL") == 0
? dsn_ret_full
: 0;
DEBUG(D_receive) debug_printf("DSN_RET: %d\n", dsn_ret);
/* Check for invalid invalid value, and exit with error */
if (dsn_ret == 0)
{
done = synprot_error(L_smtp_syntax_error, 501, NULL,
US"Value for RET is invalid");
goto COMMAND_LOOP;
}
} }
} break;
break; case ENV_MAIL_OPT_ENVID:
case ENV_MAIL_OPT_ENVID: if (fl.dsn_advertised)
if (dsn_advertised)
{
/* Check if the dsn envid has been already set */
if (dsn_envid != NULL)
{ {
synprot_error(L_smtp_syntax_error, 501, NULL, /* Check if the dsn envid has been already set */
US"ENVID can be specified once only"); if (dsn_envid)
goto COMMAND_LOOP; {
done = synprot_error(L_smtp_syntax_error, 501, NULL,
US"ENVID can be specified once only");
goto COMMAND_LOOP;
}
dsn_envid = string_copy(value);
DEBUG(D_receive) debug_printf("DSN_ENVID: %s\n", dsn_envid);
} }
dsn_envid = string_copy(value); break;
DEBUG(D_receive) debug_printf("DSN_ENVID: %s\n", dsn_envid);
}
break;
/* Handle the AUTH extension. If the value given is not "<>" and either
the ACL says "yes" or there is no ACL but the sending host is
authenticated, we set it up as the authenticated sender. However, if the
authenticator set a condition to be tested, we ignore AUTH on MAIL unles
s
the condition is met. The value of AUTH is an xtext, which means that +,
= and cntrl chars are coded in hex; however "<>" is unaffected by this
coding. */
case ENV_MAIL_OPT_AUTH:
if (Ustrcmp(value, "<>") != 0)
{
int rc;
uschar *ignore_msg;
if (auth_xtextdecode(value, &authenticated_sender) < 0)
{
/* Put back terminator overrides for error message */
value[-1] = '=';
name[-1] = ' ';
done = synprot_error(L_smtp_syntax_error, 501, NULL,
US"invalid data for AUTH");
goto COMMAND_LOOP;
}
if (!acl_smtp_mailauth)
{
ignore_msg = US"client not authenticated";
rc = sender_host_authenticated ? OK : FAIL;
}
else
{
ignore_msg = US"rejected by ACL";
rc = acl_check(ACL_WHERE_MAILAUTH, NULL, acl_smtp_mailauth,
&user_msg, &log_msg);
}
switch (rc)
{
case OK:
if (authenticated_by == NULL ||
authenticated_by->mail_auth_condition == NULL ||
expand_check_condition(authenticated_by->mail_auth_condition,
authenticated_by->name, US"authenticator"))
break; /* Accept the AUTH */
ignore_msg = US"server_mail_auth_condition failed";
if (authenticated_id != NULL)
ignore_msg = string_sprintf("%s: authenticated ID=\"%s\"",
ignore_msg, authenticated_id);
/* Fall through */
case FAIL:
authenticated_sender = NULL;
log_write(0, LOG_MAIN, "ignoring AUTH=%s from %s (%s)",
value, host_and_ident(TRUE), ignore_msg);
break;
/* Should only get DEFER or ERROR here. Put back terminator /* Handle the AUTH extension. If the value given is not "<>" and either
overrides for error message */ the ACL says "yes" or there is no ACL but the sending host is
authenticated, we set it up as the authenticated sender. However, if th
e
authenticator set a condition to be tested, we ignore AUTH on MAIL unle
ss
the condition is met. The value of AUTH is an xtext, which means that +
,
= and cntrl chars are coded in hex; however "<>" is unaffected by this
coding. */
case ENV_MAIL_OPT_AUTH:
if (Ustrcmp(value, "<>") != 0)
{
int rc;
uschar *ignore_msg;
default: if (auth_xtextdecode(value, &authenticated_sender) < 0)
{
/* Put back terminator overrides for error message */
value[-1] = '='; value[-1] = '=';
name[-1] = ' '; name[-1] = ' ';
(void)smtp_handle_acl_fail(ACL_WHERE_MAILAUTH, rc, user_msg, done = synprot_error(L_smtp_syntax_error, 501, NULL,
log_msg); US"invalid data for AUTH");
goto COMMAND_LOOP; goto COMMAND_LOOP;
} }
} if (!acl_smtp_mailauth)
break; {
ignore_msg = US"client not authenticated";
rc = sender_host_authenticated ? OK : FAIL;
}
else
{
ignore_msg = US"rejected by ACL";
rc = acl_check(ACL_WHERE_MAILAUTH, NULL, acl_smtp_mailauth,
&user_msg, &log_msg);
}
switch (rc)
{
case OK:
if (authenticated_by == NULL ||
authenticated_by->mail_auth_condition == NULL ||
expand_check_condition(authenticated_by->mail_auth_conditio
n,
authenticated_by->name, US"authenticator"))
break; /* Accept the AUTH */
ignore_msg = US"server_mail_auth_condition failed";
if (authenticated_id != NULL)
ignore_msg = string_sprintf("%s: authenticated ID=\"%s\"",
ignore_msg, authenticated_id);
/* Fall through */
case FAIL:
authenticated_sender = NULL;
log_write(0, LOG_MAIN, "ignoring AUTH=%s from %s (%s)",
value, host_and_ident(TRUE), ignore_msg);
break;
/* Should only get DEFER or ERROR here. Put back terminator
overrides for error message */
default:
value[-1] = '=';
name[-1] = ' ';
(void)smtp_handle_acl_fail(ACL_WHERE_MAILAUTH, rc, user_msg,
log_msg);
goto COMMAND_LOOP;
}
}
break;
#ifndef DISABLE_PRDR #ifndef DISABLE_PRDR
case ENV_MAIL_OPT_PRDR: case ENV_MAIL_OPT_PRDR:
if (prdr_enable) if (prdr_enable)
prdr_requested = TRUE; prdr_requested = TRUE;
break; break;
#endif #endif
#ifdef SUPPORT_I18N #ifdef SUPPORT_I18N
case ENV_MAIL_OPT_UTF8: case ENV_MAIL_OPT_UTF8:
if (smtputf8_advertised) if (!fl.smtputf8_advertised)
{ {
int old_pool = store_pool; done = synprot_error(L_smtp_syntax_error, 501, NULL,
US"SMTPUTF8 used when not advertised");
goto COMMAND_LOOP;
}
DEBUG(D_receive) debug_printf("smtputf8 requested\n"); DEBUG(D_receive) debug_printf("smtputf8 requested\n");
message_smtputf8 = allow_utf8_domains = TRUE; message_smtputf8 = allow_utf8_domains = TRUE;
store_pool = POOL_PERM; if (Ustrncmp(received_protocol, US"utf8", 4) != 0)
received_protocol = string_sprintf("utf8%s", received_protocol); {
store_pool = old_pool; int old_pool = store_pool;
store_pool = POOL_PERM;
received_protocol = string_sprintf("utf8%s", received_protocol);
store_pool = old_pool;
}
break;
#endif
#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
case ENV_MAIL_OPT_REQTLS:
{
uschar * r, * t;
if (!fl.requiretls_advertised)
{
done = synprot_error(L_smtp_syntax_error, 555, NULL,
US"unadvertised MAIL option: REQUIRETLS");
goto COMMAND_LOOP;
}
DEBUG(D_receive) debug_printf("requiretls requested\n");
tls_requiretls = REQUIRETLS_MSG;
r = string_copy_malloc(received_protocol);
if ((t = Ustrrchr(r, 's'))) *t = 'S';
received_protocol = r;
} }
break; break;
#endif #endif
/* 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. */
case ENV_MAIL_OPT_NULL:
value[-1] = '=';
name[-1] = ' ';
arg_error = TRUE;
break;
default: assert(0); /* No valid option. Stick back the terminator characters and break
} the loop. Do the name-terminator second as extract_option sets
/* Break out of for loop if switch() had bad argument or value==name when it found no equal-sign.
when start of the email address is reached */ An error for a malformed address will occur. */
if (arg_error) break; case ENV_MAIL_OPT_NULL:
} value[-1] = '=';
name[-1] = ' ';
arg_error = TRUE;
break;
/* If we have passed the threshold for rate limiting, apply the current default: assert(0);
delay, and update it for next time, provided this is a limited host. */ }
/* Break out of for loop if switch() had bad argument or
when start of the email address is reached */
if (arg_error) break;
}
if (smtp_mailcmd_count > smtp_rlm_threshold && #if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
verify_check_host(&smtp_ratelimit_hosts) == OK) if (tls_requiretls & REQUIRETLS_MSG)
{ {
DEBUG(D_receive) debug_printf("rate limit MAIL: delay %.3g sec\n", /* Ensure headers-only bounces whether a RET option was given or not. */
smtp_delay_mail/1000.0);
millisleep((int)smtp_delay_mail);
smtp_delay_mail *= smtp_rlm_factor;
if (smtp_delay_mail > (double)smtp_rlm_limit)
smtp_delay_mail = (double)smtp_rlm_limit;
}
/* Now extract the address, first applying any SMTP-time rewriting. The DEBUG(D_receive) if (dsn_ret == dsn_ret_full)
TRUE flag allows "<>" as a sender address. */ debug_printf("requiretls override: dsn_ret_full -> dsn_ret_hdrs\n");
dsn_ret = dsn_ret_hdrs;
}
#endif
raw_sender = rewrite_existflags & rewrite_smtp /* If we have passed the threshold for rate limiting, apply the current
? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"", delay, and update it for next time, provided this is a limited host. */
global_rewrite_rules)
: smtp_cmd_data;
raw_sender =
parse_extract_address(raw_sender, &errmess, &start, &end, &sender_domain,
TRUE);
if (!raw_sender) if (smtp_mailcmd_count > smtp_rlm_threshold &&
{ verify_check_host(&smtp_ratelimit_hosts) == OK)
done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess); {
break; DEBUG(D_receive) debug_printf("rate limit MAIL: delay %.3g sec\n",
} smtp_delay_mail/1000.0);
millisleep((int)smtp_delay_mail);
smtp_delay_mail *= smtp_rlm_factor;
if (smtp_delay_mail > (double)smtp_rlm_limit)
smtp_delay_mail = (double)smtp_rlm_limit;
}
sender_address = raw_sender; /* Now extract the address, first applying any SMTP-time rewriting. The
TRUE flag allows "<>" as a sender address. */
/* If there is a configured size limit for mail, check that this message raw_sender = rewrite_existflags & rewrite_smtp
doesn't exceed it. The check is postponed to this point so that the sender ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
can be logged. */ global_rewrite_rules)
: smtp_cmd_data;
if (thismessage_size_limit > 0 && message_size > thismessage_size_limit) raw_sender =
{ parse_extract_address(raw_sender, &errmess, &start, &end, &sender_domain,
smtp_printf("552 Message size exceeds maximum permitted\r\n", FALSE); TRUE);
log_write(L_size_reject,
LOG_MAIN|LOG_REJECT, "rejected MAIL FROM:<%s> %s: "
"message too big: size%s=%d max=%d",
sender_address,
host_and_ident(TRUE),
(message_size == INT_MAX)? ">" : "",
message_size,
thismessage_size_limit);
sender_address = NULL;
break;
}
/* Check there is enough space on the disk unless configured not to. if (!raw_sender)
When smtp_check_spool_space is set, the check is for thismessage_size_limit {
plus the current message - i.e. we accept the message only if it won't done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
reduce the space below the threshold. Add 5000 to the size to allow for break;
overheads such as the Received: line and storing of recipients, etc. }
By putting the check here, even when SIZE is not given, it allow VRFY
and EXPN etc. to be used when space is short. */
if (!receive_check_fs(
(smtp_check_spool_space && message_size >= 0)?
message_size + 5000 : 0))
{
smtp_printf("452 Space shortage, please try later\r\n", FALSE);
sender_address = NULL;
break;
}
/* If sender_address is unqualified, reject it, unless this is a locally sender_address = raw_sender;
generated message, or the sending host or net is permitted to send
unqualified addresses - typically local machines behaving as MUAs -
in which case just qualify the address. The flag is set above at the start
of the SMTP connection. */
if (sender_domain == 0 && sender_address[0] != 0) /* If there is a configured size limit for mail, check that this message
{ doesn't exceed it. The check is postponed to this point so that the sender
if (allow_unqualified_sender) can be logged. */
{
sender_domain = Ustrlen(sender_address) + 1;
sender_address = rewrite_address_qualify(sender_address, FALSE);
DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
raw_sender);
}
else
{
smtp_printf("501 %s: sender address must contain a domain\r\n", FALSE,
smtp_cmd_data);
log_write(L_smtp_syntax_error,
LOG_MAIN|LOG_REJECT,
"unqualified sender rejected: <%s> %s%s",
raw_sender,
host_and_ident(TRUE),
host_lookup_msg);
sender_address = NULL;
break;
}
}
/* Apply an ACL check if one is defined, before responding. Afterwards, if (thismessage_size_limit > 0 && message_size > thismessage_size_limit)
when pipelining is not advertised, do another sync check in case the ACL {
delayed and the client started sending in the meantime. */ smtp_printf("552 Message size exceeds maximum permitted\r\n", FALSE);
log_write(L_size_reject,
LOG_MAIN|LOG_REJECT, "rejected MAIL FROM:<%s> %s: "
"message too big: size%s=%d max=%d",
sender_address,
host_and_ident(TRUE),
(message_size == INT_MAX)? ">" : "",
message_size,
thismessage_size_limit);
sender_address = NULL;
break;
}
if (acl_smtp_mail) /* Check there is enough space on the disk unless configured not to.
{ When smtp_check_spool_space is set, the check is for thismessage_size_limi
rc = acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg); t
if (rc == OK && !pipelining_advertised && !check_sync()) plus the current message - i.e. we accept the message only if it won't
goto SYNC_FAILURE; reduce the space below the threshold. Add 5000 to the size to allow for
} overheads such as the Received: line and storing of recipients, etc.
else By putting the check here, even when SIZE is not given, it allow VRFY
rc = OK; and EXPN etc. to be used when space is short. */
if (!receive_check_fs(
(smtp_check_spool_space && message_size >= 0)?
message_size + 5000 : 0))
{
smtp_printf("452 Space shortage, please try later\r\n", FALSE);
sender_address = NULL;
break;
}
if (rc == OK || rc == DISCARD) /* If sender_address is unqualified, reject it, unless this is a locally
{ generated message, or the sending host or net is permitted to send
BOOL more = pipeline_response(); unqualified addresses - typically local machines behaving as MUAs -
in which case just qualify the address. The flag is set above at the start
of the SMTP connection. */
if (!user_msg) if (!sender_domain && *sender_address)
smtp_printf("%s%s%s", more, US"250 OK", if (f.allow_unqualified_sender)
#ifndef DISABLE_PRDR {
prdr_requested ? US", PRDR Requested" : US"", sender_domain = Ustrlen(sender_address) + 1;
#else sender_address = rewrite_address_qualify(sender_address, FALSE);
US"", DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
#endif raw_sender);
US"\r\n"); }
else
{
smtp_printf("501 %s: sender address must contain a domain\r\n", FALSE,
smtp_cmd_data);
log_write(L_smtp_syntax_error,
LOG_MAIN|LOG_REJECT,
"unqualified sender rejected: <%s> %s%s",
raw_sender,
host_and_ident(TRUE),
host_lookup_msg);
sender_address = NULL;
break;
}
/* Apply an ACL check if one is defined, before responding. Afterwards,
when pipelining is not advertised, do another sync check in case the ACL
delayed and the client started sending in the meantime. */
if (acl_smtp_mail)
{
rc = acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg);
if (rc == OK && !f.smtp_in_pipelining_advertised && !check_sync())
goto SYNC_FAILURE;
}
else else
{ rc = OK;
#ifndef DISABLE_PRDR
if (prdr_requested) if (rc == OK || rc == DISCARD)
user_msg = string_sprintf("%s%s", user_msg, US", PRDR Requested"); {
#endif BOOL more = pipeline_response();
smtp_user_msg(US"250", user_msg);
} if (!user_msg)
smtp_delay_rcpt = smtp_rlr_base; smtp_printf("%s%s%s", more, US"250 OK",
recipients_discarded = (rc == DISCARD); #ifndef DISABLE_PRDR
was_rej_mail = FALSE; prdr_requested ? US", PRDR Requested" : US"",
} #else
else US"",
{ #endif
done = smtp_handle_acl_fail(ACL_WHERE_MAIL, rc, user_msg, log_msg); US"\r\n");
sender_address = NULL; else
} {
break; #ifndef DISABLE_PRDR
if (prdr_requested)
user_msg = string_sprintf("%s%s", user_msg, US", PRDR Requested");
#endif
smtp_user_msg(US"250", user_msg);
}
smtp_delay_rcpt = smtp_rlr_base;
f.recipients_discarded = (rc == DISCARD);
was_rej_mail = FALSE;
}
else
{
done = smtp_handle_acl_fail(ACL_WHERE_MAIL, rc, user_msg, log_msg);
sender_address = NULL;
}
break;
/* The RCPT command requires an address as an operand. There may be any /* The RCPT command requires an address as an operand. There may be any
number of RCPT commands, specifying multiple recipients. We build them all number of RCPT commands, specifying multiple recipients. We build them all
into a data structure. The start/end values given by parse_extract_address into a data structure. The start/end values given by parse_extract_address
are not used, as we keep only the extracted address. */ are not used, as we keep only the extracted address. */
case RCPT_CMD: case RCPT_CMD:
HAD(SCH_RCPT); HAD(SCH_RCPT);
rcpt_count++; rcpt_count++;
was_rcpt = rcpt_in_progress = TRUE; was_rcpt = fl.rcpt_in_progress = TRUE;
/* There must be a sender address; if the sender was rejected and /* There must be a sender address; if the sender was rejected and
pipelining was advertised, we assume the client was pipelining, and do not pipelining was advertised, we assume the client was pipelining, and do not
count this as a protocol error. Reset was_rej_mail so that further RCPTs count this as a protocol error. Reset was_rej_mail so that further RCPTs
get the same treatment. */ get the same treatment. */
if (sender_address == NULL) if (sender_address == NULL)
{ {
if (pipelining_advertised && last_was_rej_mail) if (f.smtp_in_pipelining_advertised && last_was_rej_mail)
{ {
smtp_printf("503 sender not yet given\r\n", FALSE); smtp_printf("503 sender not yet given\r\n", FALSE);
was_rej_mail = TRUE; was_rej_mail = TRUE;
} }
else else
{ {
done = synprot_error(L_smtp_protocol_error, 503, NULL, done = synprot_error(L_smtp_protocol_error, 503, NULL,
US"sender not yet given"); US"sender not yet given");
was_rcpt = FALSE; /* Not a valid RCPT */ was_rcpt = FALSE; /* Not a valid RCPT */
} }
rcpt_fail_count++; rcpt_fail_count++;
break; break;
} }
/* Check for an operand */ /* Check for an operand */
if (smtp_cmd_data[0] == 0) if (smtp_cmd_data[0] == 0)
{ {
done = synprot_error(L_smtp_syntax_error, 501, NULL, done = synprot_error(L_smtp_syntax_error, 501, NULL,
US"RCPT must have an address operand"); US"RCPT must have an address operand");
rcpt_fail_count++; rcpt_fail_count++;
break; break;
} }
/* Set the DSN flags orcpt and dsn_flags from the session*/ /* Set the DSN flags orcpt and dsn_flags from the session*/
orcpt = NULL; orcpt = NULL;
flags = 0; dsn_flags = 0;
if (esmtp) for(;;) if (fl.esmtp) for(;;)
{ {
uschar *name, *value; uschar *name, *value;
if (!extract_option(&name, &value)) if (!extract_option(&name, &value))
break; break;
if (dsn_advertised && strcmpic(name, US"ORCPT") == 0) if (fl.dsn_advertised && strcmpic(name, US"ORCPT") == 0)
{
/* Check whether orcpt has been already set */
if (orcpt)
{ {
synprot_error(L_smtp_syntax_error, 501, NULL, /* Check whether orcpt has been already set */
US"ORCPT can be specified once only"); if (orcpt)
goto COMMAND_LOOP; {
} done = synprot_error(L_smtp_syntax_error, 501, NULL,
orcpt = string_copy(value); US"ORCPT can be specified once only");
DEBUG(D_receive) debug_printf("DSN orcpt: %s\n", orcpt); goto COMMAND_LOOP;
} }
orcpt = string_copy(value);
DEBUG(D_receive) debug_printf("DSN orcpt: %s\n", orcpt);
}
else if (dsn_advertised && strcmpic(name, US"NOTIFY") == 0) else if (fl.dsn_advertised && strcmpic(name, US"NOTIFY") == 0)
{
/* Check if the notify flags have been already set */
if (flags > 0)
{ {
synprot_error(L_smtp_syntax_error, 501, NULL, /* Check if the notify flags have been already set */
US"NOTIFY can be specified once only"); if (dsn_flags > 0)
goto COMMAND_LOOP; {
} done = synprot_error(L_smtp_syntax_error, 501, NULL,
if (strcmpic(value, US"NEVER") == 0) US"NOTIFY can be specified once only");
flags |= rf_notify_never; goto COMMAND_LOOP;
else }
{ if (strcmpic(value, US"NEVER") == 0)
uschar *p = value; dsn_flags |= rf_notify_never;
while (*p != 0) else
{ {
uschar *pp = p; uschar *p = value;
while (*pp != 0 && *pp != ',') pp++; while (*p != 0)
if (*pp == ',') *pp++ = 0;
if (strcmpic(p, US"SUCCESS") == 0)
{
DEBUG(D_receive) debug_printf("DSN: Setting notify success\n");
flags |= rf_notify_success;
}
else if (strcmpic(p, US"FAILURE") == 0)
{
DEBUG(D_receive) debug_printf("DSN: Setting notify failure\n");
flags |= rf_notify_failure;
}
else if (strcmpic(p, US"DELAY") == 0)
{
DEBUG(D_receive) debug_printf("DSN: Setting notify delay\n");
flags |= rf_notify_delay;
}
else
{ {
/* Catch any strange values */ uschar *pp = p;
synprot_error(L_smtp_syntax_error, 501, NULL, while (*pp != 0 && *pp != ',') pp++;
US"Invalid value for NOTIFY parameter"); if (*pp == ',') *pp++ = 0;
goto COMMAND_LOOP; if (strcmpic(p, US"SUCCESS") == 0)
} {
p = pp; DEBUG(D_receive) debug_printf("DSN: Setting notify success\n");
} dsn_flags |= rf_notify_success;
DEBUG(D_receive) debug_printf("DSN Flags: %x\n", flags); }
} else if (strcmpic(p, US"FAILURE") == 0)
} {
DEBUG(D_receive) debug_printf("DSN: Setting notify failure\n");
dsn_flags |= rf_notify_failure;
}
else if (strcmpic(p, US"DELAY") == 0)
{
DEBUG(D_receive) debug_printf("DSN: Setting notify delay\n");
dsn_flags |= rf_notify_delay;
}
else
{
/* Catch any strange values */
done = synprot_error(L_smtp_syntax_error, 501, NULL,
US"Invalid value for NOTIFY parameter");
goto COMMAND_LOOP;
}
p = pp;
}
DEBUG(D_receive) debug_printf("DSN Flags: %x\n", dsn_flags);
}
}
/* Unknown option. Stick back the terminator characters and break /* Unknown option. Stick back the terminator characters and break
the loop. An error for a malformed address will occur. */ the loop. An error for a malformed address will occur. */
else else
{ {
DEBUG(D_receive) debug_printf("Invalid RCPT option: %s : %s\n", name, va DEBUG(D_receive) debug_printf("Invalid RCPT option: %s : %s\n", name, v
lue); alue);
name[-1] = ' '; name[-1] = ' ';
value[-1] = '='; value[-1] = '=';
break; break;
} }
} }
/* Apply SMTP rewriting then extract the working address. Don't allow "<>" /* Apply SMTP rewriting then extract the working address. Don't allow "<>"
as a recipient address */ as a recipient address */
recipient = rewrite_existflags & rewrite_smtp recipient = rewrite_existflags & rewrite_smtp
? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"", ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
global_rewrite_rules) global_rewrite_rules)
: smtp_cmd_data; : smtp_cmd_data;
if (!(recipient = parse_extract_address(recipient, &errmess, &start, &end, if (!(recipient = parse_extract_address(recipient, &errmess, &start, &end,
&recipient_domain, FALSE))) &recipient_domain, FALSE)))
{ {
done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess); done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
rcpt_fail_count++; rcpt_fail_count++;
break; break;
} }
/* If the recipient address is unqualified, reject it, unless this is a /* If the recipient address is unqualified, reject it, unless this is a
locally generated message. However, unqualified addresses are permitted locally generated message. However, unqualified addresses are permitted
from a configured list of hosts and nets - typically when behaving as from a configured list of hosts and nets - typically when behaving as
MUAs rather than MTAs. Sad that SMTP is used for both types of traffic, MUAs rather than MTAs. Sad that SMTP is used for both types of traffic,
really. The flag is set at the start of the SMTP connection. really. The flag is set at the start of the SMTP connection.
RFC 1123 talks about supporting "the reserved mailbox postmaster"; I always RFC 1123 talks about supporting "the reserved mailbox postmaster"; I alway
assumed this meant "reserved local part", but the revision of RFC 821 and s
friends now makes it absolutely clear that it means *mailbox*. Consequently assumed this meant "reserved local part", but the revision of RFC 821 and
we must always qualify this address, regardless. */ friends now makes it absolutely clear that it means *mailbox*. Consequentl
y
if (!recipient_domain) we must always qualify this address, regardless. */
if (!(recipient_domain = qualify_recipient(&recipient, smtp_cmd_data,
US"recipient")))
{
rcpt_fail_count++;
break;
}
/* Check maximum allowed */ if (!recipient_domain)
if (!(recipient_domain = qualify_recipient(&recipient, smtp_cmd_data,
US"recipient")))
{
rcpt_fail_count++;
break;
}
if (rcpt_count > recipients_max && recipients_max > 0) /* Check maximum allowed */
{
if (recipients_max_reject)
{
rcpt_fail_count++;
smtp_printf("552 too many recipients\r\n", FALSE);
if (!toomany)
log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: message "
"rejected: sender=<%s> %s", sender_address, host_and_ident(TRUE));
}
else
{
rcpt_defer_count++;
smtp_printf("452 too many recipients\r\n", FALSE);
if (!toomany)
log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: excess "
"temporarily rejected: sender=<%s> %s", sender_address,
host_and_ident(TRUE));
}
toomany = TRUE; if (rcpt_count > recipients_max && recipients_max > 0)
break; {
} if (recipients_max_reject)
{
rcpt_fail_count++;
smtp_printf("552 too many recipients\r\n", FALSE);
if (!toomany)
log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: message "
"rejected: sender=<%s> %s", sender_address, host_and_ident(TRUE));
}
else
{
rcpt_defer_count++;
smtp_printf("452 too many recipients\r\n", FALSE);
if (!toomany)
log_write(0, LOG_MAIN|LOG_REJECT, "too many recipients: excess "
"temporarily rejected: sender=<%s> %s", sender_address,
host_and_ident(TRUE));
}
/* If we have passed the threshold for rate limiting, apply the current toomany = TRUE;
delay, and update it for next time, provided this is a limited host. */ break;
}
if (rcpt_count > smtp_rlr_threshold && /* If we have passed the threshold for rate limiting, apply the current
verify_check_host(&smtp_ratelimit_hosts) == OK) delay, and update it for next time, provided this is a limited host. */
{
DEBUG(D_receive) debug_printf("rate limit RCPT: delay %.3g sec\n", if (rcpt_count > smtp_rlr_threshold &&
smtp_delay_rcpt/1000.0); verify_check_host(&smtp_ratelimit_hosts) == OK)
millisleep((int)smtp_delay_rcpt); {
smtp_delay_rcpt *= smtp_rlr_factor; DEBUG(D_receive) debug_printf("rate limit RCPT: delay %.3g sec\n",
if (smtp_delay_rcpt > (double)smtp_rlr_limit) smtp_delay_rcpt/1000.0);
smtp_delay_rcpt = (double)smtp_rlr_limit; millisleep((int)smtp_delay_rcpt);
} smtp_delay_rcpt *= smtp_rlr_factor;
if (smtp_delay_rcpt > (double)smtp_rlr_limit)
smtp_delay_rcpt = (double)smtp_rlr_limit;
}
/* If the MAIL ACL discarded all the recipients, we bypass ACL checking /* If the MAIL ACL discarded all the recipients, we bypass ACL checking
for them. Otherwise, check the access control list for this recipient. As for them. Otherwise, check the access control list for this recipient. As
there may be a delay in this, re-check for a synchronization error there may be a delay in this, re-check for a synchronization error
afterwards, unless pipelining was advertised. */ afterwards, unless pipelining was advertised. */
if (recipients_discarded) if (f.recipients_discarded)
rc = DISCARD; rc = DISCARD;
else else
if ( (rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg, if ( (rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg
&log_msg)) == OK ,
&& !pipelining_advertised && !check_sync()) &log_msg)) == OK
goto SYNC_FAILURE; && !f.smtp_in_pipelining_advertised && !check_sync())
goto SYNC_FAILURE;
/* The ACL was happy */ /* The ACL was happy */
if (rc == OK) if (rc == OK)
{ {
BOOL more = pipeline_response(); BOOL more = pipeline_response();
if (user_msg) if (user_msg)
smtp_user_msg(US"250", user_msg); smtp_user_msg(US"250", user_msg);
else else
smtp_printf("250 Accepted\r\n", more); smtp_printf("250 Accepted\r\n", more);
receive_add_recipient(recipient, -1); receive_add_recipient(recipient, -1);
/* Set the dsn flags in the recipients_list */ /* Set the dsn flags in the recipients_list */
recipients_list[recipients_count-1].orcpt = orcpt; recipients_list[recipients_count-1].orcpt = orcpt;
recipients_list[recipients_count-1].dsn_flags = flags; recipients_list[recipients_count-1].dsn_flags = dsn_flags;
DEBUG(D_receive) debug_printf("DSN: orcpt: %s flags: %d\n", DEBUG(D_receive) debug_printf("DSN: orcpt: %s flags: %d\n",
recipients_list[recipients_count-1].orcpt, recipients_list[recipients_count-1].orcpt,
recipients_list[recipients_count-1].dsn_flags); recipients_list[recipients_count-1].dsn_flags);
} }
/* The recipient was discarded */ /* The recipient was discarded */
else if (rc == DISCARD) else if (rc == DISCARD)
{ {
if (user_msg) if (user_msg)
smtp_user_msg(US"250", user_msg); smtp_user_msg(US"250", user_msg);
else else
smtp_printf("250 Accepted\r\n", FALSE); smtp_printf("250 Accepted\r\n", FALSE);
rcpt_fail_count++; rcpt_fail_count++;
discarded = TRUE; discarded = TRUE;
log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: " log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: "
"discarded by %s ACL%s%s", host_and_ident(TRUE), "discarded by %s ACL%s%s", host_and_ident(TRUE),
sender_address_unrewritten? sender_address_unrewritten : sender_address, sender_address_unrewritten? sender_address_unrewritten : sender_address
smtp_cmd_argument, recipients_discarded? "MAIL" : "RCPT", ,
log_msg ? US": " : US"", log_msg ? log_msg : US""); smtp_cmd_argument, f.recipients_discarded? "MAIL" : "RCPT",
} log_msg ? US": " : US"", log_msg ? log_msg : US"");
}
/* Either the ACL failed the address, or it was deferred. */ /* Either the ACL failed the address, or it was deferred. */
else else
{ {
if (rc == FAIL) rcpt_fail_count++; else rcpt_defer_count++; if (rc == FAIL) rcpt_fail_count++; else rcpt_defer_count++;
done = smtp_handle_acl_fail(ACL_WHERE_RCPT, rc, user_msg, log_msg); done = smtp_handle_acl_fail(ACL_WHERE_RCPT, rc, user_msg, log_msg);
} }
break; break;
/* The DATA command is legal only if it follows successful MAIL FROM /* The DATA command is legal only if it follows successful MAIL FROM
and RCPT TO commands. However, if pipelining is advertised, a bad DATA is and RCPT TO commands. However, if pipelining is advertised, a bad DATA is
not counted as a protocol error if it follows RCPT (which must have been not counted as a protocol error if it follows RCPT (which must have been
rejected if there are no recipients.) This function is complete when a rejected if there are no recipients.) This function is complete when a
valid DATA command is encountered. valid DATA command is encountered.
Note concerning the code used: RFC 2821 says this: Note concerning the code used: RFC 2821 says this:
- If there was no MAIL, or no RCPT, command, or all such commands - If there was no MAIL, or no RCPT, command, or all such commands
skipping to change at line 4970 skipping to change at line 5149
DATA command. DATA command.
The example in the pipelining RFC 2920 uses 554, but I use 503 here The example in the pipelining RFC 2920 uses 554, but I use 503 here
because it is the same whether pipelining is in use or not. because it is the same whether pipelining is in use or not.
If all the RCPT commands that precede DATA provoked the same error message If all the RCPT commands that precede DATA provoked the same error message
(often indicating some kind of system error), it is helpful to include it (often indicating some kind of system error), it is helpful to include it
with the DATA rejection (an idea suggested by Tony Finch). */ with the DATA rejection (an idea suggested by Tony Finch). */
case BDAT_CMD: case BDAT_CMD:
HAD(SCH_BDAT);
{ {
int n; int n;
HAD(SCH_BDAT);
if (chunking_state != CHUNKING_OFFERED) if (chunking_state != CHUNKING_OFFERED)
{ {
done = synprot_error(L_smtp_protocol_error, 503, NULL, done = synprot_error(L_smtp_protocol_error, 503, NULL,
US"BDAT command used when CHUNKING not advertised"); US"BDAT command used when CHUNKING not advertised");
break; break;
} }
/* grab size, endmarker */ /* grab size, endmarker */
if (sscanf(CS smtp_cmd_data, "%u %n", &chunking_datasize, &n) < 1) if (sscanf(CS smtp_cmd_data, "%u %n", &chunking_datasize, &n) < 1)
skipping to change at line 5005 skipping to change at line 5184
/* push the current receive_* function on the "stack", and /* push the current receive_* function on the "stack", and
replace them by bdat_getc(), which in turn will use the lwr_receive_* replace them by bdat_getc(), which in turn will use the lwr_receive_*
functions to do the dirty work. */ functions to do the dirty work. */
lwr_receive_getc = receive_getc; lwr_receive_getc = receive_getc;
lwr_receive_getbuf = receive_getbuf; lwr_receive_getbuf = receive_getbuf;
lwr_receive_ungetc = receive_ungetc; lwr_receive_ungetc = receive_ungetc;
receive_getc = bdat_getc; receive_getc = bdat_getc;
receive_ungetc = bdat_ungetc; receive_ungetc = bdat_ungetc;
dot_ends = FALSE; f.dot_ends = FALSE;
goto DATA_BDAT; goto DATA_BDAT;
} }
case DATA_CMD: case DATA_CMD:
HAD(SCH_DATA); HAD(SCH_DATA);
dot_ends = TRUE; f.dot_ends = TRUE;
DATA_BDAT: /* Common code for DATA and BDAT */ DATA_BDAT: /* Common code for DATA and BDAT */
if (!discarded && recipients_count <= 0) #ifdef EXPERIMENTAL_PIPE_CONNECT
{ fl.pipe_connect_acceptable = FALSE;
if (rcpt_smtp_response_same && rcpt_smtp_response != NULL) #endif
{ if (!discarded && recipients_count <= 0)
uschar *code = US"503"; {
int len = Ustrlen(rcpt_smtp_response); if (fl.rcpt_smtp_response_same && rcpt_smtp_response != NULL)
smtp_respond(code, 3, FALSE, US"All RCPT commands were rejected with " {
"this error:"); uschar *code = US"503";
/* Responses from smtp_printf() will have \r\n on the end */ int len = Ustrlen(rcpt_smtp_response);
if (len > 2 && rcpt_smtp_response[len-2] == '\r') smtp_respond(code, 3, FALSE, US"All RCPT commands were rejected with "
rcpt_smtp_response[len-2] = 0; "this error:");
smtp_respond(code, 3, FALSE, rcpt_smtp_response); /* Responses from smtp_printf() will have \r\n on the end */
} if (len > 2 && rcpt_smtp_response[len-2] == '\r')
if (pipelining_advertised && last_was_rcpt) rcpt_smtp_response[len-2] = 0;
smtp_printf("503 Valid RCPT command must precede %s\r\n", FALSE, smtp_respond(code, 3, FALSE, rcpt_smtp_response);
smtp_names[smtp_connection_had[smtp_ch_index-1]]); }
else if (f.smtp_in_pipelining_advertised && last_was_rcpt)
done = synprot_error(L_smtp_protocol_error, 503, NULL, smtp_printf("503 Valid RCPT command must precede %s\r\n", FALSE,
smtp_connection_had[smtp_ch_index-1] == SCH_DATA smtp_names[smtp_connection_had[smtp_ch_index-1]]);
? US"valid RCPT command must precede DATA" else
: US"valid RCPT command must precede BDAT"); done = synprot_error(L_smtp_protocol_error, 503, NULL,
smtp_connection_had[smtp_ch_index-1] == SCH_DATA
if (chunking_state > CHUNKING_OFFERED) ? US"valid RCPT command must precede DATA"
bdat_flush_data(); : US"valid RCPT command must precede BDAT");
break;
}
if (toomany && recipients_max_reject) if (chunking_state > CHUNKING_OFFERED)
{ bdat_flush_data();
sender_address = NULL; /* This will allow a new MAIL without RSET */ break;
sender_address_unrewritten = NULL; }
smtp_printf("554 Too many recipients\r\n", FALSE);
break;
}
if (chunking_state > CHUNKING_OFFERED) if (toomany && recipients_max_reject)
rc = OK; /* No predata ACL or go-ahead output for BDAT */ {
else sender_address = NULL; /* This will allow a new MAIL without RSET */
{ sender_address_unrewritten = NULL;
/* If there is an ACL, re-check the synchronization afterwards, since the smtp_printf("554 Too many recipients\r\n", FALSE);
ACL may have delayed. To handle cutthrough delivery enforce a dummy call break;
to get the DATA command sent. */ }
if (acl_smtp_predata == NULL && cutthrough.fd < 0) if (chunking_state > CHUNKING_OFFERED)
rc = OK; rc = OK; /* No predata ACL or go-ahead output for
BDAT */
else else
{ {
uschar * acl = acl_smtp_predata ? acl_smtp_predata : US"accept"; /* If there is an ACL, re-check the synchronization afterwards, since the
enable_dollar_recipients = TRUE; ACL may have delayed. To handle cutthrough delivery enforce a dummy call
rc = acl_check(ACL_WHERE_PREDATA, NULL, acl, &user_msg, to get the DATA command sent. */
&log_msg);
enable_dollar_recipients = FALSE;
if (rc == OK && !check_sync())
goto SYNC_FAILURE;
if (rc != OK) if (acl_smtp_predata == NULL && cutthrough.cctx.sock < 0)
{ /* Either the ACL failed the address, or it was deferred. */ rc = OK;
done = smtp_handle_acl_fail(ACL_WHERE_PREDATA, rc, user_msg, log_msg); else
break; {
uschar * acl = acl_smtp_predata ? acl_smtp_predata : US"accept";
f.enable_dollar_recipients = TRUE;
rc = acl_check(ACL_WHERE_PREDATA, NULL, acl, &user_msg,
&log_msg);
f.enable_dollar_recipients = FALSE;
if (rc == OK && !check_sync())
goto SYNC_FAILURE;
if (rc != OK)
{ /* Either the ACL failed the address, or it was deferred. */
done = smtp_handle_acl_fail(ACL_WHERE_PREDATA, rc, user_msg, log_msg)
;
break;
}
} }
}
if (user_msg) if (user_msg)
smtp_user_msg(US"354", user_msg); smtp_user_msg(US"354", user_msg);
else else
smtp_printf( smtp_printf(
"354 Enter message, ending with \".\" on a line by itself\r\n", FALSE); "354 Enter message, ending with \".\" on a line by itself\r\n", FALSE
} );
}
#ifdef TCP_QUICKACK #ifdef TCP_QUICKACK
if (smtp_in) /* all ACKs needed to ramp window up for bulk data */ if (smtp_in) /* all ACKs needed to ramp window up for bulk data */
(void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK, (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK,
US &on, sizeof(on)); US &on, sizeof(on));
#endif #endif
done = 3; done = 3;
message_ended = END_NOTENDED; /* Indicate in middle of data */ message_ended = END_NOTENDED; /* Indicate in middle of data */
break; break;
case VRFY_CMD: case VRFY_CMD:
{ {
uschar * address; uschar * address;
HAD(SCH_VRFY); HAD(SCH_VRFY);
if (!(address = parse_extract_address(smtp_cmd_data, &errmess, if (!(address = parse_extract_address(smtp_cmd_data, &errmess,
&start, &end, &recipient_domain, FALSE))) &start, &end, &recipient_domain, FALSE)))
{ {
skipping to change at line 5148 skipping to change at line 5330
smtp_cmd_argument, host_and_ident(TRUE)); smtp_cmd_argument, host_and_ident(TRUE));
break; break;
} }
smtp_printf("%s\r\n", FALSE, s); smtp_printf("%s\r\n", FALSE, s);
} }
break; break;
} }
case EXPN_CMD: case EXPN_CMD:
HAD(SCH_EXPN); HAD(SCH_EXPN);
rc = acl_check(ACL_WHERE_EXPN, NULL, acl_smtp_expn, &user_msg, &log_msg); rc = acl_check(ACL_WHERE_EXPN, NULL, acl_smtp_expn, &user_msg, &log_msg);
if (rc != OK) if (rc != OK)
done = smtp_handle_acl_fail(ACL_WHERE_EXPN, rc, user_msg, log_msg); done = smtp_handle_acl_fail(ACL_WHERE_EXPN, rc, user_msg, log_msg);
else else
{ {
BOOL save_log_testing_mode = log_testing_mode; BOOL save_log_testing_mode = f.log_testing_mode;
address_test_mode = log_testing_mode = TRUE; f.address_test_mode = f.log_testing_mode = TRUE;
(void) verify_address(deliver_make_addr(smtp_cmd_data, FALSE), (void) verify_address(deliver_make_addr(smtp_cmd_data, FALSE),
smtp_out, vopt_is_recipient | vopt_qualify | vopt_expn, -1, -1, -1, smtp_out, vopt_is_recipient | vopt_qualify | vopt_expn, -1, -1, -1,
NULL, NULL, NULL); NULL, NULL, NULL);
address_test_mode = FALSE; f.address_test_mode = FALSE;
log_testing_mode = save_log_testing_mode; /* true for -bh */ f.log_testing_mode = save_log_testing_mode; /* true for -bh */
} }
break; break;
#ifdef SUPPORT_TLS #ifdef SUPPORT_TLS
case STARTTLS_CMD: case STARTTLS_CMD:
HAD(SCH_STARTTLS); HAD(SCH_STARTTLS);
if (!tls_advertised) if (!fl.tls_advertised)
{ {
done = synprot_error(L_smtp_protocol_error, 503, NULL, done = synprot_error(L_smtp_protocol_error, 503, NULL,
US"STARTTLS command used when not advertised"); US"STARTTLS command used when not advertised");
break; break;
} }
/* Apply an ACL check if one is defined */
if ( acl_smtp_starttls /* Apply an ACL check if one is defined */
&& (rc = acl_check(ACL_WHERE_STARTTLS, NULL, acl_smtp_starttls,
&user_msg, &log_msg)) != OK
)
{
done = smtp_handle_acl_fail(ACL_WHERE_STARTTLS, rc, user_msg, log_msg);
break;
}
/* RFC 2487 is not clear on when this command may be sent, though it if ( acl_smtp_starttls
does state that all information previously obtained from the client && (rc = acl_check(ACL_WHERE_STARTTLS, NULL, acl_smtp_starttls,
must be discarded if a TLS session is started. It seems reasonable to &user_msg, &log_msg)) != OK
do an implied RSET when STARTTLS is received. */ )
{
incomplete_transaction_log(US"STARTTLS"); done = smtp_handle_acl_fail(ACL_WHERE_STARTTLS, rc, user_msg, log_msg);
cancel_cutthrough_connection(TRUE, US"STARTTLS received"); break;
smtp_reset(reset_point); }
toomany = FALSE;
cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = FALSE;
/* There's an attack where more data is read in past the STARTTLS command
before TLS is negotiated, then assumed to be part of the secure session
when used afterwards; we use segregated input buffers, so are not
vulnerable, but we want to note when it happens and, for sheer paranoia,
ensure that the buffer is "wiped".
Pipelining sync checks will normally have protected us too, unless disabled
by configuration. */
if (receive_smtp_buffered()) /* RFC 2487 is not clear on when this command may be sent, though it
{ does state that all information previously obtained from the client
DEBUG(D_any) must be discarded if a TLS session is started. It seems reasonable to
debug_printf("Non-empty input buffer after STARTTLS; naive attack?\n"); do an implied RSET when STARTTLS is received. */
if (tls_in.active < 0)
smtp_inend = smtp_inptr = smtp_inbuffer;
/* and if TLS is already active, tls_server_start() should fail */
}
/* There is nothing we value in the input buffer and if TLS is successfully incomplete_transaction_log(US"STARTTLS");
negotiated, we won't use this buffer again; if TLS fails, we'll just read cancel_cutthrough_connection(TRUE, US"STARTTLS received");
fresh content into it. The buffer contains arbitrary content from an smtp_reset(reset_point);
untrusted remote source; eg: NOOP <shellcode>\r\nSTARTTLS\r\n toomany = FALSE;
It seems safest to just wipe away the content rather than leave it as a cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = FALSE;
target to jump to. */
memset(smtp_inbuffer, 0, IN_BUFFER_SIZE);
/* Attempt to start up a TLS session, and if successful, discard all
knowledge that was obtained previously. At least, that's what the RFC says,
and that's what happens by default. However, in order to work round YAEB,
there is an option to remember the esmtp state. Sigh.
We must allow for an extra EHLO command and an extra AUTH command after /* There's an attack where more data is read in past the STARTTLS command
STARTTLS that don't add to the nonmail command count. */ before TLS is negotiated, then assumed to be part of the secure session
when used afterwards; we use segregated input buffers, so are not
vulnerable, but we want to note when it happens and, for sheer paranoia,
ensure that the buffer is "wiped".
Pipelining sync checks will normally have protected us too, unless disable
d
by configuration. */
s = NULL; if (receive_smtp_buffered())
if ((rc = tls_server_start(tls_require_ciphers, &s)) == OK) {
{ DEBUG(D_any)
if (!tls_remember_esmtp) debug_printf("Non-empty input buffer after STARTTLS; naive attack?\n");
helo_seen = esmtp = auth_advertised = pipelining_advertised = FALSE; if (tls_in.active.sock < 0)
cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE; smtp_inend = smtp_inptr = smtp_inbuffer;
cmd_list[CMD_LIST_AUTH].is_mail_cmd = TRUE; /* and if TLS is already active, tls_server_start() should fail */
cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE; }
if (sender_helo_name)
{
store_free(sender_helo_name);
sender_helo_name = NULL;
host_build_sender_fullhost(); /* Rebuild */
set_process_info("handling incoming TLS connection from %s",
host_and_ident(FALSE));
}
received_protocol =
(sender_host_address ? protocols : protocols_local)
[ (esmtp
? pextend + (sender_host_authenticated ? pauthed : 0)
: pnormal)
+ (tls_in.active >= 0 ? pcrpted : 0)
];
sender_host_auth_pubname = sender_host_authenticated = NULL; /* There is nothing we value in the input buffer and if TLS is successfull
authenticated_id = NULL; y
sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING; negotiated, we won't use this buffer again; if TLS fails, we'll just read
DEBUG(D_tls) debug_printf("TLS active\n"); fresh content into it. The buffer contains arbitrary content from an
break; /* Successful STARTTLS */ untrusted remote source; eg: NOOP <shellcode>\r\nSTARTTLS\r\n
} It seems safest to just wipe away the content rather than leave it as a
else target to jump to. */
(void) smtp_log_tls_fail(s);
memset(smtp_inbuffer, 0, IN_BUFFER_SIZE);
/* Attempt to start up a TLS session, and if successful, discard all
knowledge that was obtained previously. At least, that's what the RFC says
,
and that's what happens by default. However, in order to work round YAEB,
there is an option to remember the esmtp state. Sigh.
/* Some local configuration problem was discovered before actually trying We must allow for an extra EHLO command and an extra AUTH command after
to do a TLS handshake; give a temporary error. */ STARTTLS that don't add to the nonmail command count. */
if (rc == DEFER) s = NULL;
{ if ((rc = tls_server_start(tls_require_ciphers, &s)) == OK)
smtp_printf("454 TLS currently unavailable\r\n", FALSE); {
break; if (!tls_remember_esmtp)
} fl.helo_seen = fl.esmtp = fl.auth_advertised = f.smtp_in_pipelining_adv
ertised = 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)
{
store_free(sender_helo_name);
sender_helo_name = NULL;
host_build_sender_fullhost(); /* Rebuild */
set_process_info("handling incoming TLS connection from %s",
host_and_ident(FALSE));
}
received_protocol =
(sender_host_address ? protocols : protocols_local)
[ (fl.esmtp
? pextend + (sender_host_authenticated ? pauthed : 0)
: pnormal)
+ (tls_in.active.sock >= 0 ? pcrpted : 0)
];
sender_host_auth_pubname = sender_host_authenticated = NULL;
authenticated_id = NULL;
sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING;
DEBUG(D_tls) debug_printf("TLS active\n");
break; /* Successful STARTTLS */
}
else
(void) smtp_log_tls_fail(s);
/* Hard failure. Reject everything except QUIT or closed connection. One /* Some local configuration problem was discovered before actually trying
cause for failure is a nested STARTTLS, in which case tls_in.active remains to do a TLS handshake; give a temporary error. */
set, but we must still reject all incoming commands. */
DEBUG(D_tls) debug_printf("TLS failed to start\n"); if (rc == DEFER)
while (done <= 0) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED)) {
{ smtp_printf("454 TLS currently unavailable\r\n", FALSE);
case EOF_CMD:
log_write(L_smtp_connection, LOG_MAIN, "%s closed by EOF",
smtp_get_connection_info());
smtp_notquit_exit(US"tls-failed", NULL, NULL);
done = 2;
break; break;
}
/* It is perhaps arguable as to which exit ACL should be called here, /* Hard failure. Reject everything except QUIT or closed connection. One
but as it is probably a situation that almost never arises, it cause for failure is a nested STARTTLS, in which case tls_in.active remain
probably doesn't matter. We choose to call the real QUIT ACL, which in s
some sense is perhaps "right". */ set, but we must still reject all incoming commands. Another is a handsha
ke
case QUIT_CMD: failure - and there may some encrypted data still in the pipe to us, which
user_msg = NULL; we
if ( acl_smtp_quit see as garbage commands. */
&& ((rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg,
&log_msg)) == ERROR))
log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
log_msg);
if (user_msg)
smtp_respond(US"221", 3, TRUE, user_msg);
else
smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostnam
e);
log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
smtp_get_connection_info());
done = 2;
break;
default: DEBUG(D_tls) debug_printf("TLS failed to start\n");
smtp_printf("554 Security failure\r\n", FALSE); while (done <= 0) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
break; {
} case EOF_CMD:
tls_close(TRUE, TLS_SHUTDOWN_NOWAIT); log_write(L_smtp_connection, LOG_MAIN, "%s closed by EOF",
break; smtp_get_connection_info());
smtp_notquit_exit(US"tls-failed", NULL, NULL);
done = 2;
break;
/* It is perhaps arguable as to which exit ACL should be called here,
but as it is probably a situation that almost never arises, it
probably doesn't matter. We choose to call the real QUIT ACL, which in
some sense is perhaps "right". */
case QUIT_CMD:
user_msg = NULL;
if ( acl_smtp_quit
&& ((rc = acl_check(ACL_WHERE_QUIT, NULL, acl_smtp_quit, &user_msg,
&log_msg)) == ERROR))
log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
log_msg);
if (user_msg)
smtp_respond(US"221", 3, TRUE, user_msg);
else
smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostn
ame);
log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
smtp_get_connection_info());
done = 2;
break;
default:
smtp_printf("554 Security failure\r\n", FALSE);
break;
}
tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
break;
#endif #endif
/* The ACL for QUIT is provided for gathering statistical information or /* The ACL for QUIT is provided for gathering statistical information or
similar; it does not affect the response code, but it can supply a custom similar; it does not affect the response code, but it can supply a custom
message. */ message. */
case QUIT_CMD: case QUIT_CMD:
smtp_quit_handler(&user_msg, &log_msg); smtp_quit_handler(&user_msg, &log_msg);
done = 2; done = 2;
break; break;
case RSET_CMD: case RSET_CMD:
smtp_rset_handler(); smtp_rset_handler();
cancel_cutthrough_connection(TRUE, US"RSET received"); cancel_cutthrough_connection(TRUE, US"RSET received");
smtp_reset(reset_point); smtp_reset(reset_point);
toomany = FALSE; toomany = FALSE;
break; break;
case NOOP_CMD: case NOOP_CMD:
HAD(SCH_NOOP); HAD(SCH_NOOP);
smtp_printf("250 OK\r\n", FALSE); smtp_printf("250 OK\r\n", FALSE);
break; break;
/* Show ETRN/EXPN/VRFY if there's an ACL for checking hosts; if actually /* Show ETRN/EXPN/VRFY if there's an ACL for checking hosts; if actually
used, a check will be done for permitted hosts. Show STARTTLS only if not used, a check will be done for permitted hosts. Show STARTTLS only if not
already in a TLS session and if it would be advertised in the EHLO already in a TLS session and if it would be advertised in the EHLO
response. */ response. */
case HELP_CMD: case HELP_CMD:
HAD(SCH_HELP); HAD(SCH_HELP);
smtp_printf("214-Commands supported:\r\n", TRUE); smtp_printf("214-Commands supported:\r\n", TRUE);
{ {
uschar buffer[256]; uschar buffer[256];
buffer[0] = 0; buffer[0] = 0;
Ustrcat(buffer, " AUTH"); Ustrcat(buffer, " AUTH");
#ifdef SUPPORT_TLS #ifdef SUPPORT_TLS
if (tls_in.active < 0 && if (tls_in.active.sock < 0 &&
verify_check_host(&tls_advertise_hosts) != FAIL) verify_check_host(&tls_advertise_hosts) != FAIL)
Ustrcat(buffer, " STARTTLS"); Ustrcat(buffer, " STARTTLS");
#endif #endif
Ustrcat(buffer, " HELO EHLO MAIL RCPT DATA BDAT"); Ustrcat(buffer, " HELO EHLO MAIL RCPT DATA BDAT");
Ustrcat(buffer, " NOOP QUIT RSET HELP"); Ustrcat(buffer, " NOOP QUIT RSET HELP");
if (acl_smtp_etrn != NULL) Ustrcat(buffer, " ETRN"); if (acl_smtp_etrn != NULL) Ustrcat(buffer, " ETRN");
if (acl_smtp_expn != NULL) Ustrcat(buffer, " EXPN"); if (acl_smtp_expn != NULL) Ustrcat(buffer, " EXPN");
if (acl_smtp_vrfy != NULL) Ustrcat(buffer, " VRFY"); if (acl_smtp_vrfy != NULL) Ustrcat(buffer, " VRFY");
smtp_printf("214%s\r\n", FALSE, buffer); smtp_printf("214%s\r\n", FALSE, buffer);
} }
break; break;
case EOF_CMD: case EOF_CMD:
incomplete_transaction_log(US"connection lost"); incomplete_transaction_log(US"connection lost");
smtp_notquit_exit(US"connection-lost", US"421", smtp_notquit_exit(US"connection-lost", US"421",
US"%s lost input connection", smtp_active_hostname); US"%s lost input connection", smtp_active_hostname);
/* Don't log by default unless in the middle of a message, as some mailers /* Don't log by default unless in the middle of a message, as some mailers
just drop the call rather than sending QUIT, and it clutters up the logs. just drop the call rather than sending QUIT, and it clutters up the logs.
*/ */
if (sender_address || recipients_count > 0) if (sender_address || recipients_count > 0)
log_write(L_lost_incoming_connection, LOG_MAIN, log_write(L_lost_incoming_connection, LOG_MAIN,
"unexpected %s while reading SMTP command from %s%s%s D=%s", "unexpected %s while reading SMTP command from %s%s%s D=%s",
sender_host_unknown ? "EOF" : "disconnection", f.sender_host_unknown ? "EOF" : "disconnection",
tcp_in_fastopen && !tcp_in_fastopen_logged ? US"TFO " : US"", f.tcp_in_fastopen_logged
host_and_ident(FALSE), smtp_read_error, ? US""
string_timesince(&smtp_connection_start) : f.tcp_in_fastopen
); ? f.tcp_in_fastopen_data ? US"TFO* " : US"TFO "
: US"",
else host_and_ident(FALSE), smtp_read_error,
log_write(L_smtp_connection, LOG_MAIN, "%s %slost%s D=%s", string_timesince(&smtp_connection_start)
smtp_get_connection_info(), );
tcp_in_fastopen && !tcp_in_fastopen_logged ? US"TFO " : US"",
smtp_read_error,
string_timesince(&smtp_connection_start)
);
done = 1; else
break; log_write(L_smtp_connection, LOG_MAIN, "%s %slost%s D=%s",
smtp_get_connection_info(),
f.tcp_in_fastopen && !f.tcp_in_fastopen_logged ? US"TFO " : US"",
smtp_read_error,
string_timesince(&smtp_connection_start)
);
case ETRN_CMD: done = 1;
HAD(SCH_ETRN);
if (sender_address)
{
done = synprot_error(L_smtp_protocol_error, 503, NULL,
US"ETRN is not permitted inside a transaction");
break; break;
}
log_write(L_etrn, LOG_MAIN, "ETRN %s received from %s", smtp_cmd_argument, case ETRN_CMD:
host_and_ident(FALSE)); HAD(SCH_ETRN);
if (sender_address)
{
done = synprot_error(L_smtp_protocol_error, 503, NULL,
US"ETRN is not permitted inside a transaction");
break;
}
if ((rc = acl_check(ACL_WHERE_ETRN, NULL, acl_smtp_etrn, log_write(L_etrn, LOG_MAIN, "ETRN %s received from %s", smtp_cmd_argument,
&user_msg, &log_msg)) != OK) host_and_ident(FALSE));
{
done = smtp_handle_acl_fail(ACL_WHERE_ETRN, rc, user_msg, log_msg);
break;
}
/* Compute the serialization key for this command. */ if ((rc = acl_check(ACL_WHERE_ETRN, NULL, acl_smtp_etrn,
&user_msg, &log_msg)) != OK)
{
done = smtp_handle_acl_fail(ACL_WHERE_ETRN, rc, user_msg, log_msg);
break;
}
etrn_serialize_key = string_sprintf("etrn-%s\n", smtp_cmd_data); /* Compute the serialization key for this command. */
/* If a command has been specified for running as a result of ETRN, we etrn_serialize_key = string_sprintf("etrn-%s\n", smtp_cmd_data);
permit any argument to ETRN. If not, only the # standard form is permitted,
since that is strictly the only kind of ETRN that can be implemented
according to the RFC. */
if (smtp_etrn_command) /* 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; since that is strictly the only kind of ETRN that can be implemented
etrn_command = smtp_etrn_command; according to the RFC. */
deliver_domain = smtp_cmd_data;
rc = transport_set_up_command(&argv, smtp_etrn_command, TRUE, 0, NULL,
US"ETRN processing", &error);
deliver_domain = NULL;
if (!rc)
{
log_write(0, LOG_MAIN|LOG_PANIC, "failed to set up ETRN command: %s",
error);
smtp_printf("458 Internal failure\r\n", FALSE);
break;
}
}
/* Else set up to call Exim with the -R option. */ if (smtp_etrn_command)
{
uschar *error;
BOOL rc;
etrn_command = smtp_etrn_command;
deliver_domain = smtp_cmd_data;
rc = transport_set_up_command(&argv, smtp_etrn_command, TRUE, 0, NULL,
US"ETRN processing", &error);
deliver_domain = NULL;
if (!rc)
{
log_write(0, LOG_MAIN|LOG_PANIC, "failed to set up ETRN command: %s",
error);
smtp_printf("458 Internal failure\r\n", FALSE);
break;
}
}
else /* Else set up to call Exim with the -R option. */
{
if (*smtp_cmd_data++ != '#')
{
done = synprot_error(L_smtp_syntax_error, 501, NULL,
US"argument must begin with #");
break;
}
etrn_command = US"exim -R";
argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, NULL, TRUE,
*queue_name ? 4 : 2,
US"-R", smtp_cmd_data,
US"-MCG", queue_name);
}
/* If we are host-testing, don't actually do anything. */ else
{
if (*smtp_cmd_data++ != '#')
{
done = synprot_error(L_smtp_syntax_error, 501, NULL,
US"argument must begin with #");
break;
}
etrn_command = US"exim -R";
argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, NULL, TRUE,
*queue_name ? 4 : 2,
US"-R", smtp_cmd_data,
US"-MCG", queue_name);
}
if (host_checking) /* If we are host-testing, don't actually do anything. */
{
HDEBUG(D_any)
{
debug_printf("ETRN command is: %s\n", etrn_command);
debug_printf("ETRN command execution skipped\n");
}
if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE);
else smtp_user_msg(US"250", user_msg);
break;
}
/* If ETRN queue runs are to be serialized, check the database to if (host_checking)
ensure one isn't already running. */ {
HDEBUG(D_any)
{
debug_printf("ETRN command is: %s\n", etrn_command);
debug_printf("ETRN command execution skipped\n");
}
if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE);
else smtp_user_msg(US"250", user_msg);
break;
}
if (smtp_etrn_serialize && !enq_start(etrn_serialize_key, 1)) /* If ETRN queue runs are to be serialized, check the database to
{ ensure one isn't already running. */
smtp_printf("458 Already processing %s\r\n", FALSE, smtp_cmd_data);
break;
}
/* Fork a child process and run the command. We don't want to have to if (smtp_etrn_serialize && !enq_start(etrn_serialize_key, 1))
wait for the process at any point, so set SIGCHLD to SIG_IGN before {
forking. It should be set that way anyway for external incoming SMTP, smtp_printf("458 Already processing %s\r\n", FALSE, smtp_cmd_data);
but we save and restore to be tidy. If serialization is required, we break;
actually run the command in yet another process, so we can wait for it }
to complete and then remove the serialization lock. */
oldsignal = signal(SIGCHLD, SIG_IGN); /* Fork a child process and run the command. We don't want to have to
wait for the process at any point, so set SIGCHLD to SIG_IGN before
forking. It should be set that way anyway for external incoming SMTP,
but we save and restore to be tidy. If serialization is required, we
actually run the command in yet another process, so we can wait for it
to complete and then remove the serialization lock. */
if ((pid = fork()) == 0) oldsignal = signal(SIGCHLD, SIG_IGN);
{
smtp_input = FALSE; /* This process is not associated with the */
(void)fclose(smtp_in); /* SMTP call any more. */
(void)fclose(smtp_out);
signal(SIGCHLD, SIG_DFL); /* Want to catch child */ if ((pid = fork()) == 0)
{
smtp_input = FALSE; /* This process is not associated with the */
(void)fclose(smtp_in); /* SMTP call any more. */
(void)fclose(smtp_out);
/* If not serializing, do the exec right away. Otherwise, fork down signal(SIGCHLD, SIG_DFL); /* Want to catch child */
into another process. */
if (!smtp_etrn_serialize || (pid = fork()) == 0) /* If not serializing, do the exec right away. Otherwise, fork down
{ into another process. */
DEBUG(D_exec) debug_print_argv(argv);
exim_nullstd(); /* Ensure std{in,out,err} exist */
execv(CS argv[0], (char *const *)argv);
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "exec of \"%s\" (ETRN) failed: %s",
etrn_command, strerror(errno));
_exit(EXIT_FAILURE); /* paranoia */
}
/* Obey this if smtp_serialize and the 2nd fork yielded non-zero. That if (!smtp_etrn_serialize || (pid = fork()) == 0)
is, we are in the first subprocess, after forking again. All we can do {
for a failing fork is to log it. Otherwise, wait for the 2nd process to DEBUG(D_exec) debug_print_argv(argv);
complete, before removing the serialization. */ exim_nullstd(); /* Ensure std{in,out,err} exist */
execv(CS argv[0], (char *const *)argv);
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "exec of \"%s\" (ETRN) failed: %s"
,
etrn_command, strerror(errno));
_exit(EXIT_FAILURE); /* paranoia */
}
if (pid < 0) /* Obey this if smtp_serialize and the 2nd fork yielded non-zero. That
log_write(0, LOG_MAIN|LOG_PANIC, "2nd fork for serialized ETRN " is, we are in the first subprocess, after forking again. All we can do
"failed: %s", strerror(errno)); for a failing fork is to log it. Otherwise, wait for the 2nd process to
else complete, before removing the serialization. */
{
int status; if (pid < 0)
DEBUG(D_any) debug_printf("waiting for serialized ETRN process %d\n", log_write(0, LOG_MAIN|LOG_PANIC, "2nd fork for serialized ETRN "
(int)pid); "failed: %s", strerror(errno));
(void)wait(&status); else
DEBUG(D_any) debug_printf("serialized ETRN process %d ended\n", {
(int)pid); int status;
} DEBUG(D_any) debug_printf("waiting for serialized ETRN process %d\n",
(int)pid);
(void)wait(&status);
DEBUG(D_any) debug_printf("serialized ETRN process %d ended\n",
(int)pid);
}
enq_end(etrn_serialize_key); enq_end(etrn_serialize_key);
_exit(EXIT_SUCCESS); _exit(EXIT_SUCCESS);
} }
/* Back in the top level SMTP process. Check that we started a subprocess /* Back in the top level SMTP process. Check that we started a subprocess
and restore the signal state. */ and restore the signal state. */
if (pid < 0) if (pid < 0)
{ {
log_write(0, LOG_MAIN|LOG_PANIC, "fork of process for ETRN failed: %s", log_write(0, LOG_MAIN|LOG_PANIC, "fork of process for ETRN failed: %s",
strerror(errno)); strerror(errno));
smtp_printf("458 Unable to fork process\r\n", FALSE); smtp_printf("458 Unable to fork process\r\n", FALSE);
if (smtp_etrn_serialize) enq_end(etrn_serialize_key); if (smtp_etrn_serialize) enq_end(etrn_serialize_key);
} }
else else
{ {
if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE); if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE);
else smtp_user_msg(US"250", user_msg); else smtp_user_msg(US"250", user_msg);
} }
signal(SIGCHLD, oldsignal); signal(SIGCHLD, oldsignal);
break; break;
case BADARG_CMD: case BADARG_CMD:
done = synprot_error(L_smtp_syntax_error, 501, NULL, done = synprot_error(L_smtp_syntax_error, 501, NULL,
US"unexpected argument data"); US"unexpected argument data");
break; break;
/* This currently happens only for NULLs, but could be extended. */ /* This currently happens only for NULLs, but could be extended. */
case BADCHAR_CMD: case BADCHAR_CMD:
done = synprot_error(L_smtp_syntax_error, 0, NULL, /* Just logs */ done = synprot_error(L_smtp_syntax_error, 0, NULL, /* Just logs */
US"NUL character(s) present (shown as '?')"); US"NUL character(s) present (shown as '?')");
smtp_printf("501 NUL characters are not allowed in SMTP commands\r\n", FALSE smtp_printf("501 NUL characters are not allowed in SMTP commands\r\n",
); FALSE);
break; break;
case BADSYN_CMD: case BADSYN_CMD:
SYNC_FAILURE: SYNC_FAILURE:
if (smtp_inend >= smtp_inbuffer + IN_BUFFER_SIZE) if (smtp_inend >= smtp_inbuffer + IN_BUFFER_SIZE)
smtp_inend = smtp_inbuffer + IN_BUFFER_SIZE - 1; smtp_inend = smtp_inbuffer + IN_BUFFER_SIZE - 1;
c = smtp_inend - smtp_inptr; c = smtp_inend - smtp_inptr;
if (c > 150) c = 150; /* limit logged amount */ if (c > 150) c = 150; /* limit logged amount */
smtp_inptr[c] = 0; smtp_inptr[c] = 0;
incomplete_transaction_log(US"sync failure"); incomplete_transaction_log(US"sync failure");
log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error " log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
"(next input sent too soon: pipelining was%s advertised): " "(next input sent too soon: pipelining was%s advertised): "
"rejected \"%s\" %s next input=\"%s\"", "rejected \"%s\" %s next input=\"%s\"",
pipelining_advertised? "" : " not", f.smtp_in_pipelining_advertised ? "" : " not",
smtp_cmd_buffer, host_and_ident(TRUE), smtp_cmd_buffer, host_and_ident(TRUE),
string_printing(smtp_inptr)); string_printing(smtp_inptr));
smtp_notquit_exit(US"synchronization-error", US"554", smtp_notquit_exit(US"synchronization-error", US"554",
US"SMTP synchronization error"); US"SMTP synchronization error");
done = 1; /* Pretend eof - drops connection */ done = 1; /* Pretend eof - drops connection */
break; break;
case TOO_MANY_NONMAIL_CMD: case TOO_MANY_NONMAIL_CMD:
s = smtp_cmd_buffer; s = smtp_cmd_buffer;
while (*s != 0 && !isspace(*s)) s++; while (*s != 0 && !isspace(*s)) s++;
incomplete_transaction_log(US"too many non-mail commands"); incomplete_transaction_log(US"too many non-mail commands");
log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many " log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
"nonmail commands (last was \"%.*s\")", host_and_ident(FALSE), "nonmail commands (last was \"%.*s\")", host_and_ident(FALSE),
(int)(s - smtp_cmd_buffer), smtp_cmd_buffer); (int)(s - smtp_cmd_buffer), smtp_cmd_buffer);
smtp_notquit_exit(US"bad-commands", US"554", US"Too many nonmail commands"); smtp_notquit_exit(US"bad-commands", US"554", US"Too many nonmail commands"
done = 1; /* Pretend eof - drops connection */ );
break; done = 1; /* Pretend eof - drops connection */
break;
#ifdef SUPPORT_PROXY #ifdef SUPPORT_PROXY
case PROXY_FAIL_IGNORE_CMD: case PROXY_FAIL_IGNORE_CMD:
smtp_printf("503 Command refused, required Proxy negotiation failed\r\n", FA smtp_printf("503 Command refused, required Proxy negotiation failed\r\n",
LSE); FALSE);
break; break;
#endif #endif
default: default:
if (unknown_command_count++ >= smtp_max_unknown_commands) if (unknown_command_count++ >= smtp_max_unknown_commands)
{ {
log_write(L_smtp_syntax_error, LOG_MAIN, log_write(L_smtp_syntax_error, LOG_MAIN,
"SMTP syntax error in \"%s\" %s %s", "SMTP syntax error in \"%s\" %s %s",
string_printing(smtp_cmd_buffer), host_and_ident(TRUE), string_printing(smtp_cmd_buffer), host_and_ident(TRUE),
US"unrecognized command"); US"unrecognized command");
incomplete_transaction_log(US"unrecognized command"); incomplete_transaction_log(US"unrecognized command");
smtp_notquit_exit(US"bad-commands", US"500", smtp_notquit_exit(US"bad-commands", US"500",
US"Too many unrecognized commands"); US"Too many unrecognized commands");
done = 2; done = 2;
log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many " log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
"unrecognized commands (last was \"%s\")", host_and_ident(FALSE), "unrecognized commands (last was \"%s\")", host_and_ident(FALSE),
string_printing(smtp_cmd_buffer)); string_printing(smtp_cmd_buffer));
} }
else else
done = synprot_error(L_smtp_syntax_error, 500, NULL, done = synprot_error(L_smtp_syntax_error, 500, NULL,
US"unrecognized command"); US"unrecognized command");
break; break;
} }
/* This label is used by goto's inside loops that want to break out to /* This label is used by goto's inside loops that want to break out to
the end of the command-processing loop. */ the end of the command-processing loop. */
COMMAND_LOOP: COMMAND_LOOP:
last_was_rej_mail = was_rej_mail; /* Remember some last commands for */ last_was_rej_mail = was_rej_mail; /* Remember some last commands for */
last_was_rcpt = was_rcpt; /* protocol error handling */ last_was_rcpt = was_rcpt; /* protocol error handling */
continue; continue;
} }
 End of changes. 342 change blocks. 
1592 lines changed or deleted 1809 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)