* Exim - an Internet mail transport agent *
*************************************************/
+/* Copyright (c) The Exim Maintainers 2020 - 2022 */
/* Copyright (c) University of Cambridge 1995 - 2018 */
-/* Copyright (c) The Exim Maintainers 2020 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for handling an incoming SMTP call. */
#endif
BOOL dsn_advertised :1;
BOOL esmtp :1;
- BOOL helo_required :1;
+ BOOL helo_verify_required :1;
BOOL helo_verify :1;
BOOL helo_seen :1;
BOOL helo_accept_junk :1;
BOOL smtputf8_advertised :1;
#endif
} fl = {
- .helo_required = FALSE,
+ .helo_verify_required = FALSE,
.helo_verify = FALSE,
.smtp_exit_function_called = FALSE,
};
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;
-
-#ifndef DISABLE_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 || f.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
- || f.sender_host_notsocket || !f.smtp_in_pipelining_advertised)
- return FALSE;
-
-if (wouldblock_reading()) return FALSE;
-f.smtp_in_pipelining_used = TRUE;
-return TRUE;
-}
-
-
-#ifndef DISABLE_PIPE_CONNECT
-static BOOL
-pipeline_connect_sends(void)
-{
-if (!sender_host_address || f.sender_host_notsocket || !fl.pipe_connect_acceptable)
- return FALSE;
-
-if (wouldblock_reading()) return FALSE;
-f.smtp_in_early_pipe_used = TRUE;
-return TRUE;
-}
-#endif
-
/*************************************************
* Log incomplete transactions *
*************************************************/
if (recipients_count > 0)
{
- raw_recipients = store_get(recipients_count * sizeof(uschar *), FALSE);
+ raw_recipients = store_get(recipients_count * sizeof(uschar *), GET_UNTAINTED);
for (int i = 0; i < recipients_count; i++)
raw_recipients[i] = recipients_list[i].address;
raw_recipients_count = recipients_count;
}
+/******************************************************************************/
+/* SMTP input buffer handling. Most of these are similar to stdio routines. */
+
+static void
+smtp_buf_init(void)
+{
+/* Set up the buffer for inputting using direct read() calls, and arrange to
+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 = 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';
+
+smtp_inptr = smtp_inend = smtp_inbuffer;
+smtp_had_eof = smtp_had_error = 0;
+}
+
+
/* Refill the buffer, and notify DKIM verification code.
Return false for error or EOF.
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);
return TRUE;
}
-/*************************************************
-* SMTP version of getc() *
-*************************************************/
-/* This gets the next byte from the SMTP input buffer. If the buffer is empty,
+/* Check if there is buffered data */
+
+BOOL
+smtp_hasc(void)
+{
+return smtp_inptr < smtp_inend;
+}
+
+/* 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 negotiated itself into an TLS/SSL state.
int
smtp_getc(unsigned lim)
{
-if (smtp_inptr >= smtp_inend)
- if (!smtp_refill(lim))
- return EOF;
+if (!smtp_hasc() && !smtp_refill(lim)) return EOF;
return *smtp_inptr++;
}
+/* Get many bytes, refilling buffer if needed */
+
uschar *
smtp_getbuf(unsigned * len)
{
unsigned size;
uschar * buf;
-if (smtp_inptr >= smtp_inend)
- if (!smtp_refill(*len))
- { *len = 0; return NULL; }
+if (!smtp_hasc() && !smtp_refill(*len))
+ { *len = 0; return NULL; }
if ((size = smtp_inend - smtp_inptr) > *len) size = *len;
buf = smtp_inptr;
return buf;
}
+/* Copy buffered data to the dkim feed.
+Called, unless TLS, just before starting to read message headers. */
+
void
-smtp_get_cache(void)
+smtp_get_cache(unsigned lim)
{
#ifndef DISABLE_DKIM
int n = smtp_inend - smtp_inptr;
+if (n > lim)
+ n = lim;
if (n > 0)
dkim_exim_verify_feed(smtp_inptr, n);
#endif
}
+/* SMTP version of ungetc()
+Puts a character back in the input buffer. Only ever called once.
+
+Arguments:
+ ch the character
+
+Returns: the character
+*/
+
+int
+smtp_ungetc(int ch)
+{
+if (smtp_inptr <= smtp_inbuffer) /* NB: NOT smtp_hasc() ! */
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "buffer underflow in smtp_ungetc");
+
+*--smtp_inptr = ch;
+return ch;
+}
+
+
+/* SMTP version of feof()
+Tests for a previous EOF
+
+Arguments: none
+Returns: non-zero if the eof flag is set
+*/
+
+int
+smtp_feof(void)
+{
+return smtp_had_eof;
+}
+
+
+/* SMTP version of ferror()
+Tests for a previous read error, and returns with errno
+restored to what it was when the error was detected.
+
+Arguments: none
+Returns: non-zero if the error flag is set
+*/
+
+int
+smtp_ferror(void)
+{
+errno = smtp_had_error;
+return smtp_had_error;
+}
+
+
+/* Check if a getc will block or not */
+
+static BOOL
+smtp_could_getc(void)
+{
+int fd, rc;
+fd_set fds;
+struct timeval tzero = {.tv_sec = 0, .tv_usec = 0};
+
+if (smtp_inptr < smtp_inend)
+ return TRUE;
+
+fd = fileno(smtp_in);
+FD_ZERO(&fds);
+FD_SET(fd, &fds);
+rc = select(fd + 1, (SELECT_ARG2_TYPE *)&fds, NULL, NULL, &tzero);
+
+if (rc <= 0) return FALSE; /* Not ready to read */
+rc = smtp_getc(GETC_BUFFER_UNLIMITED);
+if (rc < 0) return FALSE; /* End of file or error */
+
+smtp_ungetc(rc);
+return TRUE;
+}
+
+
+/******************************************************************************/
+/*************************************************
+* 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)
+{
+#ifndef DISABLE_TLS
+if (tls_in.active.sock >= 0)
+ return !tls_could_getc();
+#endif
+
+return !smtp_could_getc();
+}
+
+static BOOL
+check_sync(void)
+{
+if (!smtp_enforce_sync || !sender_host_address || f.sender_host_notsocket)
+ return TRUE;
+
+return wouldblock_reading();
+}
+
+
+/******************************************************************************/
+/* Variants of the smtp_* input handling functions for use in CHUNKING mode */
+
+/* Forward declarations */
+static inline void bdat_push_receive_functions(void);
+static inline void bdat_pop_receive_functions(void);
+
+
/* Get a byte from the smtp input, in CHUNKING mode. Handle ack of the
previous BDAT chunk and getting new ones when we run out. Uses the
underlying smtp_getc or tls_getc both for that and for getting the
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;
+ bdat_pop_receive_functions();
#ifndef DISABLE_DKIM
dkim_save = dkim_collect_input;
dkim_collect_input = 0;
if (chunking_state == CHUNKING_LAST)
{
#ifndef DISABLE_DKIM
+ dkim_collect_input = dkim_save;
dkim_exim_verify_feed(NULL, 0); /* notify EOD */
+ dkim_collect_input = 0;
#endif
return EOD;
}
goto repeat_until_rset;
}
- receive_getc = bdat_getc;
- receive_getbuf = bdat_getbuf; /* r~getbuf is never actually used */
- receive_ungetc = bdat_ungetc;
+ bdat_push_receive_functions();
#ifndef DISABLE_DKIM
dkim_collect_input = dkim_save;
#endif
}
}
+BOOL
+bdat_hasc(void)
+{
+if (chunking_data_left > 0)
+ return lwr_receive_hasc();
+return TRUE;
+}
+
uschar *
bdat_getbuf(unsigned * len)
{
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);
- }
+bdat_pop_receive_functions();
+chunking_state = CHUNKING_OFFERED;
+DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state);
}
+static inline void
+bdat_push_receive_functions(void)
+{
+/* 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. */
+if (!lwr_receive_getc)
+ {
+ lwr_receive_getc = receive_getc;
+ lwr_receive_getbuf = receive_getbuf;
+ lwr_receive_hasc = receive_hasc;
+ lwr_receive_ungetc = receive_ungetc;
+ }
+else
+ {
+ DEBUG(D_receive) debug_printf("chunking double-push receive functions\n");
+ }
+receive_getc = bdat_getc;
+receive_getbuf = bdat_getbuf;
+receive_hasc = bdat_hasc;
+receive_ungetc = bdat_ungetc;
+}
-/*************************************************
-* SMTP version of ungetc() *
-*************************************************/
-
-/* Puts a character back in the input buffer. Only ever
-called once.
-
-Arguments:
- ch the character
-
-Returns: the character
-*/
-
-int
-smtp_ungetc(int ch)
+static inline void
+bdat_pop_receive_functions(void)
{
-*--smtp_inptr = ch;
-return ch;
-}
+if (!lwr_receive_getc)
+ {
+ DEBUG(D_receive) debug_printf("chunking double-pop receive functions\n");
+ return;
+ }
+receive_getc = lwr_receive_getc;
+receive_getbuf = lwr_receive_getbuf;
+receive_hasc = lwr_receive_hasc;
+receive_ungetc = lwr_receive_ungetc;
+lwr_receive_getc = NULL;
+lwr_receive_getbuf = NULL;
+lwr_receive_hasc = NULL;
+lwr_receive_ungetc = NULL;
+}
int
bdat_ungetc(int ch)
{
chunking_data_left++;
+bdat_push_receive_functions(); /* we're not done yet, calling push is safe, because it checks the state before pushing anything */
return lwr_receive_ungetc(ch);
}
-/*************************************************
-* SMTP version of feof() *
-*************************************************/
-
-/* Tests for a previous EOF
-
-Arguments: none
-Returns: non-zero if the eof flag is set
-*/
-
-int
-smtp_feof(void)
-{
-return smtp_had_eof;
-}
-
-
-
-
-/*************************************************
-* SMTP version of ferror() *
-*************************************************/
-
-/* Tests for a previous read error, and returns with errno
-restored to what it was when the error was detected.
-
-Arguments: none
-Returns: non-zero if the error flag is set
-*/
-
-int
-smtp_ferror(void)
-{
-errno = smtp_had_error;
-return smtp_had_error;
-}
-
-
-
-/*************************************************
-* Test for characters in the SMTP buffer *
-*************************************************/
-
-/* Used at the end of a message
-
-Arguments: none
-Returns: TRUE/FALSE
-*/
-
-BOOL
-smtp_buffered(void)
-{
-return smtp_inptr < smtp_inend;
-}
-
-
+/******************************************************************************/
/*************************************************
* Write formatted string to SMTP channel *
yield = !! string_vformat(&gs, SVFMT_TAINT_NOCHK, format, ap);
string_from_gstring(&gs);
-DEBUG(D_receive)
- {
- uschar *msg_copy, *cr, *end;
- msg_copy = string_copy(gs.s);
- end = msg_copy + gs.ptr;
- while ((cr = Ustrchr(msg_copy, '\r')) != NULL) /* lose CRs */
- memmove(cr, cr + 1, (end--) - cr);
- debug_printf("SMTP>> %s", msg_copy);
- }
+DEBUG(D_receive) for (const uschar * t, * s = gs.s;
+ s && (t = Ustrchr(s, '\r'));
+ s = t + 2) /* \r\n */
+ debug_printf("%s %.*s\n",
+ s == gs.s ? "SMTP>>" : " ",
+ (int)(t - s), s);
if (!yield)
{
if (fl.rcpt_in_progress)
{
- if (rcpt_smtp_response == NULL)
+ if (!rcpt_smtp_response)
rcpt_smtp_response = string_copy(big_buffer);
else if (fl.rcpt_smtp_response_same &&
Ustrcmp(rcpt_smtp_response, big_buffer) != 0)
+/* 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
+ || f.sender_host_notsocket || !f.smtp_in_pipelining_advertised)
+ return FALSE;
+
+if (wouldblock_reading()) return FALSE;
+f.smtp_in_pipelining_used = TRUE;
+return TRUE;
+}
+
+
+#ifndef DISABLE_PIPE_CONNECT
+static BOOL
+pipeline_connect_sends(void)
+{
+if (!sender_host_address || f.sender_host_notsocket || !fl.pipe_connect_acceptable)
+ return FALSE;
+
+if (wouldblock_reading()) return FALSE;
+f.smtp_in_early_pipe_used = TRUE;
+return TRUE;
+}
+#endif
+
/*************************************************
* SMTP command read timeout *
*************************************************/
|| smtp_cmd_buffer[p->len] == ' '
) )
{
- if (smtp_inptr < smtp_inend && /* Outstanding input */
- p->cmd < sync_cmd_limit && /* Command should sync */
- check_sync && /* Local flag set */
- smtp_enforce_sync && /* Global flag set */
- sender_host_address != NULL && /* Not local input */
- !f.sender_host_notsocket) /* Really is a socket */
+ if ( smtp_inptr < smtp_inend /* Outstanding input */
+ && p->cmd < sync_cmd_limit /* Command should sync */
+ && check_sync /* Local flag set */
+ && smtp_enforce_sync /* Global flag set */
+ && sender_host_address != NULL /* Not local input */
+ && !f.sender_host_notsocket /* Really is a socket */
+ )
return BADSYN_CMD;
/* The variables $smtp_command and $smtp_command_argument point into the
&& check_sync /* Local flag set */
&& smtp_enforce_sync /* Global flag set */
&& sender_host_address /* Not local input */
- && !f.sender_host_notsocket) /* Really is a socket */
+ && !f.sender_host_notsocket /* Really is a socket */
+ )
return BADSYN_CMD;
return OTHER_CMD;
*/
void
-smtp_closedown(uschar *message)
+smtp_closedown(uschar * message)
{
if (!smtp_in || smtp_batched_input) return;
receive_swallow_smtp();
extract_option(uschar **name, uschar **value)
{
uschar *n;
-uschar *v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1;
-while (isspace(*v)) v--;
+uschar *v;
+if (Ustrlen(smtp_cmd_data) <= 0) return FALSE;
+v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1;
+while (v > smtp_cmd_data && isspace(*v)) v--;
v[1] = 0;
+
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);
+ if (*v == '"')
+ {
+ do v--; while (v > smtp_cmd_data && *v != '"');
+ if (v <= smtp_cmd_data) return FALSE;
+ }
v--;
}
+if (v <= smtp_cmd_data) return FALSE;
n = v;
if (*v == '=')
{
- while(isalpha(n[-1])) n--;
+ while (n > smtp_cmd_data && isalpha(n[-1])) n--;
/* RFC says SP, but TAB seen in wild and other major MTAs accept it */
- if (!isspace(n[-1])) return FALSE;
+ if (n <= smtp_cmd_data || !isspace(n[-1])) return FALSE;
n[-1] = 0;
}
else
{
n++;
- if (v == smtp_cmd_data) return FALSE;
}
*v++ = 0;
*name = n;
acl_warn_logged = acl_warn_logged->next;
store_free(this);
}
+
+message_tidyup();
store_reset(reset_point);
+
+message_start();
return store_mark();
}
case MAIL_CMD:
smtp_mailcmd_count++; /* Count for no-mail log */
- if (sender_address != NULL)
+ if (sender_address)
/* The function moan_smtp_batch() does not return. */
moan_smtp_batch(smtp_cmd_buffer, "503 Sender already given");
/* 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
+ /* deconst ok as smtp_cmd_data was not const */
+ ? US 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 */
&& sender_address[0] != 0 && sender_address[0] != '@')
if (f.allow_unqualified_sender)
{
- sender_address = rewrite_address_qualify(sender_address, FALSE);
+ /* deconst ok as sender_address was not const */
+ sender_address = US rewrite_address_qualify(sender_address, FALSE);
DEBUG(D_receive) debug_printf("unqualified address %s accepted "
"and rewritten\n", raw_sender);
}
recipient address */
recipient = rewrite_existflags & rewrite_smtp
- ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+ /* deconst ok as smtp_cmd_data was not const */
+ ? US rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
global_rewrite_rules)
: smtp_cmd_data;
{
DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
recipient);
- recipient = rewrite_address_qualify(recipient, TRUE);
+ /* deconst ok as recipient was not const */
+ recipient = US rewrite_address_qualify(recipient, TRUE);
}
/* The function moan_smtp_batch() does not return. */
else
#ifndef DISABLE_TLS
static BOOL
-smtp_log_tls_fail(uschar * errstr)
+smtp_log_tls_fail(const uschar * errstr)
{
-uschar * conn_info = smtp_get_connection_info();
+const 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 */
/* Allow for trailing 0 in the command and data buffers. Tainted. */
-smtp_cmd_buffer = store_get_perm(2*SMTP_CMD_BUFFER_SIZE + 2, TRUE);
+smtp_cmd_buffer = store_get_perm(2*SMTP_CMD_BUFFER_SIZE + 2, GET_TAINTED);
smtp_cmd_buffer[0] = 0;
smtp_data_buffer = smtp_cmd_buffer + SMTP_CMD_BUFFER_SIZE + 1;
(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. Place a NUL at the
-end of the buffer to safety-stop C-string reads from it. */
+call the local functions instead of the standard C ones. */
-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';
+smtp_buf_init();
receive_getc = smtp_getc;
receive_getbuf = smtp_getbuf;
receive_get_cache = smtp_get_cache;
+receive_hasc = smtp_hasc;
receive_ungetc = smtp_ungetc;
receive_feof = smtp_feof;
receive_ferror = smtp_ferror;
-receive_smtp_buffered = smtp_buffered;
-smtp_inptr = smtp_inend = smtp_inbuffer;
-smtp_had_eof = smtp_had_error = 0;
+lwr_receive_getc = NULL;
+lwr_receive_getbuf = NULL;
+lwr_receive_hasc = NULL;
+lwr_receive_ungetc = NULL;
/* Set up the message size limit; this may be host-specific */
{
#if OPTSTYLE == 1
EXIM_SOCKLEN_T optlen = sizeof(struct ip_options) + MAX_IPOPTLEN;
- struct ip_options *ipopt = store_get(optlen, FALSE);
+ struct ip_options *ipopt = store_get(optlen, GET_UNTAINTED);
#elif OPTSTYLE == 2
struct ip_opts ipoptblock;
struct ip_opts *ipopt = &ipoptblock;
/* Determine whether HELO/EHLO is required for this host. The requirement
can be hard or soft. */
- fl.helo_required = verify_check_host(&helo_verify_hosts) == OK;
- if (!fl.helo_required)
+ fl.helo_verify_required = verify_check_host(&helo_verify_hosts) == OK;
+ if (!fl.helo_verify_required)
fl.helo_verify = verify_check_host(&helo_try_verify_hosts) == OK;
/* Determine whether this hosts is permitted to send syntactic junk
if (smtp_batched_input) return TRUE;
/* If valid Proxy Protocol source is connecting, set up session.
- * Failure will not allow any SMTP function other than QUIT. */
+Failure will not allow any SMTP function other than QUIT. */
#ifdef SUPPORT_PROXY
proxy_session = FALSE;
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. */
+/* Start up TLS if tls_on_connect is set. This is for supporting the legacy
+smtps port for use with older style SSL MTAs. */
#ifndef DISABLE_TLS
- if (tls_in.on_connect)
- {
- if (tls_server_start(&user_msg) != OK)
- return smtp_log_tls_fail(user_msg);
- cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
- }
+if (tls_in.on_connect)
+ {
+ if (tls_server_start(&user_msg) != OK)
+ return smtp_log_tls_fail(user_msg);
+ cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE;
+ }
#endif
/* Run the connect ACL if it exists */
p = s + Ustrlen(s);
while (p > s && isspace(p[-1])) p--;
-*p = 0;
+s = string_copyn(s, p-s);
/* It seems that CC:Mail is braindead, and assumes that the greeting message
is all contained in a single IP packet. The original code wrote out the
if (fl.rcpt_in_progress)
{
- if (rcpt_smtp_response == NULL)
+ if (!rcpt_smtp_response)
rcpt_smtp_response = string_copy(msg);
else if (fl.rcpt_smtp_response_same &&
Ustrcmp(rcpt_smtp_response, msg) != 0)
for (;;)
{
uschar *nl = Ustrchr(msg, '\n');
- if (nl == NULL)
+ if (!nl)
{
smtp_printf("%.3s%c%.*s%s\r\n", !final, code, final ? ' ':'-', esclen, esc, msg);
return;
smtp_message_code(uschar **code, int *codelen, uschar **msg, uschar **log_msg,
BOOL check_valid)
{
-int n;
-int ovector[3];
-
-if (!msg || !*msg) return;
+uschar * match;
+int len;
-if ((n = pcre_exec(regex_smtp_code, NULL, CS *msg, Ustrlen(*msg), 0,
- PCRE_EOPT, ovector, sizeof(ovector)/sizeof(int))) < 0) return;
+if (!msg || !*msg || !regex_match(regex_smtp_code, *msg, -1, &match))
+ return;
+len = Ustrlen(match);
if (check_valid && (*msg)[0] != (*code)[0])
{
log_write(0, LOG_MAIN|LOG_PANIC, "configured error code starts with "
"incorrect digit (expected %c) in \"%s\"", (*code)[0], *msg);
- if (log_msg != NULL && *log_msg == *msg)
- *log_msg = string_sprintf("%s %s", *code, *log_msg + ovector[1]);
+ if (log_msg && *log_msg == *msg)
+ *log_msg = string_sprintf("%s %s", *code, *log_msg + len);
}
else
{
*code = *msg;
- *codelen = ovector[1]; /* Includes final space */
+ *codelen = len; /* Includes final space */
}
-*msg += ovector[1]; /* Chop the code off the message */
+*msg += len; /* Chop the code off the message */
return;
}
uschar *smtp_code;
uschar *lognl;
uschar *sender_info = US"";
-uschar *what =
-#ifdef WITH_CONTENT_SCAN
- where == ACL_WHERE_MIME ? US"during MIME ACL checks" :
-#endif
- where == ACL_WHERE_PREDATA ? US"DATA" :
- where == ACL_WHERE_DATA ? US"after DATA" :
-#ifndef DISABLE_PRDR
- where == ACL_WHERE_PRDR ? US"after DATA PRDR" :
-#endif
- smtp_cmd_data ?
- string_sprintf("%s %s", acl_wherenames[where], smtp_cmd_data) :
- string_sprintf("%s in \"connect\" ACL", acl_wherenames[where]);
+uschar *what;
if (drop) rc = FAIL;
this is what should be logged, so I've changed to logging the unrewritten
address to retain backward compatibility. */
-#ifndef WITH_CONTENT_SCAN
-if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA)
-#else
-if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA || where == ACL_WHERE_MIME)
+switch (where)
+ {
+#ifdef WITH_CONTENT_SCAN
+ case ACL_WHERE_MIME: what = US"during MIME ACL checks"; break;
+#endif
+ case ACL_WHERE_PREDATA: what = US"DATA"; break;
+ case ACL_WHERE_DATA: what = US"after DATA"; break;
+#ifndef DISABLE_PRDR
+ case ACL_WHERE_PRDR: what = US"after DATA PRDR"; break;
#endif
+ default:
+ {
+ uschar * place = smtp_cmd_data ? smtp_cmd_data : US"in \"connect\" ACL";
+ int lim = 100;
+
+ if (where == ACL_WHERE_AUTH) /* avoid logging auth creds */
+ {
+ uschar * s;
+ for (s = smtp_cmd_data; *s && !isspace(*s); ) s++;
+ lim = s - smtp_cmd_data; /* atop after method */
+ }
+ what = string_sprintf("%s %.*s", acl_wherenames[where], lim, place);
+ }
+ }
+switch (where)
{
- sender_info = string_sprintf("F=<%s>%s%s%s%s ",
- sender_address_unrewritten ? sender_address_unrewritten : sender_address,
- sender_host_authenticated ? US" A=" : US"",
- sender_host_authenticated ? sender_host_authenticated : US"",
- sender_host_authenticated && authenticated_id ? US":" : US"",
- sender_host_authenticated && authenticated_id ? authenticated_id : US""
- );
+ case ACL_WHERE_RCPT:
+ case ACL_WHERE_DATA:
+#ifdef WITH_CONTENT_SCAN
+ case ACL_WHERE_MIME:
+#endif
+ sender_info = string_sprintf("F=<%s>%s%s%s%s ",
+ sender_address_unrewritten ? sender_address_unrewritten : sender_address,
+ sender_host_authenticated ? US" A=" : US"",
+ sender_host_authenticated ? sender_host_authenticated : US"",
+ sender_host_authenticated && authenticated_id ? US":" : US"",
+ sender_host_authenticated && authenticated_id ? authenticated_id : US""
+ );
+ break;
}
/* If there's been a sender verification failure with a specific message, and
const uschar *set_id = NULL;
int rc;
+/* Set up globals for error messages */
+
+authenticator_name = au->name;
+driver_srcfile = au->srcfile;
+driver_srcline = au->srcline;
+
/* Run the checking code, passing the remainder of the command line as
data. Initials the $auth<n> variables as empty. Initialize $0 empty and set
it as the only set numerical variable. The authenticator may set $auth<n>
if (au->set_id) set_id = expand_string(au->set_id);
expand_nmax = -1; /* Reset numeric variables */
for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL; /* Reset $auth<n> */
+driver_srcfile = authenticator_name = NULL; driver_srcline = 0;
/* The value of authenticated_id is stored in the spool file and printed in
log lines. It must not contain binary zeros or newline characters. In
DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
*recipient);
rd = Ustrlen(recipient) + 1;
- *recipient = rewrite_address_qualify(*recipient, TRUE);
+ /* deconst ok as *recipient was not const */
+ *recipient = US rewrite_address_qualify(*recipient, TRUE);
return rd;
}
smtp_printf("501 %s: recipient address must contain a domain\r\n", FALSE,
log_write(0, LOG_MAIN|LOG_PANIC, "ACL for QUIT returned ERROR: %s",
*log_msgp);
-#ifdef TCP_CORK
-(void) setsockopt(fileno(smtp_out), IPPROTO_TCP, TCP_CORK, US &on, sizeof(on));
+#ifdef EXIM_TCP_CORK
+(void) setsockopt(fileno(smtp_out), IPPROTO_TCP, EXIM_TCP_CORK, US &on, sizeof(on));
#endif
if (*user_msgp)
else
smtp_printf("221 %s closing connection\r\n", FALSE, smtp_active_hostname);
-#ifndef DISABLE_TLS
+#ifdef SERVERSIDE_CLOSE_NOWAIT
+# ifndef DISABLE_TLS
tls_close(NULL, TLS_SHUTDOWN_NOWAIT);
-#endif
+# endif
+
+log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
+ smtp_get_connection_info());
+#else
+
+# ifndef DISABLE_TLS
+tls_close(NULL, TLS_SHUTDOWN_WAIT);
+# endif
log_write(L_smtp_connection, LOG_MAIN, "%s closed by QUIT",
smtp_get_connection_info());
+
+/* Pause, hoping client will FIN first so that they get the TIME_WAIT.
+The socket should become readble (though with no data) */
+
+(void) poll_one_fd(fileno(smtp_in), POLLIN, 200);
+#endif /*!SERVERSIDE_CLOSE_NOWAIT*/
}
incomplete_transaction_log(US"RSET");
smtp_printf("250 Reset OK\r\n", FALSE);
cmd_list[CMD_LIST_RSET].is_mail_cmd = FALSE;
+if (chunking_state > CHUNKING_OFFERED)
+ chunking_state = CHUNKING_OFFERED;
}
+static int
+expand_mailmax(const uschar * s)
+{
+if (!(s = expand_cstring(s)))
+ log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand smtp_accept_max_per_connection");
+return *s ? Uatoi(s) : 0;
+}
/*************************************************
* Initialize for SMTP incoming message *
cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = TRUE;
#endif
+if (lwr_receive_getc != NULL)
+ {
+ /* This should have already happened, but if we've gotten confused,
+ force a reset here. */
+ DEBUG(D_receive) debug_printf("WARNING: smtp_setup_msg had to restore receive functions to lowers\n");
+ bdat_pop_receive_functions();
+ }
+
/* Set the local signal handler for SIGTERM - it tries to end off tidily */
had_command_sigterm = 0;
if (smtp_batched_input) return smtp_setup_batch_msg();
+#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
+
/* Deal with SMTP commands. This loop is exited by setting done to a POSITIVE
value. The values are 2 larger than the required yield of the function. */
}
#endif
-#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(
#ifndef DISABLE_PIPE_CONNECT
!fl.pipe_connect_acceptable,
/* Find the name of the requested authentication mechanism. */
s = smtp_cmd_data;
- while ((c = *smtp_cmd_data) != 0 && !isspace(c))
- {
+ for (; (c = *smtp_cmd_data) && !isspace(c); smtp_cmd_data++)
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 (*smtp_cmd_data != 0)
+ if (*smtp_cmd_data)
{
*smtp_cmd_data++ = 0;
while (isspace(*smtp_cmd_data)) smtp_cmd_data++;
/* 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
+ host and we haven't done a reverse lookup, force one now. If helo_verify_required
is set, ensure that the HELO name matches the actual host. If helo_verify
is set, do the same check, but softly. */
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,
+ make some people happy to be able to do it. If helo_verify_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. */
f.helo_verified = f.helo_verify_failed = sender_helo_dnssec = FALSE;
- if (fl.helo_required || fl.helo_verify)
+ if (fl.helo_verify_required || fl.helo_verify)
{
BOOL tempfail = !smtp_verify_helo();
if (!f.helo_verified)
{
- if (fl.helo_required)
+ if (fl.helo_verify_required)
{
smtp_printf("%d %s argument does not match calling host\r\n", FALSE,
tempfail? 451 : 550, hello);
fl.smtputf8_advertised = FALSE;
#endif
+ /* Expand the per-connection message count limit option */
+ smtp_mailcmd_max = expand_mailmax(smtp_accept_max_per_connection);
+
smtp_code = US"250 "; /* Default response code plus space*/
if (!user_msg)
{
/* sender_host_name below will be tainted, so save on copy when we hit it */
- g = string_get_tainted(24, TRUE);
+ g = string_get_tainted(24, GET_TAINTED);
g = string_fmt_append(g, "%.3s %s Hello %s%s%s",
smtp_code,
smtp_active_hostname,
g = string_catn(g, US"-SIZE\r\n", 7);
}
+#ifdef EXPERIMENTAL_ESMTP_LIMITS
+ if ( (smtp_mailcmd_max > 0 || recipients_max)
+ && verify_check_host(&limits_advertise_hosts) == OK)
+ {
+ g = string_fmt_append(g, "%.3s-LIMITS", smtp_code);
+ if (smtp_mailcmd_max > 0)
+ g = string_fmt_append(g, " MAILMAX=%d", smtp_mailcmd_max);
+ if (recipients_max)
+ g = string_fmt_append(g, " RCPTMAX=%d", recipients_max);
+ g = string_catn(g, US"\r\n", 2);
+ }
+#endif
+
/* 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.
#endif
(void) fwrite(g->s, 1, g->ptr, smtp_out);
- DEBUG(D_receive)
- {
- uschar *cr;
-
- (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);
- }
+ DEBUG(D_receive) for (const uschar * t, * s = string_from_gstring(g);
+ s && (t = Ustrchr(s, '\r'));
+ s = t + 2) /* \r\n */
+ debug_printf("%s %.*s\n",
+ s == g->s ? "SMTP>>" : " ",
+ (int)(t - s), s);
fl.helo_seen = TRUE;
/* Reset the protocol and the state, abandoning any previous message. */
case MAIL_CMD:
HAD(SCH_MAIL);
smtp_mailcmd_count++; /* Count for limit and ratelimit */
+ message_start();
was_rej_mail = TRUE; /* Reset if accepted */
env_mail_type_t * mail_args; /* Sanity check & validate args */
- if (fl.helo_required && !fl.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 (!fl.helo_seen)
+ if ( fl.helo_verify_required
+ || verify_check_host(&hosts_require_helo) == OK)
+ {
+ 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;
+ }
+ else if (smtp_mailcmd_max < 0)
+ smtp_mailcmd_max = expand_mailmax(smtp_accept_max_per_connection);
if (sender_address)
{
/* 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)
+ if (smtp_mailcmd_max > 0 && smtp_mailcmd_count > smtp_mailcmd_max)
{
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 "
TRUE flag allows "<>" as a sender address. */
raw_sender = rewrite_existflags & rewrite_smtp
- ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+ /* deconst ok as smtp_cmd_data was not const */
+ ? US rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
global_rewrite_rules)
: smtp_cmd_data;
if (f.allow_unqualified_sender)
{
sender_domain = Ustrlen(sender_address) + 1;
- sender_address = rewrite_address_qualify(sender_address, FALSE);
+ /* deconst ok as sender_address was not const */
+ sender_address = US rewrite_address_qualify(sender_address, FALSE);
DEBUG(D_receive) debug_printf("unqualified address %s accepted\n",
raw_sender);
}
case RCPT_CMD:
HAD(SCH_RCPT);
+ /* We got really to many recipients. A check against configured
+ limits is done later */
+ if (rcpt_count < 0 || rcpt_count >= INT_MAX/2)
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Too many recipients: %d", rcpt_count);
rcpt_count++;
was_rcpt = fl.rcpt_in_progress = TRUE;
count this as a protocol error. Reset was_rej_mail so that further RCPTs
get the same treatment. */
- if (sender_address == NULL)
+ if (!sender_address)
{
if (f.smtp_in_pipelining_advertised && last_was_rej_mail)
{
/* Check for an operand */
- if (smtp_cmd_data[0] == 0)
+ if (!smtp_cmd_data[0])
{
done = synprot_error(L_smtp_syntax_error, 501, NULL,
US"RCPT must have an address operand");
as a recipient address */
recipient = rewrite_existflags & rewrite_smtp
- ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
+ /* deconst ok as smtp_cmd_data was not const */
+ ? US rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"",
global_rewrite_rules)
: smtp_cmd_data;
/* Check maximum allowed */
- if (rcpt_count > recipients_max && recipients_max > 0)
+ if (rcpt_count+1 < 0 || rcpt_count > recipients_max && recipients_max > 0)
{
if (recipients_max_reject)
{
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;
-
+ f.bdat_readers_wanted = TRUE; /* FIXME: redundant vs chunking_state? */
f.dot_ends = FALSE;
goto DATA_BDAT;
case DATA_CMD:
HAD(SCH_DATA);
f.dot_ends = TRUE;
+ f.bdat_readers_wanted = FALSE;
DATA_BDAT: /* Common code for DATA and BDAT */
#ifndef DISABLE_PIPE_CONNECT
#endif
if (!discarded && recipients_count <= 0)
{
- if (fl.rcpt_smtp_response_same && rcpt_smtp_response != NULL)
+ if (fl.rcpt_smtp_response_same && rcpt_smtp_response)
{
uschar *code = US"503";
int len = Ustrlen(rcpt_smtp_response);
: US"valid RCPT command must precede BDAT");
if (chunking_state > CHUNKING_OFFERED)
+ {
+ bdat_push_receive_functions();
bdat_flush_data();
+ }
break;
}
sender_address = NULL; /* This will allow a new MAIL without RSET */
sender_address_unrewritten = NULL;
smtp_printf("554 Too many recipients\r\n", FALSE);
+
+ if (chunking_state > CHUNKING_OFFERED)
+ {
+ bdat_push_receive_functions();
+ bdat_flush_data();
+ }
break;
}
ACL may have delayed. To handle cutthrough delivery enforce a dummy call
to get the DATA command sent. */
- if (acl_smtp_predata == NULL && cutthrough.cctx.sock < 0)
+ if (!acl_smtp_predata && cutthrough.cctx.sock < 0)
rc = OK;
else
{
"354 Enter message, ending with \".\" on a line by itself\r\n", FALSE);
}
+ if (f.bdat_readers_wanted)
+ bdat_push_receive_functions();
+
#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,
Pipelining sync checks will normally have protected us too, unless disabled
by configuration. */
- if (receive_smtp_buffered())
+ if (receive_hasc())
{
DEBUG(D_any)
debug_printf("Non-empty input buffer after STARTTLS; naive attack?\n");
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);
+ FALSE, US"ETRN processing", &error);
deliver_domain = NULL;
if (!rc)
{
{
DEBUG(D_exec) debug_print_argv(argv);
exim_nullstd(); /* Ensure std{in,out,err} exist */
+ /* argv[0] should be untainted, from child_exec_exim() */
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));
COMMAND_LOOP:
last_was_rej_mail = was_rej_mail; /* Remember some last commands for */
last_was_rcpt = was_rcpt; /* protocol error handling */
- continue;
}
return done - 2; /* Convert yield values */