X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/1811cb4cb491183bd4b32bca3e80f77b4c8d391d..40525d07a858c90293bc09188fb539a1cec3f8aa:/src/src/smtp_in.c diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 8f4309686..8832908f3 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 **); @@ -344,6 +343,9 @@ if (!smtp_enforce_sync || sender_host_address == NULL || sender_host_notsocket || tls_in.active >= 0) return TRUE; +if (smtp_inptr < smtp_inend) + return FALSE; + fd = fileno(smtp_in); FD_ZERO(&fds); FD_SET(fd, &fds); @@ -401,6 +403,44 @@ log_write(L_smtp_incomplete_transaction, LOG_MAIN|LOG_SENDER|LOG_RECIPIENTS, +/* 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() * *************************************************/ @@ -418,39 +458,28 @@ int 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) { @@ -486,12 +515,18 @@ 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_getbuf = lwr_receive_getbuf; receive_ungetc = lwr_receive_ungetc; #ifndef DISABLE_DKIM + dkim_save = dkim_collect_input; dkim_collect_input = FALSE; #endif @@ -500,12 +535,15 @@ for(;;) 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\"", smtp_cmd_buffer, host_and_ident(TRUE), - string_printing(smtp_inptr)); + string_printing(string_copyn(smtp_inptr, n))); (void) synprot_error(L_smtp_protocol_error, 554, NULL, US"SMTP synchronization error"); goto repeat_until_rset; @@ -590,9 +628,10 @@ next_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 */ } @@ -600,14 +639,28 @@ next_cmd: } } -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; +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) @@ -921,6 +974,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 * *************************************************/ @@ -973,11 +1091,41 @@ 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); @@ -996,38 +1144,80 @@ 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 - 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 +1285,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 +1295,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++))) { @@ -1214,6 +1416,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; } @@ -1730,11 +1933,9 @@ 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; -cancel_cutthrough_connection("smtp reset"); message_linecount = 0; message_size = -1; acl_added_headers = NULL; @@ -1753,7 +1954,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 */ @@ -1766,6 +1972,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; @@ -1773,6 +1980,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 @@ -1799,13 +2007,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; @@ -1815,12 +2023,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); } @@ -1857,6 +2066,7 @@ bsmtp_transaction_linecount = receive_linecount; 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 @@ -1881,6 +2091,7 @@ while (done <= 0) /* Fall through */ case RSET_CMD: + cancel_cutthrough_connection(TRUE, US"RSET received"); smtp_reset(reset_point); bsmtp_transaction_linecount = receive_linecount; break; @@ -1904,6 +2115,7 @@ while (done <= 0) /* Reset to start of message */ + cancel_cutthrough_connection(TRUE, US"MAIL received"); smtp_reset(reset_point); /* Apply SMTP rewrite */ @@ -2063,6 +2275,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 * *************************************************/ @@ -2156,6 +2381,7 @@ if (!(smtp_inbuffer = (uschar *)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; @@ -2551,8 +2777,8 @@ if (check_proxy_protocol_host()) 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 */ @@ -2643,10 +2869,13 @@ 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)); + string_printing(string_copyn(smtp_inptr, n))); smtp_printf("554 SMTP synchronization error\r\n"); return FALSE; } @@ -4079,6 +4308,7 @@ while (done <= 0) : pnormal) + (tls_in.active >= 0 ? pcrpted : 0) ]; + cancel_cutthrough_connection(TRUE, US"sent EHLO response"); smtp_reset(reset_point); toomany = FALSE; break; /* HELO/EHLO */ @@ -4133,6 +4363,7 @@ while (done <= 0) /* 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; @@ -4321,9 +4552,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,6 +5028,7 @@ while (done <= 0) (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; @@ -4984,6 +5220,7 @@ while (done <= 0) 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; @@ -5022,7 +5259,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; @@ -5051,11 +5289,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; @@ -5117,6 +5357,7 @@ while (done <= 0) case RSET_CMD: smtp_rset_handler(); + cancel_cutthrough_connection(TRUE, US"RSET received"); smtp_reset(reset_point); toomany = FALSE; break;