X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/bd8fbe3606d80e5a3fc02fe71b521146c6938448..5e6d12accb0bdfa1cee9d8c9a1ecec3131b9f502:/src/src/smtp_in.c diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 9c647efda..f0885b3a1 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -2,7 +2,7 @@ * 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. */ @@ -304,7 +304,6 @@ static int smtp_had_error; /* 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 **); @@ -486,12 +485,17 @@ uschar * log_msg; 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_ungetc = lwr_receive_ungetc; #ifndef DISABLE_DKIM + dkim_save = dkim_collect_input; dkim_collect_input = FALSE; #endif @@ -592,7 +596,7 @@ next_cmd: receive_getc = bdat_getc; receive_ungetc = bdat_ungetc; #ifndef DISABLE_DKIM - dkim_collect_input = TRUE; + dkim_collect_input = dkim_save; #endif break; /* to top of main loop */ } @@ -600,7 +604,7 @@ next_cmd: } } -static void +void bdat_flush_data(void) { while (chunking_data_left > 0) @@ -897,7 +901,8 @@ if (get_ok == 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 @@ -907,12 +912,10 @@ static 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"); @@ -922,6 +925,71 @@ return proxy_session; } +/************************************************* +* 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 * *************************************************/ @@ -934,7 +1002,7 @@ Arguments: none Returns: Boolean success */ -static BOOL +static void setup_proxy_protocol_host() { union { @@ -974,14 +1042,45 @@ struct sockaddr_in tmpaddr; 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); @@ -991,43 +1090,85 @@ 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 */ @@ -1095,6 +1236,7 @@ if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0) break; case 0x00: /* LOCAL command */ /* Keep local connection address for LOCAL */ + iptype = US"local"; break; default: DEBUG(D_receive) @@ -1104,22 +1246,33 @@ if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0) } else if (ret >= 8 && memcmp(hdr.v1.line, "PROXY", 5) == 0) { - uschar *p = string_copy(hdr.v1.line); - uschar *end = memchr(p, '\r', ret - 1); + uschar *p; + uschar *end; uschar *sp; /* Utility variables follow */ int tmp_port; + int r2; char *endc; - if (!end || end[1] != '\n') + /* get the rest of the line */ + r2 = swallow_until_crlf(fd, (uschar*)&hdr, ret, sizeof(hdr)-ret); + if (r2 == -1) + goto proxyfail; + ret += r2; + + p = string_copy(hdr.v1.line); + end = memchr(p, '\r', ret - 1); + + if (!end || (end == (uschar*)&hdr + ret) || end[1] != '\n') { DEBUG(D_receive) debug_printf("Partial or invalid PROXY header\n"); goto proxyfail; } *end = '\0'; /* Terminate the string */ - size = end + 2 - hdr.v1.line; /* Skip header + CRLF */ + size = end + 2 - p; /* Skip header + CRLF */ DEBUG(D_receive) debug_printf("Detected PROXYv1 header\n"); + DEBUG(D_receive) debug_printf("Bytes read not within PROXY header: %d\n", ret - size); /* Step through the string looking for the required fields. Ensure - strict adherence to required formatting, exit for any error. */ + strict adherence to required formatting, exit for any error. */ p += 5; if (!isspace(*(p++))) { @@ -1209,26 +1362,41 @@ else if (ret >= 8 && memcmp(hdr.v1.line, "PROXY", 5) == 0) } 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 @@ -1306,14 +1474,11 @@ if required. */ 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:" */ @@ -1674,7 +1839,13 @@ uschar *n; 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 == '=') @@ -1713,7 +1884,6 @@ Returns: nothing 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; @@ -1736,7 +1906,12 @@ submission_mode = FALSE; /* Can be set by ACL */ 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 */ @@ -1749,6 +1924,7 @@ authenticated_sender = NULL; bmi_run = 0; bmi_verdicts = NULL; #endif +dnslist_domain = dnslist_matched = NULL; #ifndef DISABLE_DKIM dkim_signers = NULL; dkim_disable_verify = FALSE; @@ -1756,6 +1932,7 @@ dkim_collect_input = 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 @@ -1782,13 +1959,13 @@ acl_var_m = NULL; 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; @@ -1798,12 +1975,13 @@ if (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); } @@ -2046,6 +2224,19 @@ return done - 2; /* Convert yield values */ +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 * *************************************************/ @@ -2398,14 +2589,6 @@ if (!sender_host_unknown) "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) @@ -2425,19 +2608,17 @@ if (!sender_host_unknown) 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) { @@ -2459,7 +2640,7 @@ if (!sender_host_unknown) } return FALSE; } - #endif +#endif /* Check for reserved slots. The value of smtp_accept_count has already been incremented to include this process. */ @@ -2530,27 +2711,25 @@ if (!sender_host_unknown) 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) @@ -4316,9 +4495,13 @@ while (done <= 0) 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 @@ -5017,7 +5200,8 @@ while (done <= 0) We must allow for an extra EHLO command and an extra AUTH command after STARTTLS that don't add to the nonmail command count. */ - 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; @@ -5046,11 +5230,13 @@ while (done <= 0) 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;