X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/9e7e0f6a60dea9e21aac8b4ea5f8725bdc52a743..4804c62909a62a3ac12ec4777ebd48c541028965:/src/src/smtp_in.c diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 91b1c7e6d..02075404d 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 */ } @@ -921,6 +925,69 @@ 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 * *************************************************/ @@ -973,6 +1040,36 @@ 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); @@ -996,18 +1093,32 @@ if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CS &tv, sizeof(tv)) < 0) 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 @@ -1019,15 +1130,43 @@ if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0) 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 */ @@ -1105,20 +1244,31 @@ 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. */ p += 5; @@ -1215,6 +1365,7 @@ 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; } @@ -1731,7 +1882,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; @@ -1754,7 +1904,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 */ @@ -1767,6 +1922,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; @@ -1774,6 +1930,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 @@ -1800,13 +1957,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; @@ -1816,12 +1973,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); } @@ -4322,9 +4480,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 @@ -4793,16 +4955,23 @@ while (done <= 0) DEBUG(D_receive) debug_printf("chunking state %d, %d bytes\n", (int)chunking_state, chunking_data_left); + /* push the current receive_* function on the "stack", and + replace them by bdat_getc(), which in turn will use the lwr_receive_* + functions to do the dirty work. */ lwr_receive_getc = receive_getc; lwr_receive_ungetc = receive_ungetc; + receive_getc = bdat_getc; receive_ungetc = bdat_ungetc; + dot_ends = FALSE; + goto DATA_BDAT; } case DATA_CMD: HAD(SCH_DATA); + dot_ends = TRUE; DATA_BDAT: /* Common code for DATA and BDAT */ if (!discarded && recipients_count <= 0)