* 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. */
}
}
-static void
+void
bdat_flush_data(void)
{
while (chunking_data_left > 0)
* 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, void *vto, int capacity)
+{
+ uschar *to = (uschar *)vto;
+ int have = 0;
+ int ret;
+ int last = 0;
+
+ 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 {
int get_ok = 0;
int size, ret;
int fd = fileno(smtp_in);
+#define PROXY_INITIAL_READ 12
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);
+ /* We assume that the zie of a v2sig is less than the size of a complete
+ PROXYv1 line, so that we will _have_ to read more data; "PROXY TCPn\r\n"
+ is 12, so an IP address pushes it over, so we're good. */
+ ret = recv(fd, &hdr, PROXY_INITIAL_READ, 0);
}
while (ret == -1 && errno == EINTR);
if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0)
{
- uint8_t ver = (hdr.v2.ver_cmd & 0xf0) >> 4;
- uint8_t cmd = (hdr.v2.ver_cmd & 0x0f);
+ uint8_t ver, cmd;
+ int ret2nd;
+
+ /* It's now safe to read the rest. */
+ do {
+ ret2nd = recv(fd, (uschar*)&hdr + ret, sizeof(hdr)-ret, 0);
+ } while (ret2nd == -1 && errno == EINTR);
+ if (ret2nd == -1)
+ goto proxyfail;
+ ret += ret2nd;
+
+ ver = (hdr.v2.ver_cmd & 0xf0) >> 4;
+ cmd = (hdr.v2.ver_cmd & 0x0f);
/* May 2014: haproxy combined the version and command into one byte to
allow two full bytes for the length field in order to proxy SSL
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;
+ /* 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[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;
}
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:" */
uschar *v = smtp_cmd_data + Ustrlen(smtp_cmd_data) - 1;
while (isspace(*v)) v--;
v[1] = 0;
-while (v > smtp_cmd_data && *v != '=' && !isspace(*v)) v--;
+while (v > smtp_cmd_data && *v != '=' && !isspace(*v))
+ {
+ /* Take care to not stop at a space embedded in a quoted local-part */
+
+ if (*v == '"') do v--; while (*v != '"' && v > smtp_cmd_data+1);
+ v--;
+ }
n = v;
if (*v == '=')
"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)
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)
{
}
return FALSE;
}
- #endif
+#endif
/* Check for reserved slots. The value of smtp_accept_count has already been
incremented to include this process. */
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
+
+ /* 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
-/* Run the ACL if it exists */
+/* Run the connect ACL if it exists */
user_msg = NULL;
if (acl_smtp_connect)