* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2017 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for handling an incoming SMTP call. */
/* forward declarations */
-int bdat_ungetc(int ch);
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 **);
*************************************************/
/* 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.
+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
*/
static BOOL
-check_sync(void)
+wouldblock_reading(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;
+#ifdef SUPPORT_TLS
+if (tls_in.active >= 0)
+ return !tls_could_read();
+#endif
+
+if (smtp_inptr < smtp_inend)
+ return FALSE;
fd = fileno(smtp_in);
FD_ZERO(&fds);
return FALSE;
}
+static BOOL
+check_sync(void)
+{
+if (!smtp_enforce_sync || sender_host_address == NULL || 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 || !pipelining_advertised)
+ return FALSE;
+
+return !wouldblock_reading();
+}
+
/*************************************************
+/* 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 */
+
+rc = read(fileno(smtp_in), smtp_inbuffer, MIN(IN_BUFFER_SIZE, lim));
+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;
+ 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() *
*************************************************/
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);
-
- /* Limit amount read, so non-message data is not fed to DKIM */
-
- rc = read(fileno(smtp_in), smtp_inbuffer, MIN(IN_BUFFER_SIZE, lim));
- 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)
{
for(;;)
{
+#ifndef DISABLE_DKIM
+ BOOL 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 = FALSE;
#endif
if (!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\"",
+ "rejected \"%s\" %s next input=\"%s\"%s",
smtp_cmd_buffer, host_and_ident(TRUE),
- string_printing(smtp_inptr));
- (void) synprot_error(L_smtp_protocol_error, 554, NULL,
- US"SMTP synchronization error");
+ 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;
}
return EOD;
}
- smtp_printf("250 %u byte chunk received\r\n", chunking_datasize);
+ smtp_printf("250 %u byte chunk received\r\n", FALSE, chunking_datasize);
chunking_state = CHUNKING_OFFERED;
DEBUG(D_receive) debug_printf("chunking state %d\n", (int)chunking_state);
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:
}
receive_getc = bdat_getc;
+ receive_getbuf = bdat_getbuf;
receive_ungetc = bdat_ungetc;
#ifndef DISABLE_DKIM
- dkim_collect_input = TRUE;
+ 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(chunking_data_left--) < 0)
- break;
+while (chunking_data_left)
+{
+ unsigned n = chunking_data_left;
+ (void) bdat_getbuf(&n);
+}
receive_getc = lwr_receive_getc;
+receive_getbuf = lwr_receive_getbuf;
receive_ungetc = lwr_receive_ungetc;
if (chunking_state != CHUNKING_LAST)
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
#ifdef SUPPORT_TLS
if (tls_in.active >= 0)
{
- if (tls_write(TRUE, big_buffer, Ustrlen(big_buffer)) < 0)
+ if (tls_write(TRUE, big_buffer, Ustrlen(big_buffer), more) < 0)
smtp_write_error = -1;
}
else
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);
+exim_exit(EXIT_FAILURE, US"receiving");
}
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);
+exim_exit(EXIT_FAILURE, US"receiving");
}
* 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 *
*************************************************/
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;
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;
struct timeval tvtmp;
socklen_t vslen = sizeof(struct timeval);
+BOOL yield = FALSE;
/* Save current socket timeout values */
get_ok = getsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &tvtmp, &vslen);
tv.tv_sec = PROXY_NEGOTIATION_TIMEOUT_SEC;
tv.tv_usec = PROXY_NEGOTIATION_TIMEOUT_USEC;
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &tv, sizeof(tv)) < 0)
- return FALSE;
+ 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)
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 = (hdr.v2.ver_cmd & 0xf0) >> 4;
- uint8_t cmd = (hdr.v2.ver_cmd & 0x0f);
+ 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;
+
+ ver = (hdr.v2.ver_cmd & 0xf0) >> 4;
/* 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. */
+ 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 + ntohs(hdr.v2.len);
- if (ret < size)
+ 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;
}
+
+ do
+ {
+ 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 */
break;
case 0x00: /* LOCAL command */
/* Keep local connection address for LOCAL */
+ iptype = US"local";
break;
default:
DEBUG(D_receive)
}
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 adherence to required formatting, exit for any error. */
+ strict adherence to required formatting, exit for any error. */
p += 5;
if (!isspace(*(p++)))
{
}
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
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:" */
{
if (smtp_in == NULL || 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, GETC_BUFFER_UNLIMITED))
{
return;
case QUIT_CMD:
- smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
+ 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");
+ smtp_printf("250 Reset OK\r\n", FALSE);
break;
default:
- smtp_printf("421 %s\r\n", message);
+ smtp_printf("421 %s\r\n", FALSE, message);
break;
}
}
/* 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)
{
- 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 *
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;
static 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_orig = NULL;
+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;
#ifndef DISABLE_DKIM
dkim_signers = NULL;
dkim_disable_verify = FALSE;
#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
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
/* Fall through */
case RSET_CMD:
+ cancel_cutthrough_connection(TRUE, US"RSET received");
smtp_reset(reset_point);
bsmtp_transaction_linecount = receive_linecount;
break;
/* Reset to start of message */
+ cancel_cutthrough_connection(TRUE, US"MAIL received");
smtp_reset(reset_point);
/* Apply SMTP rewrite */
+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;
/* Set up the buffer for inputting using direct read() calls, and arrange to
call the local functions instead of the standard C ones. */
-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");
receive_getc = smtp_getc;
+receive_getbuf = smtp_getbuf;
receive_get_cache = smtp_get_cache;
receive_ungetc = smtp_ungetc;
receive_feof = smtp_feof;
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;
}
"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())
- {
- proxy_session_failed = TRUE;
- DEBUG(D_receive)
- debug_printf("Failure to extract proxied host, only QUIT allowed\n");
- }
- else
- {
- sender_host_name = NULL;
- (void)host_name_lookup();
- host_build_sender_fullhost();
- }
+ setup_proxy_protocol_host();
#endif
-/* Run the ACL if it exists */
+ /* 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, &user_msg) != OK)
+ return smtp_log_tls_fail(user_msg);
+#endif
+
+/* 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);
+ ss = string_catn(ss, code, 3);
if (linebreak == NULL)
{
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++;
}
while (*p != 0);
-ss[ptr] = 0; /* string_cat leaves room for this */
-
/* 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;
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++;
}
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"";
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();
*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);
{
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;
}
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;
#ifdef AUTH_TLS
/* Check once per STARTTLS or SSL-on-connect for a TLS AUTH */
{
c = smtp_in_auth(au, &s, &ss);
- smtp_printf("%s\r\n", 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);
if (!check_helo(smtp_cmd_data))
{
- smtp_printf("501 Syntactically invalid %s argument(s)\r\n", hello);
+ smtp_printf("501 Syntactically invalid %s argument(s)\r\n", FALSE, hello);
log_write(0, LOG_MAIN|LOG_REJECT, "rejected %s from %s: syntactically "
"invalid argument(s): %s", hello, host_and_ident(FALSE),
{
if (helo_required)
{
- smtp_printf("%d %s argument does not match calling host\r\n",
+ 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 " : "",
#endif
smtp_code = US"250 "; /* Default response code plus space*/
- if (user_msg == NULL)
+ if (!user_msg)
{
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;
+ 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 != NULL)
+ if (sender_host_address)
{
- 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);
+ g = string_catn(g, US" [", 2);
+ g = string_cat (g, sender_host_address);
+ g = string_catn(g, US"]", 1);
}
}
"newlines: message truncated: %s", string_printing(s));
*ss = 0;
}
- ptr = Ustrlen(s);
- size = ptr + 1;
+ g = string_cat(NULL, s);
}
- s = string_catn(s, &size, &ptr, US"\r\n", 2);
+ g = string_catn(g, US"\r\n", 2);
/* If we received EHLO, we must create a multiline response which includes
the functions supported. */
if (esmtp)
{
- s[3] = '-';
+ g->s[3] = '-';
/* 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
{
sprintf(CS big_buffer, "%.3s-SIZE %d\r\n", smtp_code,
thismessage_size_limit);
- s = string_cat(s, &size, &ptr, big_buffer);
+ g = string_cat(g, big_buffer);
}
else
{
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-SIZE\r\n", 7);
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-SIZE\r\n", 7);
}
/* Exim does not do protocol conversion or data conversion. It is 8-bit
if (accept_8bitmime)
{
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-8BITMIME\r\n", 11);
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-8BITMIME\r\n", 11);
}
/* 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);
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-DSN\r\n", 6);
dsn_advertised = TRUE;
}
if (acl_smtp_etrn)
{
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-ETRN\r\n", 7);
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-ETRN\r\n", 7);
}
if (acl_smtp_vrfy)
{
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-VRFY\r\n", 7);
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-VRFY\r\n", 7);
}
if (acl_smtp_expn)
{
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-EXPN\r\n", 7);
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-EXPN\r\n", 7);
}
/* Exim is quite happy with pipelining, so let the other end know that
if (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);
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-PIPELINING\r\n", 13);
sync_cmd_limit = NON_SYNC_CMD_PIPELINING;
pipelining_advertised = TRUE;
}
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")))
+ {
+ au->advertised = FALSE;
+ if (au->server)
{
- int saveptr;
- if (first)
+ 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")
+ )
{
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US"-AUTH", 5);
- first = FALSE;
- auth_advertised = TRUE;
+ 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 */
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);
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-CHUNKING\r\n", 11);
chunking_offered = TRUE;
chunking_state = CHUNKING_OFFERED;
}
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);
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-STARTTLS\r\n", 11);
tls_advertised = TRUE;
}
#endif
/* 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);
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US"-PRDR\r\n", 7);
}
#endif
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);
+ 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. */
- s = string_catn(s, &size, &ptr, smtp_code, 3);
- s = string_catn(s, &size, &ptr, US" HELP\r\n", 7);
+ g = string_catn(g, smtp_code, 3);
+ g = string_catn(g, US" HELP\r\n", 7);
}
/* Terminate the string (for debug), write it, and note that HELO/EHLO
has been seen. */
- s[ptr] = 0;
-
#ifdef SUPPORT_TLS
- if (tls_in.active >= 0) (void)tls_write(TRUE, s, ptr); else
+ if (tls_in.active >= 0) (void)tls_write(TRUE, g->s, g->ptr, FALSE); else
#endif
{
- int i = fwrite(s, 1, ptr, smtp_out); i = i; /* compiler quietening */
+ int i = fwrite(g->s, 1, g->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);
+
+ (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;
: pnormal)
+ (tls_in.active >= 0 ? pcrpted : 0)
];
+ cancel_cutthrough_connection(TRUE, US"sent EHLO response");
smtp_reset(reset_point);
toomany = FALSE;
break; /* HELO/EHLO */
if (helo_required && !helo_seen)
{
- smtp_printf("503 HELO or EHLO required\r\n");
+ 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 (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");
+ smtp_printf("421 too many messages in this connection\r\n", FALSE);
log_write(0, LOG_MAIN|LOG_REJECT, "rejected MAIL command %s: too many "
"messages in one connection", host_and_ident(TRUE));
break;
/* Reset for start of message - even if this is going to fail, we
obviously need to throw away any previous data. */
+ cancel_cutthrough_connection(TRUE, US"MAIL received");
smtp_reset(reset_point);
toomany = FALSE;
sender_data = recipient_data = NULL;
case ENV_MAIL_OPT_UTF8:
if (smtputf8_advertised)
{
+ int old_pool = store_pool;
+
DEBUG(D_receive) debug_printf("smtputf8 requested\n");
message_smtputf8 = allow_utf8_domains = TRUE;
+ store_pool = POOL_PERM;
received_protocol = string_sprintf("utf8%s", received_protocol);
+ store_pool = old_pool;
}
break;
#endif
if (thismessage_size_limit > 0 && message_size > thismessage_size_limit)
{
- smtp_printf("552 Message size exceeds maximum permitted\r\n");
+ 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",
(smtp_check_spool_space && message_size >= 0)?
message_size + 5000 : 0))
{
- smtp_printf("452 Space shortage, please try later\r\n");
+ smtp_printf("452 Space shortage, please try later\r\n", FALSE);
sender_address = NULL;
break;
}
}
else
{
- smtp_printf("501 %s: sender address must contain a domain\r\n",
+ 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,
if (rc == OK || rc == DISCARD)
{
+ BOOL more = pipeline_response();
+
if (!user_msg)
- smtp_printf("%s%s%s", US"250 OK",
+ smtp_printf("%s%s%s", more, US"250 OK",
#ifndef DISABLE_PRDR
prdr_requested ? US", PRDR Requested" : US"",
#else
{
if (pipelining_advertised && last_was_rej_mail)
{
- smtp_printf("503 sender not yet given\r\n");
+ smtp_printf("503 sender not yet given\r\n", FALSE);
was_rej_mail = TRUE;
}
else
if (recipients_max_reject)
{
rcpt_fail_count++;
- smtp_printf("552 too many recipients\r\n");
+ 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");
+ 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,
if (rc == OK)
{
+ BOOL more = pipeline_response();
+
if (user_msg)
smtp_user_msg(US"250", user_msg);
else
- smtp_printf("250 Accepted\r\n");
+ smtp_printf("250 Accepted\r\n", more);
receive_add_recipient(recipient, -1);
/* Set the dsn flags in the recipients_list */
if (user_msg)
smtp_user_msg(US"250", user_msg);
else
- smtp_printf("250 Accepted\r\n");
+ smtp_printf("250 Accepted\r\n", FALSE);
rcpt_fail_count++;
discarded = TRUE;
log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: "
(int)chunking_state, chunking_data_left);
lwr_receive_getc = receive_getc;
+ lwr_receive_getbuf = receive_getbuf;
lwr_receive_ungetc = receive_ungetc;
receive_getc = bdat_getc;
receive_ungetc = bdat_ungetc;
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_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,
{
sender_address = NULL; /* This will allow a new MAIL without RSET */
sender_address_unrewritten = NULL;
- smtp_printf("554 Too many recipients\r\n");
+ smtp_printf("554 Too many recipients\r\n", FALSE);
break;
}
smtp_user_msg(US"354", user_msg);
else
smtp_printf(
- "354 Enter message, ending with \".\" on a line by itself\r\n");
+ "354 Enter message, ending with \".\" on a line by itself\r\n", FALSE);
}
#ifdef TCP_QUICKACK
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;
}
break;
}
- smtp_printf("%s\r\n", s);
+ smtp_printf("%s\r\n", FALSE, s);
}
break;
}
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;
We must allow for an extra EHLO command and an extra AUTH command after
STARTTLS that don't add to the nonmail command count. */
- if ((rc = tls_server_start(tls_require_ciphers)) == OK)
+ s = NULL;
+ if ((rc = tls_server_start(tls_require_ciphers, &s)) == OK)
{
if (!tls_remember_esmtp)
helo_seen = esmtp = auth_advertised = pipelining_advertised = FALSE;
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. */
- else if (rc == DEFER)
+ if (rc == DEFER)
{
- smtp_printf("454 TLS currently unavailable\r\n");
+ smtp_printf("454 TLS currently unavailable\r\n", FALSE);
break;
}
if (user_msg)
smtp_respond(US"221", 3, TRUE, user_msg);
else
- smtp_printf("221 %s closing connection\r\n", smtp_active_hostname);
+ 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");
+ smtp_printf("554 Security failure\r\n", FALSE);
break;
}
tls_close(TRUE, TRUE);
case RSET_CMD:
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");
+ smtp_printf("250 OK\r\n", FALSE);
break;
case HELP_CMD:
HAD(SCH_HELP);
- smtp_printf("214-Commands supported:\r\n");
+ smtp_printf("214-Commands supported:\r\n", TRUE);
{
uschar buffer[256];
buffer[0] = 0;
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);
+ smtp_printf("214%s\r\n", FALSE, buffer);
}
break;
just drop the call rather than sending QUIT, and it clutters up the logs.
*/
- 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);
+ 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)
+ );
- else log_write(L_smtp_connection, LOG_MAIN, "%s lost%s",
- smtp_get_connection_info(), smtp_read_error);
+ 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;
{
log_write(0, LOG_MAIN|LOG_PANIC, "failed to set up ETRN command: %s",
error);
- smtp_printf("458 Internal failure\r\n");
+ smtp_printf("458 Internal failure\r\n", FALSE);
break;
}
}
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");
+ if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE);
else smtp_user_msg(US"250", user_msg);
break;
}
if (smtp_etrn_serialize && !enq_start(etrn_serialize_key, 1))
{
- smtp_printf("458 Already processing %s\r\n", smtp_cmd_data);
+ smtp_printf("458 Already processing %s\r\n", FALSE, smtp_cmd_data);
break;
}
{
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");
+ smtp_printf("458 Unable to fork process\r\n", FALSE);
if (smtp_etrn_serialize) enq_end(etrn_serialize_key);
}
else
{
- if (user_msg == NULL) smtp_printf("250 OK\r\n");
+ if (user_msg == NULL) smtp_printf("250 OK\r\n", FALSE);
else smtp_user_msg(US"250", user_msg);
}
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");
+ smtp_printf("501 NULL characters are not allowed in SMTP commands\r\n", FALSE);
break;
#ifdef SUPPORT_PROXY
case PROXY_FAIL_IGNORE_CMD:
- smtp_printf("503 Command refused, required Proxy negotiation failed\r\n");
+ smtp_printf("503 Command refused, required Proxy negotiation failed\r\n", FALSE);
break;
#endif