* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for handling an incoming SMTP call. */
we need room to handle large base64-encoded AUTHs for GSSAPI.
*/
-#define smtp_cmd_buffer_size 16384
+#define SMTP_CMD_BUFFER_SIZE 16384
/* Size of buffer for reading SMTP incoming packets */
-#define in_buffer_size 8192
+#define IN_BUFFER_SIZE 8192
/* Structure for SMTP command list */
MAIL_CMD, RCPT_CMD, RSET_CMD,
+ /* This is a dummy to identify the non-sync commands when not pipelining */
+
+ NON_SYNC_CMD_NON_PIPELINING,
+
/* RFC3030 section 2: "After all MAIL and RCPT responses are collected and
processed the message is sent using a series of BDAT commands"
implies that BDAT should be synchronized. However, we see Google, at least,
sending MAIL,RCPT,BDAT-LAST in a single packet, clearly not waiting for
- processing of the RPCT response(s). We shall do the same, and not require
- synch for BDAT. */
+ processing of the RCPT response(s). We shall do the same, and not require
+ synch for BDAT. Worse, as the chunk may (very likely will) follow the
+ command-header in the same packet we cannot do the usual "is there any
+ follow-on data after the command line" even for non-pipeline mode.
+ So we'll need an explicit check after reading the expected chunk amount
+ when non-pipe, before sending the ACK. */
BDAT_CMD,
- /* This is a dummy to identify the non-sync commands when not pipelining */
-
- NON_SYNC_CMD_NON_PIPELINING,
-
/* I have been unable to find a statement about the use of pipelining
with AUTH, so to be on the safe side it is here, though I kind of feel
it should be up there with the synchronized commands. */
static BOOL auth_advertised;
#ifdef SUPPORT_TLS
static BOOL tls_advertised;
+# ifdef EXPERIMENTAL_REQUIRETLS
+static BOOL requiretls_advertised;
+# endif
#endif
static BOOL dsn_advertised;
static BOOL esmtp;
static BOOL helo_seen;
static BOOL helo_accept_junk;
static BOOL count_nonmail;
-static BOOL pipelining_advertised;
static BOOL rcpt_smtp_response_same;
static BOOL rcpt_in_progress;
static int nonmail_command_count;
{ "helo", sizeof("helo")-1, HELO_CMD, TRUE, FALSE },
{ "ehlo", sizeof("ehlo")-1, EHLO_CMD, TRUE, FALSE },
{ "auth", sizeof("auth")-1, AUTH_CMD, TRUE, TRUE },
- #ifdef SUPPORT_TLS
+#ifdef SUPPORT_TLS
{ "starttls", sizeof("starttls")-1, STARTTLS_CMD, FALSE, FALSE },
- { "tls_auth", 0, TLS_AUTH_CMD, FALSE, TRUE },
- #endif
+ { "tls_auth", 0, TLS_AUTH_CMD, FALSE, FALSE },
+#endif
/* If you change anything above here, also fix the definitions below. */
ENV_MAIL_OPT_RET, ENV_MAIL_OPT_ENVID,
#ifdef SUPPORT_I18N
ENV_MAIL_OPT_UTF8,
+#endif
+#ifdef EXPERIMENTAL_REQUIRETLS
+ ENV_MAIL_OPT_REQTLS,
#endif
};
typedef struct {
{ US"ENVID", ENV_MAIL_OPT_ENVID, TRUE },
#ifdef SUPPORT_I18N
{ US"SMTPUTF8",ENV_MAIL_OPT_UTF8, FALSE }, /* rfc6531 */
+#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 */
{ US"NULL", ENV_MAIL_OPT_NULL, FALSE },
/* forward declarations */
-int bdat_ungetc(int ch);
-static int smtp_read_command(BOOL check_sync);
+static int smtp_read_command(BOOL check_sync, unsigned buffer_lim);
static int synprot_error(int type, int code, uschar *data, uschar *errmess);
static void smtp_quit_handler(uschar **, uschar **);
static void smtp_rset_handler(void);
+/*************************************************
+* Recheck synchronization *
+*************************************************/
+
+/* Synchronization checks can never be perfect because a packet may be on its
+way but not arrived when the check is done. Normally, the checks happen when
+commands are read: Exim ensures that there is no more input in the input buffer.
+In normal cases, the response to the command will be fast, and there is no
+further check.
+
+However, for some commands an ACL is run, and that can include delays. In those
+cases, it is useful to do another check on the input just before sending the
+response. This also applies at the start of a connection. This function does
+that check by means of the select() function, as long as the facility is not
+disabled or inappropriate. A failure of select() is ignored.
+
+When there is unwanted input, we read it so that it appears in the log of the
+error.
+
+Arguments: none
+Returns: TRUE if all is well; FALSE if there is input pending
+*/
+
+static BOOL
+wouldblock_reading(void)
+{
+int fd, rc;
+fd_set fds;
+struct timeval tzero;
+
+#ifdef SUPPORT_TLS
+if (tls_in.active.sock >= 0)
+ return !tls_could_read();
+#endif
+
+if (smtp_inptr < smtp_inend)
+ return FALSE;
+
+fd = fileno(smtp_in);
+FD_ZERO(&fds);
+FD_SET(fd, &fds);
+tzero.tv_sec = 0;
+tzero.tv_usec = 0;
+rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero);
+
+if (rc <= 0) return TRUE; /* Not ready to read */
+rc = smtp_getc(GETC_BUFFER_UNLIMITED);
+if (rc < 0) return TRUE; /* End of file or error */
+
+smtp_ungetc(rc);
+return FALSE;
+}
+
+static BOOL
+check_sync(void)
+{
+if (!smtp_enforce_sync || !sender_host_address || sender_host_notsocket)
+ return TRUE;
+
+return wouldblock_reading();
+}
+
+
+/* If there's input waiting (and we're doing pipelineing) then we can pipeline
+a reponse with the one following. */
+
+static BOOL
+pipeline_response(void)
+{
+if ( !smtp_enforce_sync || !sender_host_address
+ || sender_host_notsocket || !smtp_in_pipelining_advertised)
+ return FALSE;
+
+if (wouldblock_reading()) return FALSE;
+smtp_in_pipelining_used = TRUE;
+return TRUE;
+}
+
+
+
+/*************************************************
+* Log incomplete transactions *
+*************************************************/
+
+/* This function is called after a transaction has been aborted by RSET, QUIT,
+connection drops or other errors. It logs the envelope information received
+so far in order to preserve address verification attempts.
+
+Argument: string to indicate what aborted the transaction
+Returns: nothing
+*/
+
+static void
+incomplete_transaction_log(uschar *what)
+{
+if (sender_address == NULL || /* No transaction in progress */
+ !LOGGING(smtp_incomplete_transaction))
+ return;
+
+/* Build list of recipients for logging */
+
+if (recipients_count > 0)
+ {
+ int i;
+ raw_recipients = store_get(recipients_count * sizeof(uschar *));
+ for (i = 0; i < recipients_count; i++)
+ raw_recipients[i] = recipients_list[i].address;
+ raw_recipients_count = recipients_count;
+ }
+
+log_write(L_smtp_incomplete_transaction, LOG_MAIN|LOG_SENDER|LOG_RECIPIENTS,
+ "%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.
+Return false for error or EOF.
+*/
+
+static BOOL
+smtp_refill(unsigned lim)
+{
+int rc, save_errno;
+if (!smtp_out) return FALSE;
+fflush(smtp_out);
+if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
+
+/* 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. */
+
+rc = read(fileno(smtp_in), smtp_inbuffer, MIN(IN_BUFFER_SIZE-1, lim));
+save_errno = errno;
+if (smtp_receive_timeout > 0) alarm(0);
+if (rc <= 0)
+ {
+ /* Must put the error text in fixed store, because this might be during
+ header reading, where it releases unused store above the header. */
+ 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_read_error = string_copy_malloc(
+ string_sprintf(" (error: %s)", strerror(save_errno)));
+ }
+ else
+ smtp_had_eof = 1;
+ return FALSE;
+ }
+#ifndef DISABLE_DKIM
+dkim_exim_verify_feed(smtp_inbuffer, rc);
+#endif
+smtp_inend = smtp_inbuffer + rc;
+smtp_inptr = smtp_inbuffer;
+return TRUE;
+}
+
/*************************************************
* SMTP version of getc() *
*************************************************/
/* This gets the next byte from the SMTP input buffer. If the buffer is empty,
it flushes the output, and refills the buffer, with a timeout. The signal
handler is set appropriately by the calling function. This function is not used
-after a connection has negotated itself into an TLS/SSL state.
+after a connection has negotiated itself into an TLS/SSL state.
-Arguments: none
+Arguments: lim Maximum amount to read/buffer
Returns: the next character or EOF
*/
int
-smtp_getc(void)
+smtp_getc(unsigned lim)
{
if (smtp_inptr >= smtp_inend)
- {
- int rc, save_errno;
- if (!smtp_out) return EOF;
- fflush(smtp_out);
- if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
- rc = read(fileno(smtp_in), smtp_inbuffer, in_buffer_size);
- save_errno = errno;
- alarm(0);
- if (rc <= 0)
- {
- /* Must put the error text in fixed store, because this might be during
- header reading, where it releases unused store above the header. */
- if (rc < 0)
- {
- smtp_had_error = save_errno;
- smtp_read_error = string_copy_malloc(
- string_sprintf(" (error: %s)", strerror(save_errno)));
- }
- else smtp_had_eof = 1;
+ if (!smtp_refill(lim))
return EOF;
- }
-#ifndef DISABLE_DKIM
- dkim_exim_verify_feed(smtp_inbuffer, rc);
-#endif
- smtp_inend = smtp_inbuffer + rc;
- smtp_inptr = smtp_inbuffer;
- }
return *smtp_inptr++;
}
+uschar *
+smtp_getbuf(unsigned * len)
+{
+unsigned size;
+uschar * buf;
+
+if (smtp_inptr >= smtp_inend)
+ if (!smtp_refill(*len))
+ { *len = 0; return NULL; }
+
+if ((size = smtp_inend - smtp_inptr) > *len) size = *len;
+buf = smtp_inptr;
+smtp_inptr += size;
+*len = size;
+return buf;
+}
+
void
smtp_get_cache(void)
{
Placed here due to the correlation with the above smtp_getc(), which it wraps,
and also by the need to do smtp command/response handling.
-Arguments: none
+Arguments: lim (ignored)
Returns: the next character or ERR, EOD or EOF
*/
int
-bdat_getc(void)
+bdat_getc(unsigned lim)
{
uschar * user_msg = NULL;
uschar * log_msg;
for(;;)
{
- if (chunking_data_left-- > 0)
- return lwr_receive_getc();
+#ifndef DISABLE_DKIM
+ unsigned dkim_save;
+#endif
+
+ if (chunking_data_left > 0)
+ return lwr_receive_getc(chunking_data_left--);
receive_getc = lwr_receive_getc;
+ receive_getbuf = lwr_receive_getbuf;
receive_ungetc = lwr_receive_ungetc;
+#ifndef DISABLE_DKIM
+ dkim_save = dkim_collect_input;
+ dkim_collect_input = 0;
+#endif
+
+ /* Unless PIPELINING was offered, there should be no next command
+ until after we ack that chunk */
+
+ if (!smtp_in_pipelining_advertised && !check_sync())
+ {
+ unsigned n = smtp_inend - smtp_inptr;
+ if (n > 32) n = 32;
+
+ incomplete_transaction_log(US"sync failure");
+ log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
+ "(next input sent too soon: pipelining was not advertised): "
+ "rejected \"%s\" %s next input=\"%s\"%s",
+ smtp_cmd_buffer, host_and_ident(TRUE),
+ string_printing(string_copyn(smtp_inptr, n)),
+ smtp_inend - smtp_inptr > n ? "..." : "");
+ (void) synprot_error(L_smtp_protocol_error, 554, NULL,
+ US"SMTP synchronization error");
+ goto repeat_until_rset;
+ }
/* If not the last, ack the received chunk. The last response is delayed
until after the data ACL decides on it */
return EOD;
}
+ smtp_printf("250 %u byte chunk received\r\n", FALSE, chunking_datasize);
chunking_state = CHUNKING_OFFERED;
- smtp_printf("250 %u byte chunk received\r\n", chunking_datasize);
+ DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state);
/* Expect another BDAT cmd from input. RFC 3030 says nothing about
QUIT, RSET or NOOP but handling them seems obvious */
next_cmd:
- switch(smtp_read_command(TRUE))
+ switch(smtp_read_command(TRUE, 1))
{
default:
(void) synprot_error(L_smtp_protocol_error, 503, NULL,
US"only BDAT permissible after non-LAST BDAT");
repeat_until_rset:
- switch(smtp_read_command(TRUE))
+ switch(smtp_read_command(TRUE, 1))
{
case QUIT_CMD: smtp_quit_handler(&user_msg, &log_msg); /*FALLTHROUGH */
case EOF_CMD: return EOF;
case NOOP_CMD:
HAD(SCH_NOOP);
- smtp_printf("250 OK\r\n");
+ smtp_printf("250 OK\r\n", FALSE);
goto next_cmd;
case BDAT_CMD:
chunking_state = strcmpic(smtp_cmd_data+n, US"LAST") == 0
? CHUNKING_LAST : CHUNKING_ACTIVE;
chunking_data_left = chunking_datasize;
+ DEBUG(D_receive) debug_printf("chunking state %d, %d bytes\n",
+ (int)chunking_state, chunking_data_left);
if (chunking_datasize == 0)
if (chunking_state == CHUNKING_LAST)
}
receive_getc = bdat_getc;
+ receive_getbuf = bdat_getbuf; /* r~getbuf is never actually used */
receive_ungetc = bdat_ungetc;
+#ifndef DISABLE_DKIM
+ dkim_collect_input = dkim_save;
+#endif
break; /* to top of main loop */
}
}
}
}
-static void
+uschar *
+bdat_getbuf(unsigned * len)
+{
+uschar * buf;
+
+if (chunking_data_left <= 0)
+ { *len = 0; return NULL; }
+
+if (*len > chunking_data_left) *len = chunking_data_left;
+buf = lwr_receive_getbuf(len); /* Either smtp_getbuf or tls_getbuf */
+chunking_data_left -= *len;
+return buf;
+}
+
+void
bdat_flush_data(void)
{
-while (chunking_data_left-- > 0)
- if (lwr_receive_getc() < 0)
- break;
+while (chunking_data_left)
+ {
+ unsigned n = chunking_data_left;
+ if (!bdat_getbuf(&n)) break;
+ }
receive_getc = lwr_receive_getc;
+receive_getbuf = lwr_receive_getbuf;
receive_ungetc = lwr_receive_ungetc;
if (chunking_state != CHUNKING_LAST)
+ {
chunking_state = CHUNKING_OFFERED;
+ DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state);
+ }
}
Arguments:
format format string
+ more further data expected
... optional arguments
Returns: nothing
*/
void
-smtp_printf(const char *format, ...)
+smtp_printf(const char *format, BOOL more, ...)
{
va_list ap;
-va_start(ap, format);
-smtp_vprintf(format, ap);
+va_start(ap, more);
+smtp_vprintf(format, more, ap);
va_end(ap);
}
call another vararg function, only a function which accepts a va_list. */
void
-smtp_vprintf(const char *format, va_list ap)
+smtp_vprintf(const char *format, BOOL more, va_list ap)
{
BOOL yield;
{
log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_printf()");
smtp_closedown(US"Unexpected error");
- exim_exit(EXIT_FAILURE);
+ exim_exit(EXIT_FAILURE, NULL);
}
/* If this is the first output for a (non-batch) RCPT command, see if all RCPTs
/* Now write the string */
#ifdef SUPPORT_TLS
-if (tls_in.active >= 0)
+if (tls_in.active.sock >= 0)
{
- if (tls_write(TRUE, big_buffer, Ustrlen(big_buffer)) < 0)
+ if (tls_write(NULL, big_buffer, Ustrlen(big_buffer), more) < 0)
smtp_write_error = -1;
}
else
int
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;
}
static void
command_timeout_handler(int sig)
{
-sig = sig; /* Keep picky compilers happy */
-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);
+had_command_timeout = sig;
}
static void
command_sigterm_handler(int sig)
{
-sig = sig; /* Keep picky compilers happy */
-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);
+had_command_sigterm = sig;
}
Returns: none
*/
static void
-restore_socket_timeout(int fd, int get_ok, struct timeval tvtmp, socklen_t vslen)
+restore_socket_timeout(int fd, int get_ok, struct timeval * tvtmp, socklen_t vslen)
{
if (get_ok == 0)
- setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tvtmp, vslen);
+ (void) setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS tvtmp, vslen);
}
/*************************************************
* Check if host is required proxy host *
*************************************************/
/* The function determines if inbound host will be a regular smtp host
-or if it is configured that it must use Proxy Protocol.
+or if it is configured that it must use Proxy Protocol. A local
+connection cannot.
Arguments: none
Returns: bool
check_proxy_protocol_host()
{
int rc;
-/* Cannot configure local connection as a proxy inbound */
-if (sender_host_address == NULL) return proxy_session;
-rc = verify_check_this_host(CUSS &hosts_proxy, NULL, NULL,
- sender_host_address, NULL);
-if (rc == OK)
+if ( sender_host_address
+ && (rc = verify_check_this_host(CUSS &hosts_proxy, NULL, NULL,
+ sender_host_address, NULL)) == OK)
{
DEBUG(D_receive)
debug_printf("Detected proxy protocol configured host\n");
}
+/*************************************************
+* Read data until newline or end of buffer *
+*************************************************/
+/* While SMTP is server-speaks-first, TLS is client-speaks-first, so we can't
+read an entire buffer and assume there will be nothing past a proxy protocol
+header. Our approach normally is to use stdio, but again that relies upon
+"STARTTLS\r\n" and a server response before the client starts TLS handshake, or
+reading _nothing_ before client TLS handshake. So we don't want to use the
+usual buffering reads which may read enough to block TLS starting.
+
+So unfortunately we're down to "read one byte at a time, with a syscall each,
+and expect a little overhead", for all proxy-opened connections which are v1,
+just to handle the TLS-on-connect case. Since SSL functions wrap the
+underlying fd, we can't assume that we can feed them any already-read content.
+
+We need to know where to read to, the max capacity, and we'll read until we
+get a CR and one more character. Let the caller scream if it's CR+!LF.
+
+Return the amount read.
+*/
+
+static int
+swallow_until_crlf(int fd, uschar *base, int already, int capacity)
+{
+uschar *to = base + already;
+uschar *cr;
+int have = 0;
+int ret;
+int last = 0;
+
+/* For "PROXY UNKNOWN\r\n" we, at time of writing, expect to have read
+up through the \r; for the _normal_ case, we haven't yet seen the \r. */
+
+cr = memchr(base, '\r', already);
+if (cr != NULL)
+ {
+ if ((cr - base) < already - 1)
+ {
+ /* \r and presumed \n already within what we have; probably not
+ actually proxy protocol, but abort cleanly. */
+ return 0;
+ }
+ /* \r is last character read, just need one more. */
+ last = 1;
+ }
+
+while (capacity > 0)
+ {
+ do { ret = recv(fd, to, 1, 0); } while (ret == -1 && errno == EINTR);
+ if (ret == -1)
+ return -1;
+ have++;
+ if (last)
+ return have;
+ if (*to == '\r')
+ last = 1;
+ capacity--;
+ to++;
+ }
+
+/* reached end without having room for a final newline, abort */
+errno = EOVERFLOW;
+return -1;
+}
+
/*************************************************
* Setup host for proxy protocol *
*************************************************/
includes an incorrect number of spaces separating args.
Arguments: none
-Returns: int
+Returns: Boolean success
*/
-static BOOL
+static void
setup_proxy_protocol_host()
{
union {
char tmpip6[INET6_ADDRSTRLEN];
struct sockaddr_in6 tmpaddr6;
+/* We can't read "all data until end" because while SMTP is
+server-speaks-first, the TLS handshake is client-speaks-first, so for
+TLS-on-connect ports the proxy protocol header will usually be immediately
+followed by a TLS handshake, and with N TLS libraries, we can't reliably
+reinject data for reading by those. So instead we first read "enough to be
+safely read within the header, and figure out how much more to read".
+For v1 we will later read to the end-of-line, for v2 we will read based upon
+the stated length.
+
+The v2 sig is 12 octets, and another 4 gets us the length, so we know how much
+data is needed total. For v1, where the line looks like:
+PROXY TCPn L3src L3dest SrcPort DestPort \r\n
+
+However, for v1 there's also `PROXY UNKNOWN\r\n` which is only 15 octets.
+We seem to support that. So, if we read 14 octets then we can tell if we're
+v2 or v1. If we're v1, we can continue reading as normal.
+
+If we're v2, we can't slurp up the entire header. We need the length in the
+15th & 16th octets, then to read everything after that.
+
+So to safely handle v1 and v2, with client-sent-first supported correctly,
+we have to do a minimum of 3 read calls, not 1. Eww.
+*/
+
+#define PROXY_INITIAL_READ 14
+#define PROXY_V2_HEADER_SIZE 16
+#if PROXY_INITIAL_READ > PROXY_V2_HEADER_SIZE
+# error Code bug in sizes of data to read for proxy usage
+#endif
+
int get_ok = 0;
-int size, ret, fd;
+int size, ret;
+int fd = fileno(smtp_in);
const char v2sig[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
-uschar *iptype; /* To display debug info */
+uschar * iptype; /* To display debug info */
struct timeval tv;
-socklen_t vslen = 0;
struct timeval tvtmp;
-
-vslen = sizeof(struct timeval);
-
-fd = fileno(smtp_in);
+socklen_t vslen = sizeof(struct timeval);
+BOOL yield = FALSE;
/* Save current socket timeout values */
-get_ok = getsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tvtmp,
- &vslen);
+get_ok = getsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &tvtmp, &vslen);
/* Proxy Protocol host must send header within a short time
(default 3 seconds) or it's considered invalid */
tv.tv_sec = PROXY_NEGOTIATION_TIMEOUT_SEC;
tv.tv_usec = PROXY_NEGOTIATION_TIMEOUT_USEC;
-setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,
- sizeof(struct timeval));
+if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &tv, sizeof(tv)) < 0)
+ goto bad;
do
{
/* The inbound host was declared to be a Proxy Protocol host, so
- don't do a PEEK into the data, actually slurp it up. */
- ret = recv(fd, &hdr, sizeof(hdr), 0);
+ don't do a PEEK into the data, actually slurp up enough to be
+ "safe". Can't take it all because TLS-on-connect clients follow
+ immediately with TLS handshake. */
+ ret = recv(fd, &hdr, PROXY_INITIAL_READ, 0);
}
while (ret == -1 && errno == EINTR);
if (ret == -1)
- {
- restore_socket_timeout(fd, get_ok, tvtmp, vslen);
- return (errno == EAGAIN) ? 0 : ERRNO_PROXYFAIL;
- }
+ goto proxyfail;
-if (ret >= 16 &&
- memcmp(&hdr.v2, v2sig, 12) == 0)
+/* For v2, handle reading the length, and then the rest. */
+if ((ret == PROXY_INITIAL_READ) && (memcmp(&hdr.v2, v2sig, sizeof(v2sig)) == 0))
{
- uint8_t ver, cmd;
+ int retmore;
+ uint8_t ver;
+
+ /* First get the length fields. */
+ do
+ {
+ retmore = recv(fd, (uschar*)&hdr + ret, PROXY_V2_HEADER_SIZE - PROXY_INITIAL_READ, 0);
+ } while (retmore == -1 && errno == EINTR);
+ if (retmore == -1)
+ goto proxyfail;
+ ret += retmore;
- /* May 2014: haproxy combined the version and command into one byte to
- allow two full bytes for the length field in order to proxy SSL
- connections. SSL Proxy is not supported in this version of Exim, but
- must still seperate values here. */
ver = (hdr.v2.ver_cmd & 0xf0) >> 4;
- cmd = (hdr.v2.ver_cmd & 0x0f);
+
+ /* May 2014: haproxy combined the version and command into one byte to
+ allow two full bytes for the length field in order to proxy SSL
+ connections. SSL Proxy is not supported in this version of Exim, but
+ must still separate values here. */
if (ver != 0x02)
{
DEBUG(D_receive) debug_printf("Invalid Proxy Protocol version: %d\n", ver);
goto proxyfail;
}
- DEBUG(D_receive) debug_printf("Detected PROXYv2 header\n");
+
/* The v2 header will always be 16 bytes per the spec. */
- size = 16 + hdr.v2.len;
- if (ret < size)
+ size = 16 + ntohs(hdr.v2.len);
+ DEBUG(D_receive) debug_printf("Detected PROXYv2 header, size %d (limit %d)\n",
+ size, (int)sizeof(hdr));
+
+ /* We should now have 16 octets (PROXY_V2_HEADER_SIZE), and we know the total
+ amount that we need. Double-check that the size is not unreasonable, then
+ get the rest. */
+ if (size > sizeof(hdr))
{
- DEBUG(D_receive) debug_printf("Truncated or too large PROXYv2 header (%d/%d)\n",
- ret, size);
+ DEBUG(D_receive) debug_printf("PROXYv2 header size unreasonably large; security attack?\n");
goto proxyfail;
}
- switch (cmd)
+
+ do
{
- case 0x01: /* PROXY command */
- switch (hdr.v2.fam)
- {
- case 0x11: /* TCPv4 address type */
- iptype = US"IPv4";
- tmpaddr.sin_addr.s_addr = hdr.v2.addr.ip4.src_addr;
- inet_ntop(AF_INET, &(tmpaddr.sin_addr), (char *)&tmpip, sizeof(tmpip));
- if (!string_is_ip_address(US tmpip,NULL))
+ do
+ {
+ retmore = recv(fd, (uschar*)&hdr + ret, size-ret, 0);
+ } while (retmore == -1 && errno == EINTR);
+ if (retmore == -1)
+ goto proxyfail;
+ ret += retmore;
+ DEBUG(D_receive) debug_printf("PROXYv2: have %d/%d required octets\n", ret, size);
+ } while (ret < size);
+
+ } /* end scope for getting rest of data for v2 */
+
+/* At this point: if PROXYv2, we've read the exact size required for all data;
+if PROXYv1 then we've read "less than required for any valid line" and should
+read the rest". */
+
+if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0)
+ {
+ uint8_t cmd = (hdr.v2.ver_cmd & 0x0f);
+
+ switch (cmd)
+ {
+ case 0x01: /* PROXY command */
+ switch (hdr.v2.fam)
+ {
+ case 0x11: /* TCPv4 address type */
+ iptype = US"IPv4";
+ tmpaddr.sin_addr.s_addr = hdr.v2.addr.ip4.src_addr;
+ inet_ntop(AF_INET, &tmpaddr.sin_addr, CS &tmpip, sizeof(tmpip));
+ if (!string_is_ip_address(US tmpip, NULL))
{
DEBUG(D_receive) debug_printf("Invalid %s source IP\n", iptype);
- return ERRNO_PROXYFAIL;
+ goto proxyfail;
}
proxy_local_address = sender_host_address;
sender_host_address = string_copy(US tmpip);
sender_host_port = tmpport;
/* Save dest ip/port */
tmpaddr.sin_addr.s_addr = hdr.v2.addr.ip4.dst_addr;
- inet_ntop(AF_INET, &(tmpaddr.sin_addr), (char *)&tmpip, sizeof(tmpip));
- if (!string_is_ip_address(US tmpip,NULL))
+ inet_ntop(AF_INET, &tmpaddr.sin_addr, CS &tmpip, sizeof(tmpip));
+ if (!string_is_ip_address(US tmpip, NULL))
{
DEBUG(D_receive) debug_printf("Invalid %s dest port\n", iptype);
- return ERRNO_PROXYFAIL;
+ goto proxyfail;
}
proxy_external_address = string_copy(US tmpip);
tmpport = ntohs(hdr.v2.addr.ip4.dst_port);
case 0x21: /* TCPv6 address type */
iptype = US"IPv6";
memmove(tmpaddr6.sin6_addr.s6_addr, hdr.v2.addr.ip6.src_addr, 16);
- inet_ntop(AF_INET6, &(tmpaddr6.sin6_addr), (char *)&tmpip6, sizeof(tmpip6));
- if (!string_is_ip_address(US tmpip6,NULL))
+ inet_ntop(AF_INET6, &tmpaddr6.sin6_addr, CS &tmpip6, sizeof(tmpip6));
+ if (!string_is_ip_address(US tmpip6, NULL))
{
DEBUG(D_receive) debug_printf("Invalid %s source IP\n", iptype);
- return ERRNO_PROXYFAIL;
+ goto proxyfail;
}
proxy_local_address = sender_host_address;
sender_host_address = string_copy(US tmpip6);
sender_host_port = tmpport;
/* Save dest ip/port */
memmove(tmpaddr6.sin6_addr.s6_addr, hdr.v2.addr.ip6.dst_addr, 16);
- inet_ntop(AF_INET6, &(tmpaddr6.sin6_addr), (char *)&tmpip6, sizeof(tmpip6));
- if (!string_is_ip_address(US tmpip6,NULL))
+ inet_ntop(AF_INET6, &tmpaddr6.sin6_addr, CS &tmpip6, sizeof(tmpip6));
+ if (!string_is_ip_address(US tmpip6, NULL))
{
DEBUG(D_receive) debug_printf("Invalid %s dest port\n", iptype);
- return ERRNO_PROXYFAIL;
+ goto proxyfail;
}
proxy_external_address = string_copy(US tmpip6);
tmpport = ntohs(hdr.v2.addr.ip6.dst_port);
break;
case 0x00: /* LOCAL command */
/* Keep local connection address for LOCAL */
+ iptype = US"local";
break;
default:
DEBUG(D_receive)
goto proxyfail;
}
}
-else if (ret >= 8 &&
- memcmp(hdr.v1.line, "PROXY", 5) == 0)
+else if (ret >= 8 && memcmp(hdr.v1.line, "PROXY", 5) == 0)
{
- uschar *p = string_copy(hdr.v1.line);
- uschar *end = memchr(p, '\r', ret - 1);
+ uschar *p;
+ uschar *end;
uschar *sp; /* Utility variables follow */
int tmp_port;
+ int r2;
char *endc;
- if (!end || end[1] != '\n')
+ /* get the rest of the line */
+ r2 = swallow_until_crlf(fd, (uschar*)&hdr, ret, sizeof(hdr)-ret);
+ if (r2 == -1)
+ goto proxyfail;
+ ret += r2;
+
+ p = string_copy(hdr.v1.line);
+ end = memchr(p, '\r', ret - 1);
+
+ if (!end || (end == (uschar*)&hdr + ret) || end[1] != '\n')
{
DEBUG(D_receive) debug_printf("Partial or invalid PROXY header\n");
goto proxyfail;
}
*end = '\0'; /* Terminate the string */
- size = end + 2 - hdr.v1.line; /* Skip header + CRLF */
+ size = end + 2 - p; /* Skip header + CRLF */
DEBUG(D_receive) debug_printf("Detected PROXYv1 header\n");
+ DEBUG(D_receive) debug_printf("Bytes read not within PROXY header: %d\n", ret - size);
/* Step through the string looking for the required fields. Ensure
- strict adherance to required formatting, exit for any error. */
+ strict adherence to required formatting, exit for any error. */
p += 5;
if (!isspace(*(p++)))
{
goto proxyfail;
}
*sp = '\0';
- if(!string_is_ip_address(p,NULL))
+ if(!string_is_ip_address(p, NULL))
{
DEBUG(D_receive)
debug_printf("Proxied src arg is not an %s address\n", iptype);
goto proxyfail;
}
*sp = '\0';
- if(!string_is_ip_address(p,NULL))
+ if(!string_is_ip_address(p, NULL))
{
DEBUG(D_receive)
debug_printf("Proxy dest arg is not an %s address\n", iptype);
goto proxyfail;
}
*sp = '\0';
- tmp_port = strtol(CCS p,&endc,10);
+ tmp_port = strtol(CCS p, &endc, 10);
if (*endc || tmp_port == 0)
{
DEBUG(D_receive)
DEBUG(D_receive) debug_printf("Did not find proxy dest port\n");
goto proxyfail;
}
- tmp_port = strtol(CCS p,&endc,10);
+ tmp_port = strtol(CCS p, &endc, 10);
if (*endc || tmp_port == 0)
{
DEBUG(D_receive)
}
proxy_external_port = tmp_port;
/* Already checked for /r /n above. Good V1 header received. */
- goto done;
}
else
{
/* Wrong protocol */
DEBUG(D_receive) debug_printf("Invalid proxy protocol version negotiation\n");
+ (void) swallow_until_crlf(fd, (uschar*)&hdr, ret, sizeof(hdr)-ret);
goto proxyfail;
}
+done:
+ DEBUG(D_receive)
+ debug_printf("Valid %s sender from Proxy Protocol header\n", iptype);
+ yield = proxy_session;
+
+/* Don't flush any potential buffer contents. Any input on proxyfail
+should cause a synchronization failure */
+
proxyfail:
-restore_socket_timeout(fd, get_ok, tvtmp, vslen);
-/* Don't flush any potential buffer contents. Any input should cause a
- synchronization failure */
-return FALSE;
+ restore_socket_timeout(fd, get_ok, &tvtmp, vslen);
-done:
-restore_socket_timeout(fd, get_ok, tvtmp, vslen);
-DEBUG(D_receive)
- debug_printf("Valid %s sender from Proxy Protocol header\n", iptype);
-return proxy_session;
+bad:
+ if (yield)
+ {
+ sender_host_name = NULL;
+ (void) host_name_lookup();
+ host_build_sender_fullhost();
+ }
+ else
+ {
+ proxy_session_failed = TRUE;
+ DEBUG(D_receive)
+ debug_printf("Failure to extract proxied host, only QUIT allowed\n");
+ }
+
+return;
}
#endif
return when it runs.
Arguments:
- check_sync if TRUE, check synchronization rules if global option is TRUE
+ check_sync if TRUE, check synchronization rules if global option is TRUE
+ buffer_lim maximum to buffer in lower layer
Returns: a code identifying the command (enumerated above)
*/
static int
-smtp_read_command(BOOL check_sync)
+smtp_read_command(BOOL check_sync, unsigned buffer_lim)
{
int c;
int ptr = 0;
smtp_cmd_list *p;
BOOL hadnull = FALSE;
+had_command_timeout = 0;
os_non_restarting_signal(SIGALRM, command_timeout_handler);
-while ((c = (receive_getc)()) != '\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);
return OTHER_CMD;
for (p = cmd_list; p < cmd_list_end; p++)
{
- #ifdef SUPPORT_PROXY
+#ifdef SUPPORT_PROXY
/* Only allow QUIT command if Proxy Protocol parsing failed */
- if (proxy_session && proxy_session_failed)
- {
- if (p->cmd != QUIT_CMD)
- continue;
- }
- #endif
+ if (proxy_session && proxy_session_failed && p->cmd != QUIT_CMD)
+ continue;
+#endif
if ( p->len
&& strncmpic(smtp_cmd_buffer, US p->name, p->len) == 0
&& ( smtp_cmd_buffer[p->len-1] == ':' /* "mail from:" or "rcpt to:" */
/* Enforce synchronization for unknown commands */
-if (smtp_inptr < smtp_inend && /* Outstanding input */
- check_sync && /* Local flag set */
- smtp_enforce_sync && /* Global flag set */
- sender_host_address != NULL && /* Not local input */
- !sender_host_notsocket) /* Really is a socket */
+if ( smtp_inptr < smtp_inend /* Outstanding input */
+ && check_sync /* Local flag set */
+ && smtp_enforce_sync /* Global flag set */
+ && sender_host_address /* Not local input */
+ && !sender_host_notsocket) /* Really is a socket */
return BADSYN_CMD;
return OTHER_CMD;
-/*************************************************
-* Recheck synchronization *
-*************************************************/
-
-/* Synchronization checks can never be perfect because a packet may be on its
-way but not arrived when the check is done. Such checks can in any case only be
-done when TLS is not in use. Normally, the checks happen when commands are
-read: Exim ensures that there is no more input in the input buffer. In normal
-cases, the response to the command will be fast, and there is no further check.
-
-However, for some commands an ACL is run, and that can include delays. In those
-cases, it is useful to do another check on the input just before sending the
-response. This also applies at the start of a connection. This function does
-that check by means of the select() function, as long as the facility is not
-disabled or inappropriate. A failure of select() is ignored.
-
-When there is unwanted input, we read it so that it appears in the log of the
-error.
-
-Arguments: none
-Returns: TRUE if all is well; FALSE if there is input pending
-*/
-
-static BOOL
-check_sync(void)
-{
-int fd, rc;
-fd_set fds;
-struct timeval tzero;
-
-if (!smtp_enforce_sync || sender_host_address == NULL ||
- sender_host_notsocket || tls_in.active >= 0)
- return TRUE;
-
-fd = fileno(smtp_in);
-FD_ZERO(&fds);
-FD_SET(fd, &fds);
-tzero.tv_sec = 0;
-tzero.tv_usec = 0;
-rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero);
-
-if (rc <= 0) return TRUE; /* Not ready to read */
-rc = smtp_getc();
-if (rc < 0) return TRUE; /* End of file or error */
-
-smtp_ungetc(rc);
-rc = smtp_inend - smtp_inptr;
-if (rc > 150) rc = 150;
-smtp_inptr[rc] = 0;
-return FALSE;
-}
-
-
-
/*************************************************
* Forced closedown of call *
*************************************************/
void
smtp_closedown(uschar *message)
{
-if (smtp_in == NULL || smtp_batched_input) return;
+if (!smtp_in || smtp_batched_input) return;
receive_swallow_smtp();
-smtp_printf("421 %s\r\n", message);
+smtp_printf("421 %s\r\n", FALSE, message);
-for (;;) switch(smtp_read_command(FALSE))
+for (;;) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
{
case EOF_CMD:
- return;
+ return;
case QUIT_CMD:
- smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
- mac_smtp_fflush();
- return;
+ smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
+ mac_smtp_fflush();
+ return;
case RSET_CMD:
- smtp_printf("250 Reset OK\r\n");
- break;
+ smtp_printf("250 Reset OK\r\n", FALSE);
+ break;
default:
- smtp_printf("421 %s\r\n", message);
- break;
+ smtp_printf("421 %s\r\n", FALSE, message);
+ break;
}
}
uschar *
smtp_get_connection_info(void)
{
-uschar *hostname = (sender_fullhost == NULL)?
- sender_host_address : sender_fullhost;
+const uschar * hostname = sender_fullhost
+ ? sender_fullhost : sender_host_address;
if (host_checking)
return string_sprintf("SMTP connection from %s", hostname);
/* Append TLS-related information to a log line
Arguments:
- s String under construction: allocated string to extend, or NULL
- sizep Pointer to current allocation size (update on return), or NULL
- ptrp Pointer to index for new entries in string (update on return), or NULL
+ g String under construction: allocated string to extend, or NULL
Returns: Allocated string or NULL
*/
-static uschar *
-s_tlslog(uschar * s, int * sizep, int * ptrp)
+static gstring *
+s_tlslog(gstring * g)
{
- int size = sizep ? *sizep : 0;
- int ptr = ptrp ? *ptrp : 0;
-
- if (LOGGING(tls_cipher) && tls_in.cipher != NULL)
- s = string_append(s, &size, &ptr, 2, US" X=", tls_in.cipher);
- if (LOGGING(tls_certificate_verified) && tls_in.cipher != NULL)
- s = string_append(s, &size, &ptr, 2, US" CV=",
- tls_in.certificate_verified? "yes":"no");
- if (LOGGING(tls_peerdn) && tls_in.peerdn != NULL)
- s = string_append(s, &size, &ptr, 3, US" DN=\"",
- string_printing(tls_in.peerdn), US"\"");
- if (LOGGING(tls_sni) && tls_in.sni != NULL)
- s = string_append(s, &size, &ptr, 3, US" SNI=\"",
- string_printing(tls_in.sni), US"\"");
-
- if (s)
- {
- s[ptr] = '\0';
- if (sizep) *sizep = size;
- if (ptrp) *ptrp = ptr;
- }
- return s;
+if (LOGGING(tls_cipher) && tls_in.cipher)
+ g = string_append(g, 2, US" X=", tls_in.cipher);
+if (LOGGING(tls_certificate_verified) && tls_in.cipher)
+ g = string_append(g, 2, US" CV=", tls_in.certificate_verified? "yes":"no");
+if (LOGGING(tls_peerdn) && tls_in.peerdn)
+ g = string_append(g, 3, US" DN=\"", string_printing(tls_in.peerdn), US"\"");
+if (LOGGING(tls_sni) && tls_in.sni)
+ g = string_append(g, 3, US" SNI=\"", string_printing(tls_in.sni), US"\"");
+return g;
}
#endif
void
smtp_log_no_mail(void)
{
-int size, ptr, i;
-uschar *s, *sep;
+int i;
+uschar * sep, * s;
+gstring * g = NULL;
if (smtp_mailcmd_count > 0 || !LOGGING(smtp_no_mail))
return;
-s = NULL;
-size = ptr = 0;
-
-if (sender_host_authenticated != NULL)
+if (sender_host_authenticated)
{
- s = string_append(s, &size, &ptr, 2, US" A=", sender_host_authenticated);
- if (authenticated_id != NULL)
- s = string_append(s, &size, &ptr, 2, US":", authenticated_id);
+ g = string_append(g, 2, US" A=", sender_host_authenticated);
+ if (authenticated_id) g = string_append(g, 2, US":", authenticated_id);
}
#ifdef SUPPORT_TLS
-s = s_tlslog(s, &size, &ptr);
+g = s_tlslog(g);
#endif
-sep = (smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE)?
- US" C=..." : US" C=";
+sep = smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE ? US" C=..." : US" C=";
+
for (i = smtp_ch_index; i < SMTP_HBUFF_SIZE; i++)
- {
if (smtp_connection_had[i] != SCH_NONE)
{
- s = string_append(s, &size, &ptr, 2, sep,
- smtp_names[smtp_connection_had[i]]);
+ g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]);
sep = US",";
}
- }
for (i = 0; i < smtp_ch_index; i++)
{
- s = string_append(s, &size, &ptr, 2, sep, smtp_names[smtp_connection_had[i]]);
+ g = string_append(g, 2, sep, smtp_names[smtp_connection_had[i]]);
sep = US",";
}
-if (s != NULL) s[ptr] = 0; else s = US"";
-log_write(0, LOG_MAIN, "no MAIL in SMTP connection from %s D=%s%s",
- host_and_ident(FALSE),
- readconf_printtime( (int) ((long)time(NULL) - (long)smtp_connection_start)),
- s);
+if (!(s = string_from_gstring(g))) s = US"";
+
+log_write(0, LOG_MAIN, "no MAIL in %sSMTP connection from %s D=%s%s",
+ tcp_in_fastopen ? US"TFO " : US"",
+ host_and_ident(FALSE), string_timesince(&smtp_connection_start), s);
+}
+
+
+/* Return list of recent smtp commands */
+
+uschar *
+smtp_cmd_hist(void)
+{
+int i;
+gstring * list = NULL;
+uschar * s;
+
+for (i = smtp_ch_index; i < SMTP_HBUFF_SIZE; i++)
+ if (smtp_connection_had[i] != SCH_NONE)
+ list = string_append_listele(list, ',', smtp_names[smtp_connection_had[i]]);
+
+for (i = 0; i < smtp_ch_index; i++)
+ list = string_append_listele(list, ',', smtp_names[smtp_connection_had[i]]);
+
+s = string_from_gstring(list);
+return s ? s : US"";
}
+
/*************************************************
* Check HELO line and set sender_helo_name *
*************************************************/
/* Check the format of a HELO line. The data for HELO/EHLO is supposed to be
the domain name of the sending host, or an ip literal in square brackets. The
-arrgument is placed in sender_helo_name, which is in malloc store, because it
+argument is placed in sender_helo_name, which is in malloc store, because it
must persist over multiple incoming messages. If helo_accept_junk is set, this
host is permitted to send any old junk (needed for some broken hosts).
Otherwise, helo_allow_chars can be used for rogue characters in general
/* Discard any previous helo name */
-if (sender_helo_name != NULL)
+if (sender_helo_name)
{
store_free(sender_helo_name);
sender_helo_name = NULL;
/* Skip tests if junk is permitted. */
if (!yield)
- {
+
/* Allow the new standard form for IPv6 address literals, namely,
[IPv6:....], and because someone is bound to use it, allow an equivalent
IPv4 form. Allow plain addresses as well. */
/* Non-literals must be alpha, dot, hyphen, plus any non-valid chars
that have been configured (usually underscore - sigh). */
- else if (*s != 0)
- {
- yield = TRUE;
- while (*s != 0)
- {
+ else if (*s)
+ for (yield = TRUE; *s; s++)
if (!isalnum(*s) && *s != '.' && *s != '-' &&
Ustrchr(helo_allow_chars, *s) == NULL)
{
yield = FALSE;
break;
}
- s++;
- }
- }
- }
/* Save argument if OK */
uschar *v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1;
while (isspace(*v)) v--;
v[1] = 0;
-while (v > smtp_cmd_data && *v != '=' && !isspace(*v)) v--;
+while (v > smtp_cmd_data && *v != '=' && !isspace(*v))
+ {
+ /* Take care to not stop at a space embedded in a quoted local-part */
+
+ if (*v == '"') do v--; while (*v != '"' && v > smtp_cmd_data+1);
+ v--;
+ }
n = v;
if (*v == '=')
-{
+ {
while(isalpha(n[-1])) n--;
/* RFC says SP, but TAB seen in wild and other major MTAs accept it */
if (!isspace(n[-1])) return FALSE;
n[-1] = 0;
-}
+ }
else
-{
+ {
n++;
if (v == smtp_cmd_data) return FALSE;
-}
+ }
*v++ = 0;
*name = n;
*value = v;
*************************************************/
/* This function is called whenever the SMTP session is reset from
-within either of the setup functions.
+within either of the setup functions; also from the daemon loop.
Argument: the stacking pool storage reset point
Returns: nothing
*/
-static void
+void
smtp_reset(void *reset_point)
{
-store_reset(reset_point);
recipients_list = NULL;
rcpt_count = rcpt_defer_count = rcpt_fail_count =
raw_recipients_count = recipients_count = recipients_list_max = 0;
-cancel_cutthrough_connection("smtp reset");
message_linecount = 0;
message_size = -1;
acl_added_headers = NULL;
suppress_local_fixups = 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 */
-sender_address = NULL;
+sending_ip_address = NULL;
+return_path = sender_address = NULL;
+sender_data = NULL; /* Can be set by ACL */
+deliver_localpart_parent = deliver_localpart_orig = NULL;
+deliver_domain_parent = deliver_domain_orig = NULL;
+callout_address = NULL;
submission_name = NULL; /* Can be set by ACL */
raw_sender = NULL; /* After SMTP rewrite, before qualifying */
sender_address_unrewritten = NULL; /* Set only after verify rewrite */
bmi_run = 0;
bmi_verdicts = NULL;
#endif
+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
-dkim_signers = NULL;
+dkim_cur_signer = dkim_signers =
+dkim_signing_domain = dkim_signing_selector = dkim_signatures = NULL;
+dkim_cur_signer = dkim_signers = dkim_signing_domain = dkim_signing_selector = NULL;
dkim_disable_verify = FALSE;
-dkim_collect_input = FALSE;
+dkim_collect_input = 0;
+dkim_verify_overall = dkim_verify_status = dkim_verify_reason = NULL;
+dkim_key_length = 0;
+dkim_verify_signers = US"$dkim_signers";
+#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 = NULL;
+#endif
+#ifdef EXPERIMENTAL_ARC
+arc_state = arc_state_reason = NULL;
#endif
dsn_ret = 0;
dsn_envid = NULL;
+deliver_host = deliver_host_address = NULL; /* Can be set by ACL */
#ifndef DISABLE_PRDR
prdr_requested = FALSE;
#endif
-#ifdef EXPERIMENTAL_SPF
-spf_header_comment = NULL;
-spf_received = NULL;
-spf_result = NULL;
-spf_smtp_comment = NULL;
-#endif
#ifdef SUPPORT_I18N
message_smtputf8 = FALSE;
#endif
not the first message in an SMTP session and the previous message caused them
to be referenced in an ACL. */
-if (message_body != NULL)
+if (message_body)
{
store_free(message_body);
message_body = NULL;
}
-if (message_body_end != NULL)
+if (message_body_end)
{
store_free(message_body_end);
message_body_end = NULL;
repetition in the same message, but it seems right to repeat them for different
messages. */
-while (acl_warn_logged != NULL)
+while (acl_warn_logged)
{
string_item *this = acl_warn_logged;
acl_warn_logged = acl_warn_logged->next;
store_free(this);
}
+store_reset(reset_point);
}
if ((receive_feof)()) return 0; /* Treat EOF as QUIT */
+cancel_cutthrough_connection(TRUE, US"smtp_setup_batch_msg");
smtp_reset(reset_point); /* Reset for start of message */
/* Deal with SMTP commands. This loop is exited by setting done to a POSITIVE
uschar *recipient = NULL;
int start, end, sender_domain, recipient_domain;
- switch(smtp_read_command(FALSE))
+ switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
{
/* The HELO/EHLO commands set sender_address_helo if they have
valid data; otherwise they are ignored, except that they do
case HELO_CMD:
case EHLO_CMD:
- check_helo(smtp_cmd_data);
- /* Fall through */
+ check_helo(smtp_cmd_data);
+ /* Fall through */
case RSET_CMD:
- smtp_reset(reset_point);
- bsmtp_transaction_linecount = receive_linecount;
- break;
+ cancel_cutthrough_connection(TRUE, US"RSET received");
+ smtp_reset(reset_point);
+ bsmtp_transaction_linecount = receive_linecount;
+ break;
/* The MAIL FROM command requires an address as an operand. All we
it is the canonical extracted address which is all that is kept. */
case MAIL_CMD:
- smtp_mailcmd_count++; /* Count for no-mail log */
- if (sender_address != NULL)
- /* The function moan_smtp_batch() does not return. */
- moan_smtp_batch(smtp_cmd_buffer, "503 Sender already given");
+ smtp_mailcmd_count++; /* Count for no-mail log */
+ if (sender_address != NULL)
+ /* The function moan_smtp_batch() does not return. */
+ moan_smtp_batch(smtp_cmd_buffer, "503 Sender already given");
- if (smtp_cmd_data[0] == 0)
- /* The function moan_smtp_batch() does not return. */
- moan_smtp_batch(smtp_cmd_buffer, "501 MAIL FROM must have an address operand");
+ if (smtp_cmd_data[0] == 0)
+ /* The function moan_smtp_batch() does not return. */
+ moan_smtp_batch(smtp_cmd_buffer, "501 MAIL FROM must have an address operand");
- /* Reset to start of message */
+ /* Reset to start of message */
- smtp_reset(reset_point);
+ cancel_cutthrough_connection(TRUE, US"MAIL received");
+ smtp_reset(reset_point);
- /* Apply SMTP rewrite */
+ /* Apply SMTP rewrite */
- raw_sender = ((rewrite_existflags & rewrite_smtp) != 0)?
- rewrite_one(smtp_cmd_data, rewrite_smtp|rewrite_smtp_sender, NULL, FALSE,
- US"", global_rewrite_rules) : smtp_cmd_data;
+ raw_sender = ((rewrite_existflags & rewrite_smtp) != 0)?
+ rewrite_one(smtp_cmd_data, rewrite_smtp|rewrite_smtp_sender, NULL, FALSE,
+ US"", global_rewrite_rules) : smtp_cmd_data;
- /* Extract the address; the TRUE flag allows <> as valid */
+ /* Extract the address; the TRUE flag allows <> as valid */
- raw_sender =
- parse_extract_address(raw_sender, &errmess, &start, &end, &sender_domain,
- TRUE);
+ raw_sender =
+ parse_extract_address(raw_sender, &errmess, &start, &end, &sender_domain,
+ TRUE);
- if (raw_sender == NULL)
- /* The function moan_smtp_batch() does not return. */
- moan_smtp_batch(smtp_cmd_buffer, "501 %s", errmess);
+ if (!raw_sender)
+ /* The function moan_smtp_batch() does not return. */
+ 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 == 0 && sender_address[0] != 0 && sender_address[0] != '@')
- {
- if (allow_unqualified_sender)
- {
- sender_address = rewrite_address_qualify(sender_address, FALSE);
- DEBUG(D_receive) debug_printf("unqualified address %s accepted "
- "and rewritten\n", raw_sender);
- }
- /* The function moan_smtp_batch() does not return. */
- else moan_smtp_batch(smtp_cmd_buffer, "501 sender address must contain "
- "a domain");
- }
- break;
+ if ( !sender_domain
+ && sender_address[0] != 0 && sender_address[0] != '@')
+ if (allow_unqualified_sender)
+ {
+ sender_address = rewrite_address_qualify(sender_address, FALSE);
+ DEBUG(D_receive) debug_printf("unqualified address %s accepted "
+ "and rewritten\n", raw_sender);
+ }
+ /* The function moan_smtp_batch() does not return. */
+ else
+ moan_smtp_batch(smtp_cmd_buffer, "501 sender address must contain "
+ "a domain");
+ break;
/* The RCPT TO command requires an address as an operand. All we do
extracted address. */
case RCPT_CMD:
- if (sender_address == NULL)
- /* The function moan_smtp_batch() does not return. */
- moan_smtp_batch(smtp_cmd_buffer, "503 No sender yet given");
+ if (!sender_address)
+ /* The function moan_smtp_batch() does not return. */
+ moan_smtp_batch(smtp_cmd_buffer, "503 No sender yet given");
- if (smtp_cmd_data[0] == 0)
- /* The function moan_smtp_batch() does not return. */
- moan_smtp_batch(smtp_cmd_buffer, "501 RCPT TO must have an address operand");
+ if (smtp_cmd_data[0] == 0)
+ /* The function moan_smtp_batch() does not return. */
+ moan_smtp_batch(smtp_cmd_buffer,
+ "501 RCPT TO must have an address operand");
- /* Check maximum number allowed */
+ /* Check maximum number allowed */
- if (recipients_max > 0 && recipients_count + 1 > recipients_max)
- /* The function moan_smtp_batch() does not return. */
- moan_smtp_batch(smtp_cmd_buffer, "%s too many recipients",
- recipients_max_reject? "552": "452");
+ if (recipients_max > 0 && recipients_count + 1 > recipients_max)
+ /* The function moan_smtp_batch() does not return. */
+ moan_smtp_batch(smtp_cmd_buffer, "%s too many recipients",
+ recipients_max_reject? "552": "452");
- /* Apply SMTP rewrite, then extract address. Don't allow "<>" as a
- recipient address */
+ /* Apply SMTP rewrite, then extract address. Don't allow "<>" as a
+ recipient address */
- recipient = rewrite_existflags & rewrite_smtp
- ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
- global_rewrite_rules)
- : smtp_cmd_data;
+ recipient = rewrite_existflags & rewrite_smtp
+ ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+ global_rewrite_rules)
+ : smtp_cmd_data;
- recipient = parse_extract_address(recipient, &errmess, &start, &end,
- &recipient_domain, FALSE);
+ recipient = parse_extract_address(recipient, &errmess, &start, &end,
+ &recipient_domain, FALSE);
- if (!recipient)
- /* The function moan_smtp_batch() does not return. */
- moan_smtp_batch(smtp_cmd_buffer, "501 %s", errmess);
+ if (!recipient)
+ /* The function moan_smtp_batch() does not return. */
+ moan_smtp_batch(smtp_cmd_buffer, "501 %s", errmess);
- /* If the recipient address is unqualified, qualify it if permitted. Then
- add it to the list of recipients. */
+ /* If the recipient address is unqualified, qualify it if permitted. Then
+ add it to the list of recipients. */
- if (recipient_domain == 0)
- {
- if (allow_unqualified_recipient)
- {
- DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
- recipient);
- recipient = rewrite_address_qualify(recipient, TRUE);
- }
- /* The function moan_smtp_batch() does not return. */
- else moan_smtp_batch(smtp_cmd_buffer, "501 recipient address must contain "
- "a domain");
- }
- receive_add_recipient(recipient, -1);
- break;
+ if (!recipient_domain)
+ if (allow_unqualified_recipient)
+ {
+ DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
+ recipient);
+ recipient = rewrite_address_qualify(recipient, TRUE);
+ }
+ /* The function moan_smtp_batch() does not return. */
+ else
+ moan_smtp_batch(smtp_cmd_buffer,
+ "501 recipient address must contain a domain");
+
+ receive_add_recipient(recipient, -1);
+ break;
/* The DATA command is legal only if it follows successful MAIL FROM
command is encountered. */
case DATA_CMD:
- if (sender_address == NULL || recipients_count <= 0)
- {
- /* The function moan_smtp_batch() does not return. */
- if (sender_address == NULL)
- moan_smtp_batch(smtp_cmd_buffer,
- "503 MAIL FROM:<sender> command must precede DATA");
+ if (!sender_address || recipients_count <= 0)
+ /* The function moan_smtp_batch() does not return. */
+ if (!sender_address)
+ moan_smtp_batch(smtp_cmd_buffer,
+ "503 MAIL FROM:<sender> command must precede DATA");
+ else
+ moan_smtp_batch(smtp_cmd_buffer,
+ "503 RCPT TO:<recipient> must precede DATA");
else
- moan_smtp_batch(smtp_cmd_buffer,
- "503 RCPT TO:<recipient> must precede DATA");
- }
- else
- {
- done = 3; /* DATA successfully achieved */
- message_ended = END_NOTENDED; /* Indicate in middle of message */
- }
- break;
+ {
+ done = 3; /* DATA successfully achieved */
+ message_ended = END_NOTENDED; /* Indicate in middle of message */
+ }
+ break;
/* The VRFY, EXPN, HELP, ETRN, and NOOP commands are ignored. */
case HELP_CMD:
case NOOP_CMD:
case ETRN_CMD:
- bsmtp_transaction_linecount = receive_linecount;
- break;
+ bsmtp_transaction_linecount = receive_linecount;
+ break;
case EOF_CMD:
case QUIT_CMD:
- done = 2;
- break;
+ done = 2;
+ break;
case BADARG_CMD:
- /* The function moan_smtp_batch() does not return. */
- moan_smtp_batch(smtp_cmd_buffer, "501 Unexpected argument data");
- break;
+ /* The function moan_smtp_batch() does not return. */
+ moan_smtp_batch(smtp_cmd_buffer, "501 Unexpected argument data");
+ break;
case BADCHAR_CMD:
- /* The function moan_smtp_batch() does not return. */
- moan_smtp_batch(smtp_cmd_buffer, "501 Unexpected NULL in SMTP command");
- break;
+ /* The function moan_smtp_batch() does not return. */
+ moan_smtp_batch(smtp_cmd_buffer, "501 Unexpected NULL in SMTP command");
+ break;
default:
- /* The function moan_smtp_batch() does not return. */
- moan_smtp_batch(smtp_cmd_buffer, "500 Command unrecognized");
- break;
+ /* The function moan_smtp_batch() does not return. */
+ moan_smtp_batch(smtp_cmd_buffer, "500 Command unrecognized");
+ break;
}
}
+static BOOL
+smtp_log_tls_fail(uschar * errstr)
+{
+uschar * conn_info = smtp_get_connection_info();
+
+if (Ustrncmp(conn_info, US"SMTP ", 5) == 0) conn_info += 5;
+/* 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);
+return FALSE;
+}
+
+
+
+
+#ifdef TCP_FASTOPEN
+static void
+tfo_in_check(void)
+{
+# ifdef TCP_INFO
+struct tcp_info tinfo;
+socklen_t len = sizeof(tinfo);
+
+if ( getsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_INFO, &tinfo, &len) == 0
+ && tinfo.tcpi_state == TCP_SYN_RECV
+ )
+ {
+ DEBUG(D_receive) debug_printf("TCP_FASTOPEN mode connection (state TCP_SYN_RECV)\n");
+ tcp_in_fastopen = TRUE;
+ }
+# endif
+}
+#endif
+
+
/*************************************************
* Start an SMTP session *
*************************************************/
BOOL
smtp_start_session(void)
{
-int size = 256;
-int ptr, esclen;
+int esclen;
uschar *user_msg, *log_msg;
uschar *code, *esc;
-uschar *p, *s, *ss;
+uschar *p, *s;
+gstring * ss;
-smtp_connection_start = time(NULL);
+gettimeofday(&smtp_connection_start, NULL);
for (smtp_ch_index = 0; smtp_ch_index < SMTP_HBUFF_SIZE; smtp_ch_index++)
smtp_connection_had[smtp_ch_index] = SCH_NONE;
smtp_ch_index = 0;
synprot_error_count = unknown_command_count = nonmail_command_count = 0;
smtp_delay_mail = smtp_rlm_base;
auth_advertised = FALSE;
-pipelining_advertised = FALSE;
+smtp_in_pipelining_advertised = smtp_in_pipelining_used = FALSE;
pipelining_enable = TRUE;
sync_cmd_limit = NON_SYNC_CMD_NON_PIPELINING;
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
authentication settings from -oMaa to remain in force. */
-if (!host_checking && !sender_host_notsocket) sender_host_authenticated = NULL;
+if (!host_checking && !sender_host_notsocket)
+ sender_host_auth_pubname = sender_host_authenticated = NULL;
authenticated_by = NULL;
#ifdef SUPPORT_TLS
tls_in.sni = NULL;
tls_in.ocsp = OCSP_NOT_REQ;
tls_advertised = FALSE;
+# ifdef EXPERIMENTAL_REQUIRETLS
+requiretls_advertised = FALSE;
+# endif
#endif
dsn_advertised = FALSE;
#ifdef SUPPORT_I18N
/* 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,
"malloc() failed for SMTP command buffer");
smtp_cmd_buffer[0] = 0;
-smtp_data_buffer = smtp_cmd_buffer + smtp_cmd_buffer_size + 1;
+smtp_data_buffer = smtp_cmd_buffer + SMTP_CMD_BUFFER_SIZE + 1;
/* For batched input, the protocol setting can be overridden from the
command line by a trusted caller. */
(sender_host_address ? protocols : protocols_local) [pnormal];
/* Set up the buffer for inputting using direct read() calls, and arrange to
-call the local functions instead of the standard C ones. */
+call the local functions instead of the standard C ones. Place a NUL at the
+end of the buffer to safety-stop C-string reads from it. */
-if (!(smtp_inbuffer = (uschar *)malloc(in_buffer_size)))
+if (!(smtp_inbuffer = US malloc(IN_BUFFER_SIZE)))
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "malloc() failed for SMTP input buffer");
+smtp_inbuffer[IN_BUFFER_SIZE-1] = '\0';
receive_getc = smtp_getc;
+receive_getbuf = smtp_getbuf;
receive_get_cache = smtp_get_cache;
receive_ungetc = smtp_ungetc;
receive_feof = smtp_feof;
/* Set up the message size limit; this may be host-specific */
thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
-if (expand_string_message != NULL)
+if (expand_string_message)
{
if (thismessage_size_limit == -1)
log_write(0, LOG_MAIN|LOG_PANIC, "unable to expand message_size_limit: "
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
#if (!defined __GLIBC__) || (__GLIBC__ < 2)
DEBUG(D_receive) debug_printf("checking for IP options\n");
- if (getsockopt(fileno(smtp_out), IPPROTO_IP, IP_OPTIONS, (uschar *)(ipopt),
+ if (getsockopt(fileno(smtp_out), IPPROTO_IP, IP_OPTIONS, US (ipopt),
&optlen) < 0)
{
if (errno != ENOPROTOOPT)
{
log_write(0, LOG_MAIN, "getsockopt() failed from %s: %s",
host_and_ident(FALSE), strerror(errno));
- smtp_printf("451 SMTP service not available\r\n");
+ smtp_printf("451 SMTP service not available\r\n", FALSE);
return FALSE;
}
}
struct in_addr addr;
#if OPTSTYLE == 1
- uschar *optstart = (uschar *)(ipopt->__data);
+ uschar *optstart = US (ipopt->__data);
#elif OPTSTYLE == 2
- uschar *optstart = (uschar *)(ipopt->ip_opts);
+ uschar *optstart = US (ipopt->ip_opts);
#else
- uschar *optstart = (uschar *)(ipopt->ipopt_list);
+ uschar *optstart = US (ipopt->ipopt_list);
#endif
DEBUG(D_receive) debug_printf("IP options exist\n");
p += Ustrlen(p);
for (opt = optstart; opt != NULL &&
- opt < (uschar *)(ipopt) + optlen;)
+ opt < US (ipopt) + optlen;)
{
switch (*opt)
{
Ustrcat(p, "[ ");
p += 2;
for (i = 0; i < opt[1]; i++)
- {
- sprintf(CS p, "%2.2x ", opt[i]);
- p += 3;
- }
+ p += sprintf(CS p, "%2.2x ", opt[i]);
*p++ = ']';
}
opt += opt[1];
log_write(0, LOG_MAIN|LOG_REJECT,
"connection from %s refused (IP options)", host_and_ident(FALSE));
- smtp_printf("554 SMTP service not available\r\n");
+ smtp_printf("554 SMTP service not available\r\n", FALSE);
return FALSE;
}
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
setting is an attempt to get rid of some hanging connections that stick in
"bad value for smtp_receive_timeout: '%s'", exp ? exp : US"");
}
- /* Start up TLS if tls_on_connect is set. This is for supporting the legacy
- smtps port for use with older style SSL MTAs. */
-
- #ifdef SUPPORT_TLS
- if (tls_in.on_connect && tls_server_start(tls_require_ciphers) != OK)
- return FALSE;
- #endif
-
/* Test for explicit connection rejection */
if (verify_check_host(&host_reject_connection) == OK)
{
log_write(L_connection_reject, LOG_MAIN|LOG_REJECT, "refused connection "
"from %s (host_reject_connection)", host_and_ident(FALSE));
- smtp_printf("554 SMTP service not available\r\n");
+ smtp_printf("554 SMTP service not available\r\n", FALSE);
return FALSE;
}
value of errno is 0 or ENOENT (which happens if /etc/hosts.{allow,deny} does
not exist). */
- #ifdef USE_TCP_WRAPPERS
+#ifdef USE_TCP_WRAPPERS
errno = 0;
- tcp_wrappers_name = expand_string(tcp_wrappers_daemon_name);
- if (tcp_wrappers_name == NULL)
- {
+ if (!(tcp_wrappers_name = expand_string(tcp_wrappers_daemon_name)))
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Expansion of \"%s\" "
"(tcp_wrappers_name) failed: %s", string_printing(tcp_wrappers_name),
expand_string_message);
- }
+
if (!hosts_ctl(tcp_wrappers_name,
- (sender_host_name == NULL)? STRING_UNKNOWN : CS sender_host_name,
- (sender_host_address == NULL)? STRING_UNKNOWN : CS sender_host_address,
- (sender_ident == NULL)? STRING_UNKNOWN : CS sender_ident))
+ sender_host_name ? CS sender_host_name : STRING_UNKNOWN,
+ sender_host_address ? CS sender_host_address : STRING_UNKNOWN,
+ sender_ident ? CS sender_ident : STRING_UNKNOWN))
{
if (errno == 0 || errno == ENOENT)
{
log_write(L_connection_reject,
LOG_MAIN|LOG_REJECT, "refused connection from %s "
"(tcp wrappers)", host_and_ident(FALSE));
- smtp_printf("554 SMTP service not available\r\n");
+ smtp_printf("554 SMTP service not available\r\n", FALSE);
}
else
{
log_write(L_connection_reject,
LOG_MAIN|LOG_REJECT, "temporarily refused connection from %s "
"(tcp wrappers errno=%d)", host_and_ident(FALSE), save_errno);
- smtp_printf("451 Temporary local problem - please try later\r\n");
+ smtp_printf("451 Temporary local problem - please try later\r\n", FALSE);
}
return FALSE;
}
- #endif
+#endif
/* Check for reserved slots. The value of smtp_accept_count has already been
incremented to include this process. */
host_and_ident(FALSE), smtp_accept_count - 1, smtp_accept_max,
smtp_accept_reserve, (rc == DEFER)? " (lookup deferred)" : "");
smtp_printf("421 %s: Too many concurrent SMTP connections; "
- "please try again later\r\n", smtp_active_hostname);
+ "please try again later\r\n", FALSE, smtp_active_hostname);
return FALSE;
}
reserved_host = TRUE;
LOG_MAIN, "temporarily refused connection from %s: not in "
"reserve list and load average = %.2f", host_and_ident(FALSE),
(double)load_average/1000.0);
- smtp_printf("421 %s: Too much load; please try again later\r\n",
+ smtp_printf("421 %s: Too much load; please try again later\r\n", FALSE,
smtp_active_hostname);
return FALSE;
}
if (smtp_batched_input) return TRUE;
-#ifdef SUPPORT_PROXY
/* If valid Proxy Protocol source is connecting, set up session.
* Failure will not allow any SMTP function other than QUIT. */
+
+#ifdef SUPPORT_PROXY
proxy_session = FALSE;
proxy_session_failed = FALSE;
if (check_proxy_protocol_host())
- if (setup_proxy_protocol_host() == FALSE)
- {
- proxy_session_failed = TRUE;
- DEBUG(D_receive)
- debug_printf("Failure to extract proxied host, only QUIT allowed\n");
- }
- else
+ setup_proxy_protocol_host();
+#endif
+
+ /* Start up TLS if tls_on_connect is set. This is for supporting the legacy
+ smtps port for use with older style SSL MTAs. */
+
+#ifdef SUPPORT_TLS
+ if (tls_in.on_connect)
{
- sender_host_name = NULL;
- (void)host_name_lookup();
- host_build_sender_fullhost();
+ if (tls_server_start(tls_require_ciphers, &user_msg) != OK)
+ return smtp_log_tls_fail(user_msg);
+ cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
}
#endif
-/* Run the ACL if it exists */
+/* Run the connect ACL if it exists */
user_msg = NULL;
if (acl_smtp_connect)
first, and output it in one fell swoop. This gives a better chance of it
ending up as a single packet. */
-ss = store_get(size);
-ptr = 0;
+ss = string_get(256);
p = s;
do /* At least once, in case we have an empty string */
{
int len;
uschar *linebreak = Ustrchr(p, '\n');
- ss = string_catn(ss, &size, &ptr, code, 3);
- if (linebreak == NULL)
+ ss = string_catn(ss, code, 3);
+ if (!linebreak)
{
len = Ustrlen(p);
- ss = string_catn(ss, &size, &ptr, US" ", 1);
+ ss = string_catn(ss, US" ", 1);
}
else
{
len = linebreak - p;
- ss = string_catn(ss, &size, &ptr, US"-", 1);
+ ss = string_catn(ss, US"-", 1);
}
- ss = string_catn(ss, &size, &ptr, esc, esclen);
- ss = string_catn(ss, &size, &ptr, p, len);
- ss = string_catn(ss, &size, &ptr, US"\r\n", 2);
+ ss = string_catn(ss, esc, esclen);
+ ss = string_catn(ss, p, len);
+ ss = string_catn(ss, US"\r\n", 2);
p += len;
- if (linebreak != NULL) p++;
+ if (linebreak) p++;
}
-while (*p != 0);
-
-ss[ptr] = 0; /* string_cat leaves room for this */
+while (*p);
/* Before we write the banner, check that there is no input pending, unless
this synchronisation check is disabled. */
if (!check_sync())
{
+ unsigned n = smtp_inend - smtp_inptr;
+ if (n > 32) n = 32;
+
log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol "
"synchronization error (input sent without waiting for greeting): "
"rejected connection from %s input=\"%s\"", host_and_ident(TRUE),
- string_printing(smtp_inptr));
- smtp_printf("554 SMTP synchronization error\r\n");
+ string_printing(string_copyn(smtp_inptr, n)));
+ smtp_printf("554 SMTP synchronization error\r\n", FALSE);
return FALSE;
}
/* Now output the banner */
-smtp_printf("%s", ss);
+smtp_printf("%s", FALSE, string_from_gstring(ss));
+
+/* 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. */
+
+#ifdef TCP_FASTOPEN
+tfo_in_check();
+#endif
+
return TRUE;
}
if (code > 0)
{
- smtp_printf("%d%c%s%s%s\r\n", code, (yield == 1)? '-' : ' ',
- (data == NULL)? US"" : data, (data == NULL)? US"" : US": ", errmess);
+ smtp_printf("%d%c%s%s%s\r\n", FALSE, code, yield == 1 ? '-' : ' ',
+ data ? data : US"", data ? US": " : US"", errmess);
if (yield == 1)
- smtp_printf("%d Too many syntax or protocol errors\r\n", code);
+ smtp_printf("%d Too many syntax or protocol errors\r\n", FALSE, code);
}
return yield;
-/*************************************************
-* Log incomplete transactions *
-*************************************************/
-
-/* This function is called after a transaction has been aborted by RSET, QUIT,
-connection drops or other errors. It logs the envelope information received
-so far in order to preserve address verification attempts.
-
-Argument: string to indicate what aborted the transaction
-Returns: nothing
-*/
-
-static void
-incomplete_transaction_log(uschar *what)
-{
-if (sender_address == NULL || /* No transaction in progress */
- !LOGGING(smtp_incomplete_transaction))
- return;
-
-/* Build list of recipients for logging */
-
-if (recipients_count > 0)
- {
- int i;
- raw_recipients = store_get(recipients_count * sizeof(uschar *));
- for (i = 0; i < recipients_count; i++)
- raw_recipients[i] = recipients_list[i].address;
- raw_recipients_count = recipients_count;
- }
-
-log_write(L_smtp_incomplete_transaction, LOG_MAIN|LOG_SENDER|LOG_RECIPIENTS,
- "%s incomplete transaction (%s)", host_and_ident(TRUE), what);
-}
-
-
-
-
/*************************************************
* Send SMTP response, possibly multiline *
*************************************************/
rcpt_in_progress = FALSE;
}
-/* Not 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,
+not the whole MAIL/RCPT/DATA response set. */
for (;;)
{
uschar *nl = Ustrchr(msg, '\n');
if (nl == NULL)
{
- smtp_printf("%.3s%c%.*s%s\r\n", code, final? ' ':'-', esclen, esc, msg);
+ smtp_printf("%.3s%c%.*s%s\r\n", !final, code, final ? ' ':'-', esclen, esc, msg);
return;
}
else if (nl[1] == 0 || no_multiline_responses)
{
- smtp_printf("%.3s%c%.*s%.*s\r\n", code, final? ' ':'-', esclen, esc,
+ smtp_printf("%.3s%c%.*s%.*s\r\n", !final, code, final ? ' ':'-', esclen, esc,
(int)(nl - msg), msg);
return;
}
else
{
- smtp_printf("%.3s-%.*s%.*s\r\n", code, esclen, esc, (int)(nl - msg), msg);
+ smtp_printf("%.3s-%.*s%.*s\r\n", TRUE, code, esclen, esc, (int)(nl - msg), msg);
msg = nl + 1;
while (isspace(*msg)) msg++;
}
/* This function is called when acl_check() fails. As well as calls from within
this module, it is called from receive.c for an ACL after DATA. It sorts out
-logging the incident, and sets up the error response. A message containing
+logging the incident, and sends the error response. A message containing
newlines is turned into a multiline SMTP response, but for logging, only the
first line is used.
failures, but not defers. However, always log it for defer, and log it for fail
unless the sender_verify_fail log selector has been turned off. */
-if (sender_verified_failed != NULL &&
+if (sender_verified_failed &&
!testflag(sender_verified_failed, af_sverify_told))
{
BOOL save_rcpt_in_progress = rcpt_in_progress;
(sender_verified_failed->message == NULL)? US"" :
string_sprintf(": %s", sender_verified_failed->message));
- if (rc == FAIL && sender_verified_failed->user_message != NULL)
+ if (rc == FAIL && sender_verified_failed->user_message)
smtp_respond(smtp_code, codelen, FALSE, string_sprintf(
testflag(sender_verified_failed, af_verify_pmfail)?
"Postmaster verification failed while checking <%s>\n%s\n"
if (log_reject_target != 0)
{
#ifdef SUPPORT_TLS
- uschar * tls = s_tlslog(NULL, NULL, NULL);
+ gstring * g = s_tlslog(NULL);
+ uschar * tls = string_from_gstring(g);
if (!tls) tls = US"";
#else
uschar * tls = US"";
passed to this function.
In case things go wrong while processing this function, causing an error that
-may re-enter this funtion, there is a recursion check.
+may re-enter this function, there is a recursion check.
Arguments:
reason What $smtp_notquit_reason will be set to in the ACL;
va_start(ap, defaultrespond);
if (!string_vformat(buffer, sizeof(buffer), CS defaultrespond, ap))
log_write(0, LOG_MAIN|LOG_PANIC, "string too large in smtp_notquit_exit()");
- smtp_printf("%s %s\r\n", code, buffer);
+ smtp_printf("%s %s\r\n", FALSE, code, buffer);
va_end(ap);
}
mac_smtp_fflush();
helo_verified = Ustrncmp(sender_helo_name+1, sender_host_address,
Ustrlen(sender_host_address)) == 0;
- #if HAVE_IPV6
+#if HAVE_IPV6
if (!helo_verified)
{
if (strncmpic(sender_host_address, US"::ffff:", 7) == 0)
helo_verified = Ustrncmp(sender_helo_name + 1,
sender_host_address + 7, Ustrlen(sender_host_address) - 7) == 0;
}
- #endif
+#endif
HDEBUG(D_receive)
{ if (helo_verified) debug_printf("matched host address\n"); }
HDEBUG(D_receive) debug_printf("getting IP address for %s\n",
sender_helo_name);
- rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A,
+ rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A | HOST_FIND_BY_AAAA,
NULL, NULL, NULL, &d, NULL, NULL);
if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL)
for (hh = &h; hh; hh = hh->next)
{
if (set_id) authenticated_id = string_copy_malloc(set_id);
sender_host_authenticated = au->name;
+ sender_host_auth_pubname = au->public_name;
authentication_failed = FALSE;
authenticated_fail_id = NULL; /* Impossible to already be set? */
received_protocol =
(sender_host_address ? protocols : protocols_local)
- [pextend + pauthed + (tls_in.active >= 0 ? pcrpted:0)];
+ [pextend + pauthed + (tls_in.active.sock >= 0 ? pcrpted:0)];
*s = *ss = US"235 Authentication succeeded";
authenticated_by = au;
break;
*recipient = rewrite_address_qualify(*recipient, TRUE);
return rd;
}
-smtp_printf("501 %s: recipient address must contain a domain\r\n",
+smtp_printf("501 %s: recipient address must contain a domain\r\n", FALSE,
smtp_cmd_data);
log_write(L_smtp_syntax_error,
LOG_MAIN|LOG_REJECT, "unqualified %s rejected: <%s> %s%s",
if (*user_msgp)
smtp_respond(US"221", 3, TRUE, *user_msgp);
else
- smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
+ smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
#ifdef SUPPORT_TLS
-tls_close(TRUE, TRUE);
+tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
#endif
log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
{
HAD(SCH_RSET);
incomplete_transaction_log(US"RSET");
-smtp_printf("250 Reset OK\r\n");
+smtp_printf("250 Reset OK\r\n", FALSE);
cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE;
}
cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
#ifdef SUPPORT_TLS
cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = TRUE;
-cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
#endif
/* Set the local signal handler for SIGTERM - it tries to end off tidily */
+had_command_sigterm = 0;
os_non_restarting_signal(SIGTERM, command_sigterm_handler);
/* Batched SMTP is handled in a different function. */
void (*oldsignal)(int);
pid_t pid;
int start, end, sender_domain, recipient_domain;
- int ptr, size, rc;
+ int rc;
int c;
auth_instance *au;
uschar *orcpt = NULL;
int flags;
+ gstring * g;
-#if defined(SUPPORT_TLS) && defined(AUTH_TLS)
+#ifdef AUTH_TLS
/* 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.certificate_verified
&& cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd
)
{
cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = FALSE;
- if ( acl_smtp_auth
- && (rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth,
- &user_msg, &log_msg)) != OK
- )
- {
- done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg);
- continue;
- }
for (au = auths; au; au = au->next)
if (strcmpic(US"tls", au->driver_name) == 0)
{
- smtp_cmd_data = NULL;
-
- if (smtp_in_auth(au, &s, &ss) == OK)
- { DEBUG(D_auth) debug_printf("tls auth succeeded\n"); }
+ 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);
else
- { DEBUG(D_auth) debug_printf("tls auth not succeeded\n"); }
+ {
+ smtp_cmd_data = NULL;
+
+ if (smtp_in_auth(au, &s, &ss) == OK)
+ { DEBUG(D_auth) debug_printf("tls auth succeeded\n"); }
+ else
+ { DEBUG(D_auth) debug_printf("tls auth not succeeded\n"); }
+ }
break;
}
}
#endif
- switch(smtp_read_command(TRUE))
+#ifdef TCP_QUICKACK
+ if (smtp_in) /* Avoid pure-ACKs while in cmd pingpong phase */
+ (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK,
+ US &off, sizeof(off));
+#endif
+
+ switch(smtp_read_command(TRUE, GETC_BUFFER_UNLIMITED))
{
/* The AUTH command is not permitted to occur inside a transaction, and may
occur successfully only once per connection. Actually, that isn't quite
AUTHS will eventually hit the nonmail threshold. */
case AUTH_CMD:
- HAD(SCH_AUTH);
- authentication_failed = TRUE;
- cmd_list[CMD_LIST_AUTH].is_mail_cmd = FALSE;
+ HAD(SCH_AUTH);
+ authentication_failed = TRUE;
+ 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;
- }
+ 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 */
+ /* Check the ACL */
- 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;
- }
+ 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;
+ }
- /* Find the name of the requested authentication mechanism. */
+ /* Find the name of the requested authentication mechanism. */
- s = smtp_cmd_data;
- 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++;
- }
+ s = smtp_cmd_data;
+ 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
- name and move the pointer on to any data that may be present. */
+ /* If not at the end of the line, we must be at white space. Terminate the
+ name and move the pointer on to any data that may be present. */
- if (*smtp_cmd_data != 0)
- {
- *smtp_cmd_data++ = 0;
- while (isspace(*smtp_cmd_data)) smtp_cmd_data++;
- }
+ if (*smtp_cmd_data != 0)
+ {
+ *smtp_cmd_data++ = 0;
+ while (isspace(*smtp_cmd_data)) smtp_cmd_data++;
+ }
- /* Search for an authentication mechanism which is configured for use
- as a server and which has been advertised (unless, sigh, allow_auth_
- unadvertised is set). */
+ /* Search for an authentication mechanism which is configured for use
+ as a server and which has been advertised (unless, sigh, allow_auth_
+ unadvertised is set). */
- for (au = auths; au; au = au->next)
- if (strcmpic(s, au->public_name) == 0 && au->server &&
- (au->advertised || allow_auth_unadvertised))
- break;
+ for (au = auths; au; au = au->next)
+ if (strcmpic(s, au->public_name) == 0 && au->server &&
+ (au->advertised || allow_auth_unadvertised))
+ break;
- if (au)
- {
- c = smtp_in_auth(au, &s, &ss);
+ if (au)
+ {
+ c = smtp_in_auth(au, &s, &ss);
- smtp_printf("%s\r\n", s);
- if (c != OK)
- log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
- au->name, host_and_ident(FALSE), ss);
- }
- else
- done = synprot_error(L_smtp_protocol_error, 504, NULL,
- string_sprintf("%s authentication mechanism not supported", s));
+ smtp_printf("%s\r\n", FALSE, s);
+ if (c != OK)
+ log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s",
+ au->name, host_and_ident(FALSE), ss);
+ }
+ else
+ done = synprot_error(L_smtp_protocol_error, 504, NULL,
+ string_sprintf("%s authentication mechanism not supported", s));
- break; /* AUTH_CMD */
+ break; /* AUTH_CMD */
/* 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
it did the reset first. */
case HELO_CMD:
- HAD(SCH_HELO);
- hello = US"HELO";
- esmtp = FALSE;
- goto HELO_EHLO;
+ HAD(SCH_HELO);
+ hello = US"HELO";
+ esmtp = FALSE;
+ goto HELO_EHLO;
case EHLO_CMD:
- HAD(SCH_EHLO);
- hello = US"EHLO";
- esmtp = TRUE;
+ HAD(SCH_EHLO);
+ hello = US"EHLO";
+ esmtp = TRUE;
HELO_EHLO: /* Common code for HELO and EHLO */
- cmd_list[CMD_LIST_HELO].is_mail_cmd = FALSE;
- cmd_list[CMD_LIST_EHLO].is_mail_cmd = FALSE;
+ cmd_list[CMD_LIST_HELO].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
- successful check causes the argument to be saved in malloc store. */
+ /* Reject the HELO if its argument was invalid or non-existent. A
+ successful check causes the argument to be saved in malloc store. */
- if (!check_helo(smtp_cmd_data))
- {
- smtp_printf("501 Syntactically invalid %s argument(s)\r\n", hello);
+ if (!check_helo(smtp_cmd_data))
+ {
+ smtp_printf("501 Syntactically invalid %s argument(s)\r\n", FALSE, hello);
- log_write(0, LOG_MAIN|LOG_REJECT, "rejected %s from %s: syntactically "
- "invalid argument(s): %s", hello, host_and_ident(FALSE),
- (*smtp_cmd_argument == 0)? US"(no argument given)" :
- string_printing(smtp_cmd_argument));
+ log_write(0, LOG_MAIN|LOG_REJECT, "rejected %s from %s: syntactically "
+ "invalid argument(s): %s", hello, host_and_ident(FALSE),
+ *smtp_cmd_argument == 0 ? US"(no argument given)" :
+ string_printing(smtp_cmd_argument));
- if (++synprot_error_count > smtp_max_synprot_errors)
- {
- 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));
- done = 1;
- }
+ if (++synprot_error_count > smtp_max_synprot_errors)
+ {
+ 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));
+ done = 1;
+ }
- break;
- }
+ break;
+ }
- /* 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
- 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
- is set, ensure that the HELO name matches the actual host. If helo_verify
- is set, do the same check, but softly. */
+ /* 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
+ 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
+ is set, ensure that the HELO name matches the actual host. If helo_verify
+ is set, do the same check, but softly. */
- if (!sender_host_unknown)
- {
- BOOL old_helo_verified = helo_verified;
- uschar *p = smtp_cmd_data;
+ if (!sender_host_unknown)
+ {
+ BOOL old_helo_verified = helo_verified;
+ uschar *p = smtp_cmd_data;
- while (*p != 0 && !isspace(*p)) { *p = tolower(*p); p++; }
- *p = 0;
+ while (*p != 0 && !isspace(*p)) { *p = tolower(*p); p++; }
+ *p = 0;
- /* Force a reverse lookup if HELO quoted something in helo_lookup_domains
- because otherwise the log can be confusing. */
+ /* Force a reverse lookup if HELO quoted something in helo_lookup_domains
+ because otherwise the log can be confusing. */
- if (sender_host_name == NULL &&
- (deliver_domain = sender_helo_name, /* set $domain */
- match_isinlist(sender_helo_name, CUSS &helo_lookup_domains, 0,
- &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL)) == OK)
- (void)host_name_lookup();
+ if ( !sender_host_name
+ && (deliver_domain = sender_helo_name, /* set $domain */
+ match_isinlist(sender_helo_name, CUSS &helo_lookup_domains, 0,
+ &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL)) == OK)
+ (void)host_name_lookup();
- /* Rebuild the fullhost info to include the HELO name (and the real name
- if it was looked up.) */
+ /* Rebuild the fullhost info to include the HELO name (and the real name
+ if it was looked up.) */
- host_build_sender_fullhost(); /* Rebuild */
- set_process_info("handling%s incoming connection from %s",
- (tls_in.active >= 0)? " TLS" : "", host_and_ident(FALSE));
+ host_build_sender_fullhost(); /* Rebuild */
+ set_process_info("handling%s incoming connection from %s",
+ tls_in.active.sock >= 0 ? " TLS" : "", host_and_ident(FALSE));
- /* 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,
- (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
- now obsolescent, since the verification can now be requested selectively
- at ACL time. */
+ /* 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,
+ (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
+ now obsolescent, since the verification can now be requested selectively
+ at ACL time. */
- helo_verified = helo_verify_failed = sender_helo_dnssec = FALSE;
- if (helo_required || helo_verify)
- {
- BOOL tempfail = !smtp_verify_helo();
- if (!helo_verified)
- {
- if (helo_required)
- {
- smtp_printf("%d %s argument does not match calling host\r\n",
- tempfail? 451 : 550, hello);
- log_write(0, LOG_MAIN|LOG_REJECT, "%srejected \"%s %s\" from %s",
- tempfail? "temporarily " : "",
- hello, sender_helo_name, host_and_ident(FALSE));
- 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);
- }
- }
- }
+ helo_verified = helo_verify_failed = sender_helo_dnssec = FALSE;
+ if (helo_required || helo_verify)
+ {
+ BOOL tempfail = !smtp_verify_helo();
+ if (!helo_verified)
+ {
+ if (helo_required)
+ {
+ 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? "temporarily " : "",
+ hello, sender_helo_name, host_and_ident(FALSE));
+ 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);
+ }
+ }
+ }
-#ifdef EXPERIMENTAL_SPF
- /* set up SPF context */
- spf_init(sender_helo_name, sender_host_address);
+#ifdef SUPPORT_SPF
+ /* set up SPF context */
+ spf_init(sender_helo_name, sender_host_address);
#endif
- /* Apply an ACL check if one is defined; afterwards, recheck
- synchronization in case the client started sending in a delay. */
+ /* Apply an ACL check if one is defined; afterwards, recheck
+ synchronization in case the client started sending in a delay. */
- if (acl_smtp_helo)
- if ((rc = acl_check(ACL_WHERE_HELO, NULL, acl_smtp_helo,
- &user_msg, &log_msg)) != OK)
- {
- done = smtp_handle_acl_fail(ACL_WHERE_HELO, rc, user_msg, log_msg);
- sender_helo_name = NULL;
- host_build_sender_fullhost(); /* Rebuild */
- break;
- }
- else if (!check_sync()) goto SYNC_FAILURE;
+ if (acl_smtp_helo)
+ if ((rc = acl_check(ACL_WHERE_HELO, NULL, acl_smtp_helo,
+ &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);
+ sender_helo_name = NULL;
+ }
+ host_build_sender_fullhost(); /* Rebuild */
+ break;
+ }
+ else if (!check_sync()) goto SYNC_FAILURE;
- /* Generate an OK reply. The default string includes the ident if present,
- and also the IP address if present. Reflecting back the ident is intended
- as a deterrent to mail forgers. For maximum efficiency, and also because
- some broken systems expect each response to be in a single packet, arrange
- that the entire reply is sent in one write(). */
+ /* Generate an OK reply. The default string includes the ident if present,
+ and also the IP address if present. Reflecting back the ident is intended
+ as a deterrent to mail forgers. For maximum efficiency, and also because
+ some broken systems expect each response to be in a single packet, arrange
+ that the entire reply is sent in one write(). */
- auth_advertised = FALSE;
- pipelining_advertised = FALSE;
+ auth_advertised = FALSE;
+ smtp_in_pipelining_advertised = FALSE;
#ifdef SUPPORT_TLS
- tls_advertised = FALSE;
+ tls_advertised = FALSE;
+# ifdef EXPERIMENTAL_REQUIRETLS
+ requiretls_advertised = FALSE;
+# endif
#endif
- dsn_advertised = FALSE;
+ dsn_advertised = FALSE;
#ifdef SUPPORT_I18N
- smtputf8_advertised = FALSE;
+ smtputf8_advertised = FALSE;
#endif
- smtp_code = US"250 "; /* Default response code plus space*/
- if (user_msg == NULL)
- {
- s = string_sprintf("%.3s %s Hello %s%s%s",
- smtp_code,
- smtp_active_hostname,
- (sender_ident == NULL)? US"" : sender_ident,
- (sender_ident == NULL)? US"" : US" at ",
- (sender_host_name == NULL)? sender_helo_name : sender_host_name);
-
- ptr = Ustrlen(s);
- size = ptr + 1;
-
- if (sender_host_address != NULL)
- {
- s = string_catn(s, &size, &ptr, US" [", 2);
- s = string_cat (s, &size, &ptr, sender_host_address);
- s = string_catn(s, &size, &ptr, US"]", 1);
- }
- }
-
- /* A user-supplied EHLO greeting may not contain more than one line. Note
- that the code returned by smtp_message_code() includes the terminating
- whitespace character. */
-
- else
- {
- char *ss;
- int codelen = 4;
- smtp_message_code(&smtp_code, &codelen, &user_msg, NULL, TRUE);
- s = string_sprintf("%.*s%s", codelen, smtp_code, user_msg);
- if ((ss = strpbrk(CS s, "\r\n")) != NULL)
- {
- log_write(0, LOG_MAIN|LOG_PANIC, "EHLO/HELO response must not contain "
- "newlines: message truncated: %s", string_printing(s));
- *ss = 0;
- }
- ptr = Ustrlen(s);
- size = ptr + 1;
- }
+ smtp_code = US"250 "; /* Default response code plus space*/
+ if (!user_msg)
+ {
+ s = string_sprintf("%.3s %s Hello %s%s%s",
+ smtp_code,
+ smtp_active_hostname,
+ sender_ident ? sender_ident : US"",
+ sender_ident ? US" at " : US"",
+ sender_host_name ? sender_host_name : sender_helo_name);
+ g = string_cat(NULL, s);
+
+ if (sender_host_address)
+ {
+ g = string_catn(g, US" [", 2);
+ g = string_cat (g, sender_host_address);
+ g = string_catn(g, US"]", 1);
+ }
+ }
- s = string_catn(s, &size, &ptr, US"\r\n", 2);
+ /* A user-supplied EHLO greeting may not contain more than one line. Note
+ that the code returned by smtp_message_code() includes the terminating
+ whitespace character. */
- /* If we received EHLO, we must create a multiline response which includes
- the functions supported. */
+ else
+ {
+ char *ss;
+ int codelen = 4;
+ smtp_message_code(&smtp_code, &codelen, &user_msg, NULL, TRUE);
+ s = string_sprintf("%.*s%s", codelen, smtp_code, user_msg);
+ if ((ss = strpbrk(CS s, "\r\n")) != NULL)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "EHLO/HELO response must not contain "
+ "newlines: message truncated: %s", string_printing(s));
+ *ss = 0;
+ }
+ g = string_cat(NULL, s);
+ }
- if (esmtp)
- {
- s[3] = '-';
+ g = string_catn(g, US"\r\n", 2);
- /* 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
- 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
- till then, VRFY and EXPN can be used after EHLO when space is short. */
+ /* If we received EHLO, we must create a multiline response which includes
+ the functions supported. */
- if (thismessage_size_limit > 0)
- {
- sprintf(CS big_buffer, "%.3s-SIZE %d\r\n", smtp_code,
- thismessage_size_limit);
- s = string_cat(s, &size, &ptr, big_buffer);
- }
- else
- {
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-SIZE\r\n", 7);
- }
+ if (esmtp)
+ {
+ g->s[3] = '-';
- /* 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
- cannot therefore specify 8BITMIME and remain consistent with the RFCs.
- However, some users want this option simply in order to stop MUAs
- mangling messages that contain top-bit-set characters. It is therefore
- provided as an option. */
+ /* 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
+ 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
+ till then, VRFY and EXPN can be used after EHLO when space is short. */
- if (accept_8bitmime)
- {
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-8BITMIME\r\n", 11);
- }
+ if (thismessage_size_limit > 0)
+ {
+ sprintf(CS big_buffer, "%.3s-SIZE %d\r\n", smtp_code,
+ thismessage_size_limit);
+ g = string_cat(g, big_buffer);
+ }
+ else
+ {
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-SIZE\r\n", 7);
+ }
- /* Advertise DSN support if configured to do so. */
- if (verify_check_host(&dsn_advertise_hosts) != FAIL)
- {
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-DSN\r\n", 6);
- dsn_advertised = TRUE;
- }
+ /* 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
+ cannot therefore specify 8BITMIME and remain consistent with the RFCs.
+ However, some users want this option simply in order to stop MUAs
+ mangling messages that contain top-bit-set characters. It is therefore
+ provided as an option. */
- /* Advertise ETRN if there's an ACL checking whether a host is
- permitted to issue it; a check is made when any host actually tries. */
+ if (accept_8bitmime)
+ {
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-8BITMIME\r\n", 11);
+ }
- if (acl_smtp_etrn != NULL)
- {
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-ETRN\r\n", 7);
- }
+ /* 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"-DSN\r\n", 6);
+ dsn_advertised = TRUE;
+ }
- /* Advertise EXPN if there's an ACL checking whether a host is
- permitted to issue it; a check is made when any host actually tries. */
+ /* Advertise ETRN/VRFY/EXPN if there's are ACL checking whether a host is
+ permitted to issue them; a check is made when any host actually tries. */
- if (acl_smtp_expn != NULL)
- {
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-EXPN\r\n", 7);
- }
+ if (acl_smtp_etrn)
+ {
+ 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
- it is safe to use it, unless advertising is disabled. */
+ /* Exim is quite happy with pipelining, so let the other end know that
+ it is safe to use it, unless advertising is disabled. */
- if (pipelining_enable &&
- verify_check_host(&pipelining_advertise_hosts) == OK)
- {
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-PIPELINING\r\n", 13);
- sync_cmd_limit = NON_SYNC_CMD_PIPELINING;
- pipelining_advertised = TRUE;
- }
+ if (pipelining_enable &&
+ 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;
+ smtp_in_pipelining_advertised = TRUE;
+ }
- /* If any server authentication mechanisms are configured, advertise
- them if the current host is in auth_advertise_hosts. The problem with
- advertising always is that some clients then require users to
- authenticate (and aren't configurable otherwise) even though it may not
- be necessary (e.g. if the host is in host_accept_relay).
+ /* If any server authentication mechanisms are configured, advertise
+ them if the current host is in auth_advertise_hosts. The problem with
+ advertising always is that some clients then require users to
+ authenticate (and aren't configurable otherwise) even though it may not
+ be necessary (e.g. if the host is in host_accept_relay).
- RFC 2222 states that SASL mechanism names contain only upper case
- letters, so output the names in upper case, though we actually recognize
- them in either case in the AUTH command. */
+ RFC 2222 states that SASL mechanism names contain only upper case
+ letters, so output the names in upper case, though we actually recognize
+ them in either case in the AUTH command. */
- if ( auths
-#if defined(SUPPORT_TLS) && defined(AUTH_TLS)
- && !sender_host_authenticated
+ if ( auths
+#ifdef AUTH_TLS
+ && !sender_host_authenticated
#endif
- && verify_check_host(&auth_advertise_hosts) == OK
- )
- {
- auth_instance *au;
- BOOL first = TRUE;
- for (au = auths; au; au = au->next)
- if (au->server && (au->advertise_condition == NULL ||
- expand_check_condition(au->advertise_condition, au->name,
- US"authenticator")))
+ && verify_check_host(&auth_advertise_hosts) == OK
+ )
+ {
+ auth_instance *au;
+ BOOL first = TRUE;
+ for (au = auths; au; au = au->next)
{
- int saveptr;
- if (first)
+ au->advertised = FALSE;
+ if (au->server)
{
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-AUTH", 5);
- first = FALSE;
- auth_advertised = TRUE;
+ DEBUG(D_auth+D_expand) debug_printf_indent(
+ "Evaluating advertise_condition for %s athenticator\n",
+ au->public_name);
+ if ( !au->advertise_condition
+ || expand_check_condition(au->advertise_condition, au->name,
+ US"authenticator")
+ )
+ {
+ int saveptr;
+ if (first)
+ {
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-AUTH", 5);
+ first = FALSE;
+ 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 = ptr;
- s = string_catn(s, &size, &ptr, US" ", 1);
- s = string_cat (s, &size, &ptr, au->public_name);
- while (++saveptr < ptr) s[saveptr] = toupper(s[saveptr]);
- au->advertised = TRUE;
}
- else
- au->advertised = FALSE;
- if (!first) s = string_catn(s, &size, &ptr, 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)
- {
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-CHUNKING\r\n", 11);
- chunking_offered = TRUE;
- chunking_state = CHUNKING_OFFERED;
- }
+ if (verify_check_host(&chunking_advertise_hosts) != FAIL)
+ {
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-CHUNKING\r\n", 11);
+ chunking_offered = TRUE;
+ chunking_state = CHUNKING_OFFERED;
+ }
- /* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer)
- 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
- secure connection. */
+ /* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer)
+ 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
+ secure connection. */
#ifdef SUPPORT_TLS
- if (tls_in.active < 0 &&
- verify_check_host(&tls_advertise_hosts) != FAIL)
- {
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-STARTTLS\r\n", 11);
- tls_advertised = TRUE;
- }
+ if (tls_in.active.sock < 0 &&
+ verify_check_host(&tls_advertise_hosts) != FAIL)
+ {
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-STARTTLS\r\n", 11);
+ 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);
+ requiretls_advertised = TRUE;
+ }
+# endif
#endif
#ifndef DISABLE_PRDR
- /* Per Recipient Data Response, draft by Eric A. Hall extending RFC */
- if (prdr_enable)
- {
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-PRDR\r\n", 7);
- }
+ /* Per Recipient Data Response, draft by Eric A. Hall extending RFC */
+ if (prdr_enable)
+ {
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-PRDR\r\n", 7);
+ }
#endif
#ifdef SUPPORT_I18N
- if ( accept_8bitmime
- && verify_check_host(&smtputf8_advertise_hosts) != FAIL)
- {
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-SMTPUTF8\r\n", 11);
- smtputf8_advertised = TRUE;
- }
+ if ( accept_8bitmime
+ && verify_check_host(&smtputf8_advertise_hosts) != FAIL)
+ {
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-SMTPUTF8\r\n", 11);
+ smtputf8_advertised = TRUE;
+ }
#endif
- /* Finish off the multiline reply with one that is always available. */
+ /* Finish off the multiline reply with one that is always available. */
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US" HELP\r\n", 7);
- }
-
- /* Terminate the string (for debug), write it, and note that HELO/EHLO
- has been seen. */
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US" HELP\r\n", 7);
+ }
- s[ptr] = 0;
+ /* Terminate the string (for debug), write it, and note that HELO/EHLO
+ has been seen. */
#ifdef SUPPORT_TLS
- if (tls_in.active >= 0) (void)tls_write(TRUE, s, ptr); else
+ if (tls_in.active.sock >= 0) (void)tls_write(NULL, g->s, g->ptr, FALSE); else
#endif
- {
- int i = fwrite(s, 1, ptr, smtp_out); i = i; /* compiler quietening */
- }
- DEBUG(D_receive)
- {
- uschar *cr;
- while ((cr = Ustrchr(s, '\r')) != NULL) /* lose CRs */
- memmove(cr, cr + 1, (ptr--) - (cr - s));
- debug_printf("SMTP>> %s", s);
- }
- helo_seen = TRUE;
+ {
+ int i = fwrite(g->s, 1, g->ptr, smtp_out); i = i; /* compiler quietening */
+ }
+ DEBUG(D_receive)
+ {
+ uschar *cr;
- /* Reset the protocol and the state, abandoning any previous message. */
- received_protocol =
- (sender_host_address ? protocols : protocols_local)
- [ (esmtp
- ? pextend + (sender_host_authenticated ? pauthed : 0)
- : pnormal)
- + (tls_in.active >= 0 ? pcrpted : 0)
- ];
- smtp_reset(reset_point);
- toomany = FALSE;
- break; /* HELO/EHLO */
+ (void) string_from_gstring(g);
+ while ((cr = Ustrchr(g->s, '\r')) != NULL) /* lose CRs */
+ memmove(cr, cr + 1, (g->ptr--) - (cr - g->s));
+ debug_printf("SMTP>> %s", g->s);
+ }
+ helo_seen = TRUE;
+
+ /* Reset the protocol and the state, abandoning any previous message. */
+ received_protocol =
+ (sender_host_address ? protocols : protocols_local)
+ [ (esmtp
+ ? pextend + (sender_host_authenticated ? pauthed : 0)
+ : pnormal)
+ + (tls_in.active.sock >= 0 ? pcrpted : 0)
+ ];
+ cancel_cutthrough_connection(TRUE, US"sent EHLO response");
+ smtp_reset(reset_point);
+ toomany = FALSE;
+ break; /* HELO/EHLO */
/* The MAIL command requires an address as an operand. All we do
it is the canonical extracted address which is all that is kept. */
case MAIL_CMD:
- HAD(SCH_MAIL);
- smtp_mailcmd_count++; /* Count for limit and ratelimit */
- was_rej_mail = TRUE; /* Reset if accepted */
- env_mail_type_t * mail_args; /* Sanity check & validate args */
+ HAD(SCH_MAIL);
+ smtp_mailcmd_count++; /* Count for limit and ratelimit */
+ was_rej_mail = TRUE; /* Reset if accepted */
+ env_mail_type_t * mail_args; /* Sanity check & validate args */
- if (helo_required && !helo_seen)
- {
- smtp_printf("503 HELO or EHLO required\r\n");
- log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL from %s: no "
- "HELO/EHLO given", host_and_ident(FALSE));
- break;
- }
+ 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)
- {
- done = synprot_error(L_smtp_protocol_error, 503, NULL,
- US"sender already given");
- break;
- }
+ if (sender_address)
+ {
+ done = synprot_error(L_smtp_protocol_error, 503, NULL,
+ US"sender already given");
+ break;
+ }
- if (smtp_cmd_data[0] == 0)
- {
- done = synprot_error(L_smtp_protocol_error, 501, NULL,
- US"MAIL must have an address operand");
- break;
- }
+ if (!*smtp_cmd_data)
+ {
+ done = synprot_error(L_smtp_protocol_error, 501, NULL,
+ US"MAIL must have an address operand");
+ break;
+ }
- /* Check to see if the limit for messages per connection would be
- exceeded by accepting further messages. */
+ /* Check to see if the limit for messages per connection would be
+ exceeded by accepting further messages. */
- if (smtp_accept_max_per_connection > 0 &&
- smtp_mailcmd_count > smtp_accept_max_per_connection)
- {
- smtp_printf("421 too many messages in this connection\r\n");
- 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
- obviously need to throw away any previous data. */
+ if (smtp_accept_max_per_connection > 0 &&
+ 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;
+ }
- smtp_reset(reset_point);
- toomany = FALSE;
- sender_data = recipient_data = NULL;
+ /* Reset for start of message - even if this is going to fail, we
+ obviously need to throw away any previous data. */
- /* 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(;;)
- {
- uschar *name, *value, *end;
- unsigned long int size;
- BOOL arg_error = FALSE;
+ /* Loop, checking for ESMTP additions to the MAIL FROM command. */
- if (!extract_option(&name, &value)) break;
+ if (esmtp) for(;;)
+ {
+ uschar *name, *value, *end;
+ unsigned long int size;
+ BOOL arg_error = FALSE;
- 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;
+ if (!extract_option(&name, &value)) break;
- switch(mail_args->value)
- {
- /* Handle SIZE= by reading the value. We don't do the check till later,
- in order to be able to log the sender address on failure. */
- case ENV_MAIL_OPT_SIZE:
- if (((size = Ustrtoul(value, &end, 10)), *end == 0))
- {
- if ((size == ULONG_MAX && errno == ERANGE) || size > INT_MAX)
- size = INT_MAX;
- message_size = (int)size;
- }
- else
- arg_error = TRUE;
- 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;
- /* If this session was initiated with EHLO and accept_8bitmime is set,
- 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 not
- 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 "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
+ switch(mail_args->value)
+ {
+ /* Handle SIZE= by reading the value. We don't do the check till later,
+ in order to be able to log the sender address on failure. */
+ case ENV_MAIL_OPT_SIZE:
+ if (((size = Ustrtoul(value, &end, 10)), *end == 0))
{
- 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);
+ if ((size == ULONG_MAX && errno == ERANGE) || size > INT_MAX)
+ size = INT_MAX;
+ message_size = (int)size;
+ }
+ else
+ arg_error = TRUE;
break;
- }
- arg_error = TRUE;
- break;
- /* Handle the two DSN options, but only if configured to do so (which
- will have caused "DSN" to be given in the EHLO response). The code itself
- is included only if configured in at build time. */
+ /* If this session was initiated with EHLO and accept_8bitmime is set,
+ 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 not
+ 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 "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;
+ 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:
- if (dsn_advertised)
- {
- /* Check if RET has already been set */
- if (dsn_ret > 0)
- {
- 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)
+ /* Handle the two DSN options, but only if configured to do so (which
+ will have caused "DSN" to be given in the EHLO response). The code itself
+ is included only if configured in at build time. */
+
+ case ENV_MAIL_OPT_RET:
+ if (dsn_advertised)
{
- synprot_error(L_smtp_syntax_error, 501, NULL,
- US"Value for RET is invalid");
- goto COMMAND_LOOP;
+ /* Check if RET has already been set */
+ if (dsn_ret > 0)
+ {
+ 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;
- case ENV_MAIL_OPT_ENVID:
- if (dsn_advertised)
- {
- /* Check if the dsn envid has been already set */
- if (dsn_envid != NULL)
+ break;
+ case ENV_MAIL_OPT_ENVID:
+ if (dsn_advertised)
{
- synprot_error(L_smtp_syntax_error, 501, NULL,
- US"ENVID can be specified once only");
- goto COMMAND_LOOP;
+ /* Check if the dsn envid has been already set */
+ if (dsn_envid)
+ {
+ 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);
- 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 unless
- 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;
+ break;
- 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 == NULL)
- {
- ignore_msg = US"client not authenticated";
- rc = (sender_host_authenticated != NULL)? OK : FAIL;
- }
- else
- {
- ignore_msg = US"rejected by ACL";
- rc = acl_check(ACL_WHERE_MAILAUTH, NULL, acl_smtp_mailauth,
- &user_msg, &log_msg);
- }
+ /* 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 unless
+ 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;
- 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
- overrides for error message */
-
- default:
+ if (auth_xtextdecode(value, &authenticated_sender) < 0)
+ {
+ /* Put back terminator overrides for error message */
value[-1] = '=';
name[-1] = ' ';
- (void)smtp_handle_acl_fail(ACL_WHERE_MAILAUTH, rc, user_msg,
- log_msg);
+ done = synprot_error(L_smtp_syntax_error, 501, NULL,
+ US"invalid data for AUTH");
goto COMMAND_LOOP;
- }
- }
- break;
+ }
+ 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
+ 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
- case ENV_MAIL_OPT_PRDR:
- if (prdr_enable)
- prdr_requested = TRUE;
- break;
+ case ENV_MAIL_OPT_PRDR:
+ if (prdr_enable)
+ prdr_requested = TRUE;
+ break;
#endif
#ifdef SUPPORT_I18N
- case ENV_MAIL_OPT_UTF8:
- if (smtputf8_advertised)
- {
+ case ENV_MAIL_OPT_UTF8:
+ if (!smtputf8_advertised)
+ {
+ 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");
message_smtputf8 = allow_utf8_domains = TRUE;
- received_protocol = string_sprintf("utf8%s", received_protocol);
+ if (Ustrncmp(received_protocol, US"utf8", 4) != 0)
+ {
+ 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 (!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
- /* 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);
- }
- /* Break out of for loop if switch() had bad argument or
- when start of the email address is reached */
- if (arg_error) break;
- }
+ /* No valid option. Stick back the terminator characters and break
+ the loop. Do the name-terminator second as extract_option sets
+ value==name when it found no equal-sign.
+ An error for a malformed address will occur. */
+ 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
- delay, and update it for next time, provided this is a limited host. */
+ default: assert(0);
+ }
+ /* 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 &&
- verify_check_host(&smtp_ratelimit_hosts) == OK)
- {
- 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;
- }
+#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
+ if (tls_requiretls & REQUIRETLS_MSG)
+ {
+ /* Ensure headers-only bounces whether a RET option was given or not. */
- /* Now extract the address, first applying any SMTP-time rewriting. The
- TRUE flag allows "<>" as a sender address. */
+ DEBUG(D_receive) if (dsn_ret == dsn_ret_full)
+ debug_printf("requiretls override: dsn_ret_full -> dsn_ret_hdrs\n");
+ dsn_ret = dsn_ret_hdrs;
+ }
+#endif
- raw_sender = rewrite_existflags & rewrite_smtp
- ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
- global_rewrite_rules)
- : smtp_cmd_data;
+ /* If we have passed the threshold for rate limiting, apply the current
+ delay, and update it for next time, provided this is a limited host. */
- raw_sender =
- parse_extract_address(raw_sender, &errmess, &start, &end, &sender_domain,
- TRUE);
+ if (smtp_mailcmd_count > smtp_rlm_threshold &&
+ verify_check_host(&smtp_ratelimit_hosts) == OK)
+ {
+ 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;
+ }
- if (!raw_sender)
- {
- done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
- break;
- }
+ /* Now extract the address, first applying any SMTP-time rewriting. The
+ TRUE flag allows "<>" as a sender address. */
- sender_address = raw_sender;
+ raw_sender = rewrite_existflags & rewrite_smtp
+ ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+ global_rewrite_rules)
+ : smtp_cmd_data;
- /* 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
- can be logged. */
+ raw_sender =
+ parse_extract_address(raw_sender, &errmess, &start, &end, &sender_domain,
+ TRUE);
- if (thismessage_size_limit > 0 && message_size > thismessage_size_limit)
- {
- smtp_printf("552 Message size exceeds maximum permitted\r\n");
- 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 (!raw_sender)
+ {
+ done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
+ break;
+ }
- /* 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_limit
- plus the current message - i.e. we accept the message only if it won't
- 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.
- 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");
- sender_address = NULL;
- break;
- }
+ sender_address = raw_sender;
- /* If sender_address is unqualified, reject it, unless this is a locally
- 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 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
+ can be logged. */
- if (sender_domain == 0 && sender_address[0] != 0)
- {
- if (allow_unqualified_sender)
- {
- 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",
- 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;
- }
- }
+ if (thismessage_size_limit > 0 && message_size > thismessage_size_limit)
+ {
+ 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;
+ }
- /* 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. */
+ /* 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_limit
+ plus the current message - i.e. we accept the message only if it won't
+ 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.
+ 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 (acl_smtp_mail)
- {
- rc = acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg);
- if (rc == OK && !pipelining_advertised && !check_sync())
- goto SYNC_FAILURE;
- }
- else
- rc = OK;
+ /* If sender_address is unqualified, reject it, unless this is a locally
+ 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 (rc == OK || rc == DISCARD)
- {
- if (!user_msg)
- smtp_printf("%s%s%s", US"250 OK",
- #ifndef DISABLE_PRDR
- prdr_requested ? US", PRDR Requested" : US"",
- #else
- US"",
- #endif
- US"\r\n");
+ if (!sender_domain && *sender_address)
+ if (allow_unqualified_sender)
+ {
+ 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,
+ 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 && !smtp_in_pipelining_advertised && !check_sync())
+ goto SYNC_FAILURE;
+ }
else
- {
- #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;
- 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;
+ rc = OK;
+
+ if (rc == OK || rc == DISCARD)
+ {
+ BOOL more = pipeline_response();
+
+ if (!user_msg)
+ smtp_printf("%s%s%s", more, US"250 OK",
+ #ifndef DISABLE_PRDR
+ prdr_requested ? US", PRDR Requested" : US"",
+ #else
+ US"",
+ #endif
+ US"\r\n");
+ else
+ {
+ #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;
+ 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
are not used, as we keep only the extracted address. */
case RCPT_CMD:
- HAD(SCH_RCPT);
- rcpt_count++;
- was_rcpt = rcpt_in_progress = TRUE;
+ HAD(SCH_RCPT);
+ rcpt_count++;
+ was_rcpt = rcpt_in_progress = TRUE;
- /* There must be a sender address; if the sender was rejected and
- 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
- get the same treatment. */
+ /* There must be a sender address; if the sender was rejected and
+ 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
+ get the same treatment. */
- if (sender_address == NULL)
- {
- if (pipelining_advertised && last_was_rej_mail)
- {
- smtp_printf("503 sender not yet given\r\n");
- was_rej_mail = TRUE;
- }
- else
- {
- done = synprot_error(L_smtp_protocol_error, 503, NULL,
- US"sender not yet given");
- was_rcpt = FALSE; /* Not a valid RCPT */
- }
- rcpt_fail_count++;
- break;
- }
+ if (sender_address == NULL)
+ {
+ if (smtp_in_pipelining_advertised && last_was_rej_mail)
+ {
+ smtp_printf("503 sender not yet given\r\n", FALSE);
+ was_rej_mail = TRUE;
+ }
+ else
+ {
+ done = synprot_error(L_smtp_protocol_error, 503, NULL,
+ US"sender not yet given");
+ was_rcpt = FALSE; /* Not a valid RCPT */
+ }
+ rcpt_fail_count++;
+ break;
+ }
- /* Check for an operand */
+ /* Check for an operand */
- if (smtp_cmd_data[0] == 0)
- {
- done = synprot_error(L_smtp_syntax_error, 501, NULL,
- US"RCPT must have an address operand");
- rcpt_fail_count++;
- break;
- }
+ if (smtp_cmd_data[0] == 0)
+ {
+ done = synprot_error(L_smtp_syntax_error, 501, NULL,
+ US"RCPT must have an address operand");
+ rcpt_fail_count++;
+ break;
+ }
- /* Set the DSN flags orcpt and dsn_flags from the session*/
- orcpt = NULL;
- flags = 0;
+ /* Set the DSN flags orcpt and dsn_flags from the session*/
+ orcpt = NULL;
+ flags = 0;
- if (esmtp) for(;;)
- {
- uschar *name, *value;
+ if (esmtp) for(;;)
+ {
+ uschar *name, *value;
- if (!extract_option(&name, &value))
- break;
+ if (!extract_option(&name, &value))
+ break;
- if (dsn_advertised && strcmpic(name, US"ORCPT") == 0)
- {
- /* Check whether orcpt has been already set */
- if (orcpt)
+ if (dsn_advertised && strcmpic(name, US"ORCPT") == 0)
{
- synprot_error(L_smtp_syntax_error, 501, NULL,
- US"ORCPT can be specified once only");
- goto COMMAND_LOOP;
- }
- orcpt = string_copy(value);
- DEBUG(D_receive) debug_printf("DSN orcpt: %s\n", orcpt);
- }
+ /* Check whether orcpt has been already set */
+ if (orcpt)
+ {
+ done = synprot_error(L_smtp_syntax_error, 501, NULL,
+ US"ORCPT can be specified once only");
+ 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)
- {
- /* Check if the notify flags have been already set */
- if (flags > 0)
+ else if (dsn_advertised && strcmpic(name, US"NOTIFY") == 0)
{
- synprot_error(L_smtp_syntax_error, 501, NULL,
- US"NOTIFY can be specified once only");
- goto COMMAND_LOOP;
- }
- if (strcmpic(value, US"NEVER") == 0)
- flags |= rf_notify_never;
- else
- {
- uschar *p = value;
- while (*p != 0)
- {
- uschar *pp = p;
- while (*pp != 0 && *pp != ',') pp++;
- 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
+ /* Check if the notify flags have been already set */
+ if (flags > 0)
+ {
+ done = synprot_error(L_smtp_syntax_error, 501, NULL,
+ US"NOTIFY can be specified once only");
+ goto COMMAND_LOOP;
+ }
+ if (strcmpic(value, US"NEVER") == 0)
+ flags |= rf_notify_never;
+ else
+ {
+ uschar *p = value;
+ while (*p != 0)
{
- /* Catch any strange values */
- 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", flags);
- }
- }
+ uschar *pp = p;
+ while (*pp != 0 && *pp != ',') pp++;
+ 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 */
+ 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", flags);
+ }
+ }
- /* Unknown option. Stick back the terminator characters and break
- the loop. An error for a malformed address will occur. */
+ /* Unknown option. Stick back the terminator characters and break
+ the loop. An error for a malformed address will occur. */
- else
- {
- DEBUG(D_receive) debug_printf("Invalid RCPT option: %s : %s\n", name, value);
- name[-1] = ' ';
- value[-1] = '=';
- break;
- }
- }
+ else
+ {
+ DEBUG(D_receive) debug_printf("Invalid RCPT option: %s : %s\n", name, value);
+ name[-1] = ' ';
+ value[-1] = '=';
+ break;
+ }
+ }
- /* Apply SMTP rewriting then extract the working address. Don't allow "<>"
- as a recipient address */
+ /* Apply SMTP rewriting then extract the working address. Don't allow "<>"
+ as a recipient address */
- recipient = rewrite_existflags & rewrite_smtp
- ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
- global_rewrite_rules)
- : smtp_cmd_data;
+ recipient = rewrite_existflags & rewrite_smtp
+ ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+ global_rewrite_rules)
+ : smtp_cmd_data;
- if (!(recipient = parse_extract_address(recipient, &errmess, &start, &end,
- &recipient_domain, FALSE)))
- {
- done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
- rcpt_fail_count++;
- break;
- }
+ if (!(recipient = parse_extract_address(recipient, &errmess, &start, &end,
+ &recipient_domain, FALSE)))
+ {
+ done = synprot_error(L_smtp_syntax_error, 501, smtp_cmd_data, errmess);
+ rcpt_fail_count++;
+ break;
+ }
- /* If the recipient address is unqualified, reject it, unless this is a
- locally generated message. However, unqualified addresses are permitted
- 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,
- really. The flag is set at the start of the SMTP connection.
+ /* If the recipient address is unqualified, reject it, unless this is a
+ locally generated message. However, unqualified addresses are permitted
+ 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,
+ really. The flag is set at the start of the SMTP connection.
- RFC 1123 talks about supporting "the reserved mailbox postmaster"; I always
- assumed this meant "reserved local part", but the revision of RFC 821 and
- friends now makes it absolutely clear that it means *mailbox*. Consequently
- we must always qualify this address, regardless. */
+ RFC 1123 talks about supporting "the reserved mailbox postmaster"; I always
+ assumed this meant "reserved local part", but the revision of RFC 821 and
+ friends now makes it absolutely clear that it means *mailbox*. Consequently
+ we must always qualify this address, regardless. */
- if (recipient_domain == 0)
- if (!(recipient_domain = qualify_recipient(&recipient, smtp_cmd_data,
- US"recipient")))
- {
- rcpt_fail_count++;
- break;
- }
+ if (!recipient_domain)
+ if (!(recipient_domain = qualify_recipient(&recipient, smtp_cmd_data,
+ US"recipient")))
+ {
+ rcpt_fail_count++;
+ break;
+ }
- /* Check maximum allowed */
+ /* Check maximum allowed */
- if (rcpt_count > recipients_max && recipients_max > 0)
- {
- if (recipients_max_reject)
- {
- rcpt_fail_count++;
- smtp_printf("552 too many recipients\r\n");
- 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");
- 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 (rcpt_count > recipients_max && recipients_max > 0)
+ {
+ 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;
- break;
- }
+ toomany = TRUE;
+ break;
+ }
- /* If we have passed the threshold for rate limiting, apply the current
- delay, and update it for next time, provided this is a limited host. */
+ /* If we have passed the threshold for rate limiting, apply the current
+ delay, and update it for next time, provided this is a limited host. */
- if (rcpt_count > smtp_rlr_threshold &&
- verify_check_host(&smtp_ratelimit_hosts) == OK)
- {
- DEBUG(D_receive) debug_printf("rate limit RCPT: delay %.3g sec\n",
- smtp_delay_rcpt/1000.0);
- 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 (rcpt_count > smtp_rlr_threshold &&
+ verify_check_host(&smtp_ratelimit_hosts) == OK)
+ {
+ DEBUG(D_receive) debug_printf("rate limit RCPT: delay %.3g sec\n",
+ smtp_delay_rcpt/1000.0);
+ 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
- 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
- afterwards, unless pipelining was advertised. */
+ /* If the MAIL ACL discarded all the recipients, we bypass ACL checking
+ 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
+ afterwards, unless pipelining was advertised. */
- if (recipients_discarded) rc = DISCARD; else
- {
- rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg,
- &log_msg);
- if (rc == OK && !pipelining_advertised && !check_sync())
- goto SYNC_FAILURE;
- }
+ if (recipients_discarded)
+ rc = DISCARD;
+ else
+ if ( (rc = acl_check(ACL_WHERE_RCPT, recipient, acl_smtp_rcpt, &user_msg,
+ &log_msg)) == OK
+ && !smtp_in_pipelining_advertised && !check_sync())
+ goto SYNC_FAILURE;
- /* The ACL was happy */
+ /* The ACL was happy */
- if (rc == OK)
- {
- if (user_msg == NULL) smtp_printf("250 Accepted\r\n");
- else smtp_user_msg(US"250", user_msg);
- receive_add_recipient(recipient, -1);
+ if (rc == OK)
+ {
+ BOOL more = pipeline_response();
+
+ if (user_msg)
+ smtp_user_msg(US"250", user_msg);
+ else
+ smtp_printf("250 Accepted\r\n", more);
+ receive_add_recipient(recipient, -1);
- /* Set the dsn flags in the recipients_list */
- recipients_list[recipients_count-1].orcpt = orcpt;
- recipients_list[recipients_count-1].dsn_flags = flags;
+ /* Set the dsn flags in the recipients_list */
+ recipients_list[recipients_count-1].orcpt = orcpt;
+ recipients_list[recipients_count-1].dsn_flags = flags;
- DEBUG(D_receive) debug_printf("DSN: orcpt: %s flags: %d\n",
- recipients_list[recipients_count-1].orcpt,
- recipients_list[recipients_count-1].dsn_flags);
- }
+ DEBUG(D_receive) debug_printf("DSN: orcpt: %s flags: %d\n",
+ recipients_list[recipients_count-1].orcpt,
+ recipients_list[recipients_count-1].dsn_flags);
+ }
- /* The recipient was discarded */
+ /* The recipient was discarded */
- else if (rc == DISCARD)
- {
- if (user_msg == NULL) smtp_printf("250 Accepted\r\n");
- else smtp_user_msg(US"250", user_msg);
- rcpt_fail_count++;
- discarded = TRUE;
- log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: "
- "discarded by %s ACL%s%s", host_and_ident(TRUE),
- sender_address_unrewritten? sender_address_unrewritten : sender_address,
- smtp_cmd_argument, recipients_discarded? "MAIL" : "RCPT",
- log_msg ? US": " : US"", log_msg ? log_msg : US"");
- }
+ else if (rc == DISCARD)
+ {
+ if (user_msg)
+ smtp_user_msg(US"250", user_msg);
+ else
+ smtp_printf("250 Accepted\r\n", FALSE);
+ rcpt_fail_count++;
+ discarded = TRUE;
+ log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: "
+ "discarded by %s ACL%s%s", host_and_ident(TRUE),
+ sender_address_unrewritten? sender_address_unrewritten : sender_address,
+ smtp_cmd_argument, 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
- {
- if (rc == FAIL) rcpt_fail_count++; else rcpt_defer_count++;
- done = smtp_handle_acl_fail(ACL_WHERE_RCPT, rc, user_msg, log_msg);
- }
- break;
+ else
+ {
+ if (rc == FAIL) rcpt_fail_count++; else rcpt_defer_count++;
+ done = smtp_handle_acl_fail(ACL_WHERE_RCPT, rc, user_msg, log_msg);
+ }
+ break;
/* The DATA command is legal only if it follows successful MAIL FROM
with the DATA rejection (an idea suggested by Tony Finch). */
case BDAT_CMD:
- HAD(SCH_BDAT);
{
int n;
+ HAD(SCH_BDAT);
if (chunking_state != CHUNKING_OFFERED)
{
done = synprot_error(L_smtp_protocol_error, 503, NULL,
chunking_state = strcmpic(smtp_cmd_data+n, US"LAST") == 0
? CHUNKING_LAST : CHUNKING_ACTIVE;
chunking_data_left = chunking_datasize;
+ DEBUG(D_receive) debug_printf("chunking state %d, %d bytes\n",
+ (int)chunking_state, chunking_data_left);
+ /* push the current receive_* function on the "stack", and
+ replace them by bdat_getc(), which in turn will use the lwr_receive_*
+ functions to do the dirty work. */
lwr_receive_getc = receive_getc;
+ lwr_receive_getbuf = receive_getbuf;
lwr_receive_ungetc = receive_ungetc;
+
receive_getc = bdat_getc;
receive_ungetc = bdat_ungetc;
- DEBUG(D_any)
- debug_printf("chunking state %d\n", (int)chunking_state);
+ dot_ends = FALSE;
+
goto DATA_BDAT;
}
case DATA_CMD:
- HAD(SCH_DATA);
-
- DATA_BDAT: /* Common code for DATA and BDAT */
- if (!discarded && recipients_count <= 0)
- {
- if (rcpt_smtp_response_same && rcpt_smtp_response != NULL)
- {
- uschar *code = US"503";
- int len = Ustrlen(rcpt_smtp_response);
- smtp_respond(code, 3, FALSE, US"All RCPT commands were rejected with "
- "this error:");
- /* Responses from smtp_printf() will have \r\n on the end */
- if (len > 2 && rcpt_smtp_response[len-2] == '\r')
- rcpt_smtp_response[len-2] = 0;
- smtp_respond(code, 3, FALSE, rcpt_smtp_response);
- }
- if (pipelining_advertised && last_was_rcpt)
- smtp_printf("503 Valid RCPT command must precede %s\r\n",
- smtp_names[smtp_connection_had[smtp_ch_index-1]]);
- else
- done = synprot_error(L_smtp_protocol_error, 503, NULL,
- smtp_connection_had[smtp_ch_index-1] == SCH_DATA
- ? US"valid RCPT command must precede DATA"
- : US"valid RCPT command must precede BDAT");
+ HAD(SCH_DATA);
+ dot_ends = TRUE;
- if (chunking_state > CHUNKING_OFFERED)
- bdat_flush_data();
- break;
- }
+ DATA_BDAT: /* Common code for DATA and BDAT */
+ if (!discarded && recipients_count <= 0)
+ {
+ if (rcpt_smtp_response_same && rcpt_smtp_response != NULL)
+ {
+ uschar *code = US"503";
+ int len = Ustrlen(rcpt_smtp_response);
+ smtp_respond(code, 3, FALSE, US"All RCPT commands were rejected with "
+ "this error:");
+ /* Responses from smtp_printf() will have \r\n on the end */
+ if (len > 2 && rcpt_smtp_response[len-2] == '\r')
+ rcpt_smtp_response[len-2] = 0;
+ smtp_respond(code, 3, FALSE, rcpt_smtp_response);
+ }
+ if (smtp_in_pipelining_advertised && last_was_rcpt)
+ smtp_printf("503 Valid RCPT command must precede %s\r\n", FALSE,
+ smtp_names[smtp_connection_had[smtp_ch_index-1]]);
+ else
+ done = synprot_error(L_smtp_protocol_error, 503, NULL,
+ smtp_connection_had[smtp_ch_index-1] == SCH_DATA
+ ? US"valid RCPT command must precede DATA"
+ : US"valid RCPT command must precede BDAT");
- if (toomany && recipients_max_reject)
- {
- sender_address = NULL; /* This will allow a new MAIL without RSET */
- sender_address_unrewritten = NULL;
- smtp_printf("554 Too many recipients\r\n");
- break;
- }
+ if (chunking_state > CHUNKING_OFFERED)
+ bdat_flush_data();
+ break;
+ }
- if (chunking_state > CHUNKING_OFFERED)
- { /* No predata ACL or go-ahead output for BDAT */
- rc = OK;
- }
- else
- {
- /* If there is an ACL, re-check the synchronization afterwards, since the
- ACL may have delayed. To handle cutthrough delivery enforce a dummy call
- to get the DATA command sent. */
+ if (toomany && recipients_max_reject)
+ {
+ sender_address = NULL; /* This will allow a new MAIL without RSET */
+ sender_address_unrewritten = NULL;
+ smtp_printf("554 Too many recipients\r\n", FALSE);
+ break;
+ }
- if (acl_smtp_predata == NULL && cutthrough.fd < 0)
- rc = OK;
+ if (chunking_state > CHUNKING_OFFERED)
+ rc = OK; /* No predata ACL or go-ahead output for BDAT */
else
{
- uschar * acl = acl_smtp_predata ? acl_smtp_predata : US"accept";
- enable_dollar_recipients = TRUE;
- rc = acl_check(ACL_WHERE_PREDATA, NULL, acl, &user_msg,
- &log_msg);
- enable_dollar_recipients = FALSE;
- if (rc == OK && !check_sync())
- goto SYNC_FAILURE;
+ /* If there is an ACL, re-check the synchronization afterwards, since the
+ ACL may have delayed. To handle cutthrough delivery enforce a dummy call
+ to get the DATA command sent. */
- 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 (acl_smtp_predata == NULL && cutthrough.cctx.sock < 0)
+ rc = OK;
+ else
+ {
+ uschar * acl = acl_smtp_predata ? acl_smtp_predata : US"accept";
+ enable_dollar_recipients = TRUE;
+ rc = acl_check(ACL_WHERE_PREDATA, NULL, acl, &user_msg,
+ &log_msg);
+ 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)
- smtp_user_msg(US"354", user_msg);
- else
- smtp_printf(
- "354 Enter message, ending with \".\" on a line by itself\r\n");
- }
+ if (user_msg)
+ smtp_user_msg(US"354", user_msg);
+ else
+ smtp_printf(
+ "354 Enter message, ending with \".\" on a line by itself\r\n", FALSE);
+ }
- done = 3;
- message_ended = END_NOTENDED; /* Indicate in middle of data */
+#ifdef TCP_QUICKACK
+ if (smtp_in) /* all ACKs needed to ramp window up for bulk data */
+ (void) setsockopt(fileno(smtp_in), IPPROTO_TCP, TCP_QUICKACK,
+ US &on, sizeof(on));
+#endif
+ done = 3;
+ message_ended = END_NOTENDED; /* Indicate in middle of data */
- break;
+ break;
case VRFY_CMD:
if (!(address = parse_extract_address(smtp_cmd_data, &errmess,
&start, &end, &recipient_domain, FALSE)))
{
- smtp_printf("501 %s\r\n", errmess);
+ smtp_printf("501 %s\r\n", FALSE, errmess);
break;
}
- if (recipient_domain == 0)
+ if (!recipient_domain)
if (!(recipient_domain = qualify_recipient(&address, smtp_cmd_data,
US"verify")))
break;
break;
}
- smtp_printf("%s\r\n", s);
+ smtp_printf("%s\r\n", FALSE, s);
}
break;
}
case EXPN_CMD:
- HAD(SCH_EXPN);
- rc = acl_check(ACL_WHERE_EXPN, NULL, acl_smtp_expn, &user_msg, &log_msg);
- if (rc != OK)
- done = smtp_handle_acl_fail(ACL_WHERE_EXPN, rc, user_msg, log_msg);
- else
- {
- BOOL save_log_testing_mode = log_testing_mode;
- address_test_mode = log_testing_mode = TRUE;
- (void) verify_address(deliver_make_addr(smtp_cmd_data, FALSE),
- smtp_out, vopt_is_recipient | vopt_qualify | vopt_expn, -1, -1, -1,
- NULL, NULL, NULL);
- address_test_mode = FALSE;
- log_testing_mode = save_log_testing_mode; /* true for -bh */
- }
- break;
+ HAD(SCH_EXPN);
+ rc = acl_check(ACL_WHERE_EXPN, NULL, acl_smtp_expn, &user_msg, &log_msg);
+ if (rc != OK)
+ done = smtp_handle_acl_fail(ACL_WHERE_EXPN, rc, user_msg, log_msg);
+ else
+ {
+ BOOL save_log_testing_mode = log_testing_mode;
+ address_test_mode = log_testing_mode = TRUE;
+ (void) verify_address(deliver_make_addr(smtp_cmd_data, FALSE),
+ smtp_out, vopt_is_recipient | vopt_qualify | vopt_expn, -1, -1, -1,
+ NULL, NULL, NULL);
+ address_test_mode = FALSE;
+ log_testing_mode = save_log_testing_mode; /* true for -bh */
+ }
+ break;
#ifdef SUPPORT_TLS
case STARTTLS_CMD:
- HAD(SCH_STARTTLS);
- if (!tls_advertised)
- {
- done = synprot_error(L_smtp_protocol_error, 503, NULL,
- US"STARTTLS command used when not advertised");
- break;
- }
-
- /* Apply an ACL check if one is defined */
+ HAD(SCH_STARTTLS);
+ if (!tls_advertised)
+ {
+ done = synprot_error(L_smtp_protocol_error, 503, NULL,
+ US"STARTTLS command used when not advertised");
+ break;
+ }
- if ( acl_smtp_starttls
- && (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;
- }
+ /* Apply an ACL check if one is defined */
- /* RFC 2487 is not clear on when this command may be sent, though it
- does state that all information previously obtained from the client
- must be discarded if a TLS session is started. It seems reasonble to
- do an implied RSET when STARTTLS is received. */
-
- incomplete_transaction_log(US"STARTTLS");
- 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())
- {
- DEBUG(D_any)
- debug_printf("Non-empty input buffer after STARTTLS; naive attack?\n");
- if (tls_in.active < 0)
- smtp_inend = smtp_inptr = smtp_inbuffer;
- /* and if TLS is already active, tls_server_start() should fail */
- }
+ if ( acl_smtp_starttls
+ && (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;
+ }
- /* There is nothing we value in the input buffer and if TLS is succesfully
- negotiated, we won't use this buffer again; if TLS fails, we'll just read
- fresh content into it. The buffer contains arbitrary content from an
- 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
- target to jump to. */
+ /* RFC 2487 is not clear on when this command may be sent, though it
+ does state that all information previously obtained from the client
+ must be discarded if a TLS session is started. It seems reasonable to
+ do an implied RSET when STARTTLS is received. */
+
+ incomplete_transaction_log(US"STARTTLS");
+ cancel_cutthrough_connection(TRUE, US"STARTTLS received");
+ 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())
+ {
+ DEBUG(D_any)
+ debug_printf("Non-empty input buffer after STARTTLS; naive attack?\n");
+ if (tls_in.active.sock < 0)
+ smtp_inend = smtp_inptr = smtp_inbuffer;
+ /* and if TLS is already active, tls_server_start() should fail */
+ }
- memset(smtp_inbuffer, 0, in_buffer_size);
+ /* There is nothing we value in the input buffer and if TLS is successfully
+ negotiated, we won't use this buffer again; if TLS fails, we'll just read
+ fresh content into it. The buffer contains arbitrary content from an
+ 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
+ target to jump to. */
- /* 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.
+ memset(smtp_inbuffer, 0, IN_BUFFER_SIZE);
- We must allow for an extra EHLO command and an extra AUTH command after
- STARTTLS that don't add to the nonmail command count. */
+ /* 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.
- if ((rc = tls_server_start(tls_require_ciphers)) == OK)
- {
- if (!tls_remember_esmtp)
- helo_seen = esmtp = auth_advertised = pipelining_advertised = FALSE;
- cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
- cmd_list[CMD_LIST_AUTH].is_mail_cmd = TRUE;
- cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
- if (sender_helo_name != NULL)
- {
- store_free(sender_helo_name);
- 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)
- ];
+ We must allow for an extra EHLO command and an extra AUTH command after
+ STARTTLS that don't add to the nonmail command count. */
- 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 */
- }
+ s = NULL;
+ if ((rc = tls_server_start(tls_require_ciphers, &s)) == OK)
+ {
+ if (!tls_remember_esmtp)
+ helo_seen = esmtp = auth_advertised = smtp_in_pipelining_advertised = FALSE;
+ cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE;
+ cmd_list[CMD_LIST_AUTH].is_mail_cmd = TRUE;
+ cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
+ if (sender_helo_name)
+ {
+ 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.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);
- /* Some local configuration problem was discovered before actually trying
- to do a TLS handshake; give a temporary error. */
+ /* Some local configuration problem was discovered before actually trying
+ to do a TLS handshake; give a temporary error. */
- else if (rc == DEFER)
- {
- smtp_printf("454 TLS currently unavailable\r\n");
- break;
- }
+ if (rc == DEFER)
+ {
+ smtp_printf("454 TLS currently unavailable\r\n", FALSE);
+ break;
+ }
- /* Hard failure. Reject everything except QUIT or closed connection. One
- cause for failure is a nested STARTTLS, in which case tls_in.active remains
- set, but we must still reject all incoming commands. */
+ /* Hard failure. Reject everything except QUIT or closed connection. One
+ cause for failure is a nested STARTTLS, in which case tls_in.active remains
+ set, but we must still reject all incoming commands. */
- DEBUG(D_tls) debug_printf("TLS failed to start\n");
- while (done <= 0) switch(smtp_read_command(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;
+ DEBUG(D_tls) debug_printf("TLS failed to start\n");
+ while (done <= 0) switch(smtp_read_command(FALSE, GETC_BUFFER_UNLIMITED))
+ {
+ 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;
- /* 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", smtp_active_hostname);
- log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
- smtp_get_connection_info());
- 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_hostname);
+ 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");
- break;
- }
- tls_close(TRUE, TRUE);
- break;
+ default:
+ smtp_printf("554 Security failure\r\n", FALSE);
+ break;
+ }
+ tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
+ break;
#endif
message. */
case QUIT_CMD:
- smtp_quit_handler(&user_msg, &log_msg);
- done = 2;
- break;
+ smtp_quit_handler(&user_msg, &log_msg);
+ done = 2;
+ break;
case RSET_CMD:
- smtp_rset_handler();
- smtp_reset(reset_point);
- toomany = FALSE;
- break;
+ smtp_rset_handler();
+ cancel_cutthrough_connection(TRUE, US"RSET received");
+ smtp_reset(reset_point);
+ toomany = FALSE;
+ break;
case NOOP_CMD:
- HAD(SCH_NOOP);
- smtp_printf("250 OK\r\n");
- break;
+ HAD(SCH_NOOP);
+ smtp_printf("250 OK\r\n", FALSE);
+ break;
/* Show ETRN/EXPN/VRFY if there's an ACL for checking hosts; if actually
response. */
case HELP_CMD:
- HAD(SCH_HELP);
- smtp_printf("214-Commands supported:\r\n");
- {
- uschar buffer[256];
- buffer[0] = 0;
- Ustrcat(buffer, " AUTH");
- #ifdef SUPPORT_TLS
- if (tls_in.active < 0 &&
- verify_check_host(&tls_advertise_hosts) != FAIL)
- Ustrcat(buffer, " STARTTLS");
- #endif
- Ustrcat(buffer, " HELO EHLO MAIL RCPT DATA BDAT");
- Ustrcat(buffer, " NOOP QUIT RSET HELP");
- if (acl_smtp_etrn != NULL) Ustrcat(buffer, " ETRN");
- if (acl_smtp_expn != NULL) Ustrcat(buffer, " EXPN");
- if (acl_smtp_vrfy != NULL) Ustrcat(buffer, " VRFY");
- smtp_printf("214%s\r\n", buffer);
- }
- break;
+ HAD(SCH_HELP);
+ smtp_printf("214-Commands supported:\r\n", TRUE);
+ {
+ uschar buffer[256];
+ buffer[0] = 0;
+ Ustrcat(buffer, " AUTH");
+ #ifdef SUPPORT_TLS
+ if (tls_in.active.sock < 0 &&
+ verify_check_host(&tls_advertise_hosts) != FAIL)
+ Ustrcat(buffer, " STARTTLS");
+ #endif
+ Ustrcat(buffer, " HELO EHLO MAIL RCPT DATA BDAT");
+ Ustrcat(buffer, " NOOP QUIT RSET HELP");
+ if (acl_smtp_etrn != NULL) Ustrcat(buffer, " ETRN");
+ if (acl_smtp_expn != NULL) Ustrcat(buffer, " EXPN");
+ if (acl_smtp_vrfy != NULL) Ustrcat(buffer, " VRFY");
+ smtp_printf("214%s\r\n", FALSE, buffer);
+ }
+ break;
case EOF_CMD:
- incomplete_transaction_log(US"connection lost");
- smtp_notquit_exit(US"connection-lost", US"421",
- US"%s lost input connection", smtp_active_hostname);
+ incomplete_transaction_log(US"connection lost");
+ smtp_notquit_exit(US"connection-lost", US"421",
+ US"%s lost input connection", smtp_active_hostname);
+
+ /* 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.
+ */
+
+ if (sender_address || recipients_count > 0)
+ log_write(L_lost_incoming_connection, LOG_MAIN,
+ "unexpected %s while reading SMTP command from %s%s%s D=%s",
+ sender_host_unknown ? "EOF" : "disconnection",
+ tcp_in_fastopen && !tcp_in_fastopen_logged ? US"TFO " : US"",
+ host_and_ident(FALSE), smtp_read_error,
+ string_timesince(&smtp_connection_start)
+ );
- /* 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.
- */
+ else
+ log_write(L_smtp_connection, LOG_MAIN, "%s %slost%s D=%s",
+ smtp_get_connection_info(),
+ tcp_in_fastopen && !tcp_in_fastopen_logged ? US"TFO " : US"",
+ smtp_read_error,
+ string_timesince(&smtp_connection_start)
+ );
+
+ done = 1;
+ break;
- if (sender_address != NULL || recipients_count > 0)
- log_write(L_lost_incoming_connection,
- LOG_MAIN,
- "unexpected %s while reading SMTP command from %s%s",
- sender_host_unknown? "EOF" : "disconnection",
- host_and_ident(FALSE), smtp_read_error);
- else log_write(L_smtp_connection, LOG_MAIN, "%s lost%s",
- smtp_get_connection_info(), smtp_read_error);
+ case ETRN_CMD:
+ HAD(SCH_ETRN);
+ if (sender_address)
+ {
+ done = synprot_error(L_smtp_protocol_error, 503, NULL,
+ US"ETRN is not permitted inside a transaction");
+ break;
+ }
- done = 1;
- break;
+ log_write(L_etrn, LOG_MAIN, "ETRN %s received from %s", smtp_cmd_argument,
+ host_and_ident(FALSE));
+ 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;
+ }
- case ETRN_CMD:
- HAD(SCH_ETRN);
- if (sender_address != NULL)
- {
- done = synprot_error(L_smtp_protocol_error, 503, NULL,
- US"ETRN is not permitted inside a transaction");
- break;
- }
+ /* Compute the serialization key for this command. */
- log_write(L_etrn, LOG_MAIN, "ETRN %s received from %s", smtp_cmd_argument,
- host_and_ident(FALSE));
+ etrn_serialize_key = string_sprintf("etrn-%s\n", smtp_cmd_data);
- 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;
- }
+ /* 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,
+ since that is strictly the only kind of ETRN that can be implemented
+ according to the RFC. */
- /* Compute the serialization key for this command. */
+ 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;
+ }
+ }
- etrn_serialize_key = string_sprintf("etrn-%s\n", smtp_cmd_data);
+ /* Else set up to call Exim with the -R option. */
- /* 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,
- since that is strictly the only kind of ETRN that can be implemented
- according to the RFC. */
+ 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 (smtp_etrn_command != NULL)
- {
- 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");
- break;
- }
- }
+ /* If we are host-testing, don't actually do anything. */
- /* Else set up to call Exim with the -R option. */
+ if (host_checking)
+ {
+ 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;
+ }
- 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 we are host-testing, don't actually do anything. */
+ /* If ETRN queue runs are to be serialized, check the database to
+ ensure one isn't already running. */
- if (host_checking)
- {
- 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");
- else smtp_user_msg(US"250", user_msg);
- break;
- }
+ if (smtp_etrn_serialize && !enq_start(etrn_serialize_key, 1))
+ {
+ 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
+ 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 ETRN queue runs are to be serialized, check the database to
- ensure one isn't already running. */
+ oldsignal = signal(SIGCHLD, SIG_IGN);
- if (smtp_etrn_serialize && !enq_start(etrn_serialize_key, 1))
- {
- smtp_printf("458 Already processing %s\r\n", smtp_cmd_data);
- break;
- }
+ 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);
- /* 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. */
+ signal(SIGCHLD, SIG_DFL); /* Want to catch child */
- oldsignal = signal(SIGCHLD, SIG_IGN);
+ /* If not serializing, do the exec right away. Otherwise, fork down
+ into another process. */
- 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 (!smtp_etrn_serialize || (pid = fork()) == 0)
+ {
+ 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 */
+ }
- signal(SIGCHLD, SIG_DFL); /* Want to catch child */
+ /* Obey this if smtp_serialize and the 2nd fork yielded non-zero. That
+ 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
+ complete, before removing the serialization. */
- /* If not serializing, do the exec right away. Otherwise, fork down
- into another process. */
+ if (pid < 0)
+ log_write(0, LOG_MAIN|LOG_PANIC, "2nd fork for serialized ETRN "
+ "failed: %s", strerror(errno));
+ else
+ {
+ 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);
+ }
- if (!smtp_etrn_serialize || (pid = fork()) == 0)
- {
- 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 */
- }
+ enq_end(etrn_serialize_key);
+ _exit(EXIT_SUCCESS);
+ }
- /* Obey this if smtp_serialize and the 2nd fork yielded non-zero. That
- 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
- complete, before removing the serialization. */
+ /* Back in the top level SMTP process. Check that we started a subprocess
+ and restore the signal state. */
if (pid < 0)
- log_write(0, LOG_MAIN|LOG_PANIC, "2nd fork for serialized ETRN "
- "failed: %s", strerror(errno));
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "fork of process for ETRN failed: %s",
+ strerror(errno));
+ smtp_printf("458 Unable to fork process\r\n", FALSE);
+ if (smtp_etrn_serialize) enq_end(etrn_serialize_key);
+ }
else
- {
- 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);
- _exit(EXIT_SUCCESS);
- }
-
- /* Back in the top level SMTP process. Check that we started a subprocess
- and restore the signal state. */
-
- if (pid < 0)
- {
- log_write(0, LOG_MAIN|LOG_PANIC, "fork of process for ETRN failed: %s",
- strerror(errno));
- smtp_printf("458 Unable to fork process\r\n");
- if (smtp_etrn_serialize) enq_end(etrn_serialize_key);
- }
- else
- {
- if (user_msg == NULL) smtp_printf("250 OK\r\n");
- else smtp_user_msg(US"250", user_msg);
- }
+ {
+ if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE);
+ else smtp_user_msg(US"250", user_msg);
+ }
- signal(SIGCHLD, oldsignal);
- break;
+ signal(SIGCHLD, oldsignal);
+ break;
case BADARG_CMD:
- done = synprot_error(L_smtp_syntax_error, 501, NULL,
- US"unexpected argument data");
- break;
+ done = synprot_error(L_smtp_syntax_error, 501, NULL,
+ US"unexpected argument data");
+ break;
/* This currently happens only for NULLs, but could be extended. */
case BADCHAR_CMD:
- done = synprot_error(L_smtp_syntax_error, 0, NULL, /* Just logs */
- US"NULL character(s) present (shown as '?')");
- smtp_printf("501 NULL characters are not allowed in SMTP commands\r\n");
- break;
+ done = synprot_error(L_smtp_syntax_error, 0, NULL, /* Just logs */
+ US"NUL character(s) present (shown as '?')");
+ smtp_printf("501 NUL characters are not allowed in SMTP commands\r\n",
+ FALSE);
+ break;
case BADSYN_CMD:
SYNC_FAILURE:
- if (smtp_inend >= smtp_inbuffer + in_buffer_size)
- smtp_inend = smtp_inbuffer + in_buffer_size - 1;
- c = smtp_inend - smtp_inptr;
- if (c > 150) c = 150;
- smtp_inptr[c] = 0;
- incomplete_transaction_log(US"sync failure");
- log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
- "(next input sent too soon: pipelining was%s advertised): "
- "rejected \"%s\" %s next input=\"%s\"",
- pipelining_advertised? "" : " not",
- smtp_cmd_buffer, host_and_ident(TRUE),
- string_printing(smtp_inptr));
- smtp_notquit_exit(US"synchronization-error", US"554",
- US"SMTP synchronization error");
- done = 1; /* Pretend eof - drops connection */
- break;
+ if (smtp_inend >= smtp_inbuffer + IN_BUFFER_SIZE)
+ smtp_inend = smtp_inbuffer + IN_BUFFER_SIZE - 1;
+ c = smtp_inend - smtp_inptr;
+ if (c > 150) c = 150; /* limit logged amount */
+ smtp_inptr[c] = 0;
+ incomplete_transaction_log(US"sync failure");
+ log_write(0, LOG_MAIN|LOG_REJECT, "SMTP protocol synchronization error "
+ "(next input sent too soon: pipelining was%s advertised): "
+ "rejected \"%s\" %s next input=\"%s\"",
+ smtp_in_pipelining_advertised ? "" : " not",
+ smtp_cmd_buffer, host_and_ident(TRUE),
+ string_printing(smtp_inptr));
+ smtp_notquit_exit(US"synchronization-error", US"554",
+ US"SMTP synchronization error");
+ done = 1; /* Pretend eof - drops connection */
+ break;
case TOO_MANY_NONMAIL_CMD:
- s = smtp_cmd_buffer;
- while (*s != 0 && !isspace(*s)) s++;
- incomplete_transaction_log(US"too many non-mail commands");
- log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
- "nonmail commands (last was \"%.*s\")", host_and_ident(FALSE),
- (int)(s - smtp_cmd_buffer), smtp_cmd_buffer);
- smtp_notquit_exit(US"bad-commands", US"554", US"Too many nonmail commands");
- done = 1; /* Pretend eof - drops connection */
- break;
+ s = smtp_cmd_buffer;
+ while (*s != 0 && !isspace(*s)) s++;
+ incomplete_transaction_log(US"too many non-mail commands");
+ log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
+ "nonmail commands (last was \"%.*s\")", host_and_ident(FALSE),
+ (int)(s - smtp_cmd_buffer), smtp_cmd_buffer);
+ smtp_notquit_exit(US"bad-commands", US"554", US"Too many nonmail commands");
+ done = 1; /* Pretend eof - drops connection */
+ break;
#ifdef SUPPORT_PROXY
case PROXY_FAIL_IGNORE_CMD:
- smtp_printf("503 Command refused, required Proxy negotiation failed\r\n");
- break;
+ smtp_printf("503 Command refused, required Proxy negotiation failed\r\n", FALSE);
+ break;
#endif
default:
- if (unknown_command_count++ >= smtp_max_unknown_commands)
- {
- log_write(L_smtp_syntax_error, LOG_MAIN,
- "SMTP syntax error in \"%s\" %s %s",
- string_printing(smtp_cmd_buffer), host_and_ident(TRUE),
- US"unrecognized command");
- incomplete_transaction_log(US"unrecognized command");
- smtp_notquit_exit(US"bad-commands", US"500",
- US"Too many unrecognized commands");
- done = 2;
- log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
- "unrecognized commands (last was \"%s\")", host_and_ident(FALSE),
- string_printing(smtp_cmd_buffer));
- }
- else
- done = synprot_error(L_smtp_syntax_error, 500, NULL,
- US"unrecognized command");
- break;
+ if (unknown_command_count++ >= smtp_max_unknown_commands)
+ {
+ log_write(L_smtp_syntax_error, LOG_MAIN,
+ "SMTP syntax error in \"%s\" %s %s",
+ string_printing(smtp_cmd_buffer), host_and_ident(TRUE),
+ US"unrecognized command");
+ incomplete_transaction_log(US"unrecognized command");
+ smtp_notquit_exit(US"bad-commands", US"500",
+ US"Too many unrecognized commands");
+ done = 2;
+ log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many "
+ "unrecognized commands (last was \"%s\")", host_and_ident(FALSE),
+ string_printing(smtp_cmd_buffer));
+ }
+ else
+ done = synprot_error(L_smtp_syntax_error, 500, NULL,
+ US"unrecognized command");
+ break;
}
/* This label is used by goto's inside loops that want to break out to
return done - 2; /* Convert yield values */
}
+
+
+gstring *
+authres_smtpauth(gstring * g)
+{
+if (!sender_host_authenticated)
+ return g;
+
+g = string_append(g, 2, US";\n\tauth=pass (", sender_host_auth_pubname);
+
+if (Ustrcmp(sender_host_auth_pubname, "tls") != 0)
+ g = string_append(g, 2, US") smtp.auth=", authenticated_id);
+else if (authenticated_id)
+ g = string_append(g, 2, US") x509.auth=", authenticated_id);
+else
+ g = string_catn(g, US") reason=x509.auth", 17);
+
+if (authenticated_sender)
+ g = string_append(g, 2, US" smtp.mailfrom=", authenticated_sender);
+return g;
+}
+
+
+
/* vi: aw ai sw=2
*/
/* End of smtp_in.c */