* 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. */
}
+/*************************************************
+* 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 *
*************************************************/
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);
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
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. */
p += 5;
{
/* 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;
}
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;
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);
}
+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;
+}
+
+
/*************************************************
* Start an SMTP session *
*************************************************/
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;
+ 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 */
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
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");
break;