X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=src%2Fsrc%2Fsmtp_in.c;h=b2f8b0fc808c0fabe0baf958dcda02bbeaa669ad;hb=1f155f8e69b44ee7678dd1009ae0348e5c8d768e;hp=4740aa5ff519e86e7ecbca2ff8815f7aa5523aa7;hpb=2d07a2158e07fbaddd52e734fcbaeff44bf61919;p=exim.git diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 4740aa5ff..b2f8b0fc8 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 - 2012 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions for handling an incoming SMTP call. */ @@ -94,6 +94,10 @@ enum { QUIT_CMD, HELP_CMD, +#ifdef EXPERIMENTAL_PROXY + PROXY_FAIL_IGNORE_CMD, +#endif + /* These are specials that don't correspond to actual commands */ EOF_CMD, OTHER_CMD, BADARG_CMD, BADCHAR_CMD, BADSYN_CMD, @@ -117,6 +121,7 @@ static BOOL auth_advertised; #ifdef SUPPORT_TLS static BOOL tls_advertised; #endif +static BOOL dsn_advertised; static BOOL esmtp; static BOOL helo_required = FALSE; static BOOL helo_verify = FALSE; @@ -128,6 +133,9 @@ static BOOL rcpt_smtp_response_same; static BOOL rcpt_in_progress; static int nonmail_command_count; static BOOL smtp_exit_function_called = 0; +#ifdef EXPERIMENTAL_INTERNATIONAL +static BOOL smtputf8_advertised; +#endif static int synprot_error_count; static int unknown_command_count; static int sync_cmd_limit; @@ -153,6 +161,8 @@ QUIT is also "falsely" labelled as a mail command so that it doesn't up the count of non-mail commands and possibly provoke an error. */ static smtp_cmd_list cmd_list[] = { + /* name len cmd has_arg is_mail_cmd */ + { "rset", sizeof("rset")-1, RSET_CMD, FALSE, FALSE }, /* First */ { "helo", sizeof("helo")-1, HELO_CMD, TRUE, FALSE }, { "ehlo", sizeof("ehlo")-1, EHLO_CMD, TRUE, FALSE }, @@ -192,7 +202,7 @@ static uschar *smtp_names[] = US"HELP", US"MAIL", US"NOOP", US"QUIT", US"RCPT", US"RSET", US"STARTTLS", US"VRFY" }; -static uschar *protocols[] = { +static uschar *protocols_local[] = { US"local-smtp", /* HELO */ US"local-smtps", /* The rare case EHLO->STARTTLS->HELO */ US"local-esmtp", /* EHLO */ @@ -200,18 +210,29 @@ static uschar *protocols[] = { US"local-esmtpa", /* EHLO->AUTH */ US"local-esmtpsa" /* EHLO->STARTTLS->EHLO->AUTH */ }; +static uschar *protocols[] = { + US"smtp", /* HELO */ + US"smtps", /* The rare case EHLO->STARTTLS->HELO */ + US"esmtp", /* EHLO */ + US"esmtps", /* EHLO->STARTTLS->EHLO */ + US"esmtpa", /* EHLO->AUTH */ + US"esmtpsa" /* EHLO->STARTTLS->EHLO->AUTH */ + }; #define pnormal 0 #define pextend 2 #define pcrpted 1 /* added to pextend or pnormal */ #define pauthed 2 /* added to pextend */ -#define pnlocal 6 /* offset to remove "local" */ /* Sanity check and validate optional args to MAIL FROM: envelope */ enum { ENV_MAIL_OPT_SIZE, ENV_MAIL_OPT_BODY, ENV_MAIL_OPT_AUTH, -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR ENV_MAIL_OPT_PRDR, +#endif + ENV_MAIL_OPT_RET, ENV_MAIL_OPT_ENVID, +#ifdef EXPERIMENTAL_INTERNATIONAL + ENV_MAIL_OPT_UTF8, #endif ENV_MAIL_OPT_NULL }; @@ -225,8 +246,13 @@ static env_mail_type_t env_mail_type_list[] = { { US"SIZE", ENV_MAIL_OPT_SIZE, TRUE }, { US"BODY", ENV_MAIL_OPT_BODY, TRUE }, { US"AUTH", ENV_MAIL_OPT_AUTH, TRUE }, -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR { US"PRDR", ENV_MAIL_OPT_PRDR, FALSE }, +#endif + { US"RET", ENV_MAIL_OPT_RET, TRUE }, + { US"ENVID", ENV_MAIL_OPT_ENVID, TRUE }, +#ifdef EXPERIMENTAL_INTERNATIONAL + { US"SMTPUTF8",ENV_MAIL_OPT_UTF8, FALSE }, /* rfc6531 */ #endif { US"NULL", ENV_MAIL_OPT_NULL, FALSE } }; @@ -549,6 +575,375 @@ exim_exit(EXIT_FAILURE); +#ifdef EXPERIMENTAL_PROXY +/************************************************* +* Restore socket timeout to previous value * +*************************************************/ +/* If the previous value was successfully retrieved, restore +it before returning control to the non-proxy routines + +Arguments: fd - File descriptor for input + get_ok - Successfully retrieved previous values + tvtmp - Time struct with previous values + vslen - Length of time struct +Returns: none +*/ +static void +restore_socket_timeout(int fd, int get_ok, struct timeval tvtmp, socklen_t vslen) +{ +if (get_ok == 0) + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tvtmp, vslen); +} + +/************************************************* +* 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. + +Arguments: none +Returns: bool +*/ + +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(&proxy_required_hosts, NULL, NULL, + sender_host_address, NULL); +if (rc == OK) + { + DEBUG(D_receive) + debug_printf("Detected proxy protocol configured host\n"); + proxy_session = TRUE; + } +return proxy_session; +} + + +/************************************************* +* Setup host for proxy protocol * +*************************************************/ +/* The function configures the connection based on a header from the +inbound host to use Proxy Protocol. The specification is very exact +so exit with an error if do not find the exact required pieces. This +includes an incorrect number of spaces separating args. + +Arguments: none +Returns: int +*/ + +static BOOL +setup_proxy_protocol_host() +{ +union { + struct { + uschar line[108]; + } v1; + struct { + uschar sig[12]; + uint8_t ver_cmd; + uint8_t fam; + uint16_t len; + union { + struct { /* TCP/UDP over IPv4, len = 12 */ + uint32_t src_addr; + uint32_t dst_addr; + uint16_t src_port; + uint16_t dst_port; + } ip4; + struct { /* TCP/UDP over IPv6, len = 36 */ + uint8_t src_addr[16]; + uint8_t dst_addr[16]; + uint16_t src_port; + uint16_t dst_port; + } ip6; + struct { /* AF_UNIX sockets, len = 216 */ + uschar src_addr[108]; + uschar dst_addr[108]; + } unx; + } addr; + } v2; +} hdr; + +/* Temp variables used in PPv2 address:port parsing */ +uint16_t tmpport; +char tmpip[INET_ADDRSTRLEN]; +struct sockaddr_in tmpaddr; +char tmpip6[INET6_ADDRSTRLEN]; +struct sockaddr_in6 tmpaddr6; + +int get_ok = 0; +int size, ret, fd; +const char v2sig[12] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"; +uschar *iptype; /* To display debug info */ +struct timeval tv; +socklen_t vslen = 0; +struct timeval tvtmp; + +vslen = sizeof(struct timeval); + +fd = fileno(smtp_in); + +/* Save current socket timeout values */ +get_ok = getsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tvtmp, + &vslen); + +/* Proxy Protocol host must send header within a short time +(default 3 seconds) or it's considered invalid */ +tv.tv_sec = PROXY_NEGOTIATION_TIMEOUT_SEC; +tv.tv_usec = PROXY_NEGOTIATION_TIMEOUT_USEC; +setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, + 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); + } + while (ret == -1 && errno == EINTR); + +if (ret == -1) + { + restore_socket_timeout(fd, get_ok, tvtmp, vslen); + return (errno == EAGAIN) ? 0 : ERRNO_PROXYFAIL; + } + +if (ret >= 16 && + memcmp(&hdr.v2, v2sig, 12) == 0) + { + uint8_t ver, cmd; + + /* 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 seperate values here. */ + ver = (hdr.v2.ver_cmd & 0xf0) >> 4; + cmd = (hdr.v2.ver_cmd & 0x0f); + + 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 + hdr.v2.len; + if (ret < size) + { + DEBUG(D_receive) debug_printf("Truncated or too large PROXYv2 header (%d/%d)\n", + ret, size); + goto proxyfail; + } + switch (cmd) + { + case 0x01: /* PROXY command */ + switch (hdr.v2.fam) + { + case 0x11: /* TCPv4 address type */ + iptype = US"IPv4"; + tmpaddr.sin_addr.s_addr = hdr.v2.addr.ip4.src_addr; + inet_ntop(AF_INET, &(tmpaddr.sin_addr), (char *)&tmpip, sizeof(tmpip)); + if (!string_is_ip_address(US tmpip,NULL)) + { + DEBUG(D_receive) debug_printf("Invalid %s source IP\n", iptype); + return ERRNO_PROXYFAIL; + } + proxy_host_address = sender_host_address; + sender_host_address = string_copy(US tmpip); + tmpport = ntohs(hdr.v2.addr.ip4.src_port); + proxy_host_port = sender_host_port; + sender_host_port = tmpport; + /* Save dest ip/port */ + tmpaddr.sin_addr.s_addr = hdr.v2.addr.ip4.dst_addr; + inet_ntop(AF_INET, &(tmpaddr.sin_addr), (char *)&tmpip, sizeof(tmpip)); + if (!string_is_ip_address(US tmpip,NULL)) + { + DEBUG(D_receive) debug_printf("Invalid %s dest port\n", iptype); + return ERRNO_PROXYFAIL; + } + proxy_target_address = string_copy(US tmpip); + tmpport = ntohs(hdr.v2.addr.ip4.dst_port); + proxy_target_port = tmpport; + goto done; + case 0x21: /* TCPv6 address type */ + iptype = US"IPv6"; + memmove(tmpaddr6.sin6_addr.s6_addr, hdr.v2.addr.ip6.src_addr, 16); + inet_ntop(AF_INET6, &(tmpaddr6.sin6_addr), (char *)&tmpip6, sizeof(tmpip6)); + if (!string_is_ip_address(US tmpip6,NULL)) + { + DEBUG(D_receive) debug_printf("Invalid %s source IP\n", iptype); + return ERRNO_PROXYFAIL; + } + proxy_host_address = sender_host_address; + sender_host_address = string_copy(US tmpip6); + tmpport = ntohs(hdr.v2.addr.ip6.src_port); + proxy_host_port = sender_host_port; + sender_host_port = tmpport; + /* Save dest ip/port */ + memmove(tmpaddr6.sin6_addr.s6_addr, hdr.v2.addr.ip6.dst_addr, 16); + inet_ntop(AF_INET6, &(tmpaddr6.sin6_addr), (char *)&tmpip6, sizeof(tmpip6)); + if (!string_is_ip_address(US tmpip6,NULL)) + { + DEBUG(D_receive) debug_printf("Invalid %s dest port\n", iptype); + return ERRNO_PROXYFAIL; + } + proxy_target_address = string_copy(US tmpip6); + tmpport = ntohs(hdr.v2.addr.ip6.dst_port); + proxy_target_port = tmpport; + goto done; + default: + DEBUG(D_receive) + debug_printf("Unsupported PROXYv2 connection type: 0x%02x\n", + hdr.v2.fam); + goto proxyfail; + } + /* Unsupported protocol, keep local connection address */ + break; + case 0x00: /* LOCAL command */ + /* Keep local connection address for LOCAL */ + break; + default: + DEBUG(D_receive) + debug_printf("Unsupported PROXYv2 command: 0x%x\n", cmd); + goto proxyfail; + } + } +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 *sp; /* Utility variables follow */ + int tmp_port; + char *endc; + + 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 */ + DEBUG(D_receive) debug_printf("Detected PROXYv1 header\n"); + /* Step through the string looking for the required fields. Ensure + strict adherance to required formatting, exit for any error. */ + p += 5; + if (!isspace(*(p++))) + { + DEBUG(D_receive) debug_printf("Missing space after PROXY command\n"); + goto proxyfail; + } + if (!Ustrncmp(p, CCS"TCP4", 4)) + iptype = US"IPv4"; + else if (!Ustrncmp(p,CCS"TCP6", 4)) + iptype = US"IPv6"; + else if (!Ustrncmp(p,CCS"UNKNOWN", 7)) + { + iptype = US"Unknown"; + goto done; + } + else + { + DEBUG(D_receive) debug_printf("Invalid TCP type\n"); + goto proxyfail; + } + + p += Ustrlen(iptype); + if (!isspace(*(p++))) + { + DEBUG(D_receive) debug_printf("Missing space after TCP4/6 command\n"); + goto proxyfail; + } + /* Find the end of the arg */ + if ((sp = Ustrchr(p, ' ')) == NULL) + { + DEBUG(D_receive) + debug_printf("Did not find proxied src %s\n", iptype); + goto proxyfail; + } + *sp = '\0'; + if(!string_is_ip_address(p,NULL)) + { + DEBUG(D_receive) + debug_printf("Proxied src arg is not an %s address\n", iptype); + goto proxyfail; + } + proxy_host_address = sender_host_address; + sender_host_address = p; + p = sp + 1; + if ((sp = Ustrchr(p, ' ')) == NULL) + { + DEBUG(D_receive) + debug_printf("Did not find proxy dest %s\n", iptype); + goto proxyfail; + } + *sp = '\0'; + if(!string_is_ip_address(p,NULL)) + { + DEBUG(D_receive) + debug_printf("Proxy dest arg is not an %s address\n", iptype); + goto proxyfail; + } + proxy_target_address = p; + p = sp + 1; + if ((sp = Ustrchr(p, ' ')) == NULL) + { + DEBUG(D_receive) debug_printf("Did not find proxied src port\n"); + goto proxyfail; + } + *sp = '\0'; + tmp_port = strtol(CCS p,&endc,10); + if (*endc || tmp_port == 0) + { + DEBUG(D_receive) + debug_printf("Proxied src port '%s' not an integer\n", p); + goto proxyfail; + } + proxy_host_port = sender_host_port; + sender_host_port = tmp_port; + p = sp + 1; + if ((sp = Ustrchr(p, '\0')) == NULL) + { + DEBUG(D_receive) debug_printf("Did not find proxy dest port\n"); + goto proxyfail; + } + tmp_port = strtol(CCS p,&endc,10); + if (*endc || tmp_port == 0) + { + DEBUG(D_receive) + debug_printf("Proxy dest port '%s' not an integer\n", p); + goto proxyfail; + } + proxy_target_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"); + goto proxyfail; + } + +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; + +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; +} +#endif + /************************************************* * Read one command line * *************************************************/ @@ -622,6 +1017,14 @@ if required. */ for (p = cmd_list; p < cmd_list_end; p++) { + #ifdef EXPERIMENTAL_PROXY + /* Only allow QUIT command if Proxy Protocol parsing failed */ + if (proxy_session && proxy_session_failed) + { + if (p->cmd != QUIT_CMD) + continue; + } + #endif if (strncmpic(smtp_cmd_buffer, US p->name, p->len) == 0 && (smtp_cmd_buffer[p->len-1] == ':' || /* "mail from:" or "rcpt to:" */ smtp_cmd_buffer[p->len] == 0 || @@ -669,6 +1072,12 @@ for (p = cmd_list; p < cmd_list_end; p++) } } +#ifdef EXPERIMENTAL_PROXY +/* Only allow QUIT command if Proxy Protocol parsing failed */ +if (proxy_session && proxy_session_failed) + return PROXY_FAIL_IGNORE_CMD; +#endif + /* Enforce synchronization for unknown commands */ if (smtp_inptr < smtp_inend && /* Outstanding input */ @@ -825,6 +1234,45 @@ return string_sprintf("SMTP connection from %s", hostname); +#ifdef SUPPORT_TLS +/* Append TLS-related information to a log line + +Arguments: + s String under construction: allocated string to extend, or NULL + sizep Pointer to current allocation size (update on return), or NULL + ptrp Pointer to index for new entries in string (update on return), or NULL + +Returns: Allocated string or NULL +*/ +static uschar * +s_tlslog(uschar * s, int * sizep, int * ptrp) +{ + int size = sizep ? *sizep : 0; + int ptr = ptrp ? *ptrp : 0; + + if ((log_extra_selector & LX_tls_cipher) != 0 && tls_in.cipher != NULL) + s = string_append(s, &size, &ptr, 2, US" X=", tls_in.cipher); + if ((log_extra_selector & LX_tls_certificate_verified) != 0 && + tls_in.cipher != NULL) + s = string_append(s, &size, &ptr, 2, US" CV=", + tls_in.certificate_verified? "yes":"no"); + if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_in.peerdn != NULL) + s = string_append(s, &size, &ptr, 3, US" DN=\"", + string_printing(tls_in.peerdn), US"\""); + if ((log_extra_selector & LX_tls_sni) != 0 && tls_in.sni != NULL) + s = string_append(s, &size, &ptr, 3, US" SNI=\"", + string_printing(tls_in.sni), US"\""); + + if (s) + { + s[ptr] = '\0'; + if (sizep) *sizep = size; + if (ptrp) *ptrp = ptr; + } + return s; +} +#endif + /************************************************* * Log lack of MAIL if so configured * *************************************************/ @@ -857,18 +1305,7 @@ if (sender_host_authenticated != NULL) } #ifdef SUPPORT_TLS -if ((log_extra_selector & LX_tls_cipher) != 0 && tls_in.cipher != NULL) - s = string_append(s, &size, &ptr, 2, US" X=", tls_in.cipher); -if ((log_extra_selector & LX_tls_certificate_verified) != 0 && - tls_in.cipher != NULL) - s = string_append(s, &size, &ptr, 2, US" CV=", - tls_in.certificate_verified? "yes":"no"); -if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_in.peerdn != NULL) - s = string_append(s, &size, &ptr, 3, US" DN=\"", - string_printing(tls_in.peerdn), US"\""); -if ((log_extra_selector & LX_tls_sni) != 0 && tls_in.sni != NULL) - s = string_append(s, &size, &ptr, 3, US" SNI=\"", - string_printing(tls_in.sni), US"\""); +s = s_tlslog(s, &size, &ptr); #endif sep = (smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE)? @@ -892,7 +1329,8 @@ for (i = 0; i < smtp_ch_index; i++) if (s != NULL) s[ptr] = 0; else s = US""; log_write(0, LOG_MAIN, "no MAIL in SMTP connection from %s D=%s%s", host_and_ident(FALSE), - readconf_printtime(time(NULL) - smtp_connection_start), s); + readconf_printtime( (int) ((long)time(NULL) - (long)smtp_connection_start)), + s); } @@ -1073,6 +1511,13 @@ sender_address_unrewritten = NULL; /* Set only after verify rewrite */ sender_verified_list = NULL; /* No senders verified */ memset(sender_address_cache, 0, sizeof(sender_address_cache)); memset(sender_domain_cache, 0, sizeof(sender_domain_cache)); + +prdr_requested = FALSE; + +/* Reset the DSN flags */ +dsn_ret = 0; +dsn_envid = NULL; + authenticated_sender = NULL; #ifdef EXPERIMENTAL_BRIGHTMAIL bmi_run = 0; @@ -1089,6 +1534,9 @@ spf_received = NULL; spf_result = NULL; spf_smtp_comment = NULL; #endif +#ifdef EXPERIMENTAL_INTERNATIONAL +message_smtputf8 = FALSE; +#endif body_linecount = body_zerocount = 0; sender_rate = sender_rate_limit = sender_rate_period = NULL; @@ -1417,8 +1865,15 @@ authenticated_by = NULL; #ifdef SUPPORT_TLS tls_in.cipher = tls_in.peerdn = NULL; +tls_in.ourcert = tls_in.peercert = NULL; +tls_in.sni = NULL; +tls_in.ocsp = OCSP_NOT_REQ; tls_advertised = FALSE; #endif +dsn_advertised = FALSE; +#ifdef EXPERIMENTAL_INTERNATIONAL +smtputf8_advertised = FALSE; +#endif /* Reset ACL connection variables */ @@ -1446,7 +1901,7 @@ reset later if any of EHLO/AUTH/STARTTLS are received. */ else received_protocol = - protocols[pnormal] + ((sender_host_address != NULL)? pnlocal : 0); + (sender_host_address ? protocols : protocols_local) [pnormal]; /* Set up the buffer for inputting using direct read() calls, and arrange to call the local functions instead of the standard C ones. */ @@ -1700,6 +2155,19 @@ if (!sender_host_unknown) set_process_info("handling incoming connection from %s", host_and_ident(FALSE)); + /* Expand smtp_receive_timeout, if needed */ + + if (smtp_receive_timeout_s) + { + uschar * exp; + if ( !(exp = expand_string(smtp_receive_timeout_s)) + || !(*exp) + || (smtp_receive_timeout = readconf_readtime(exp, 0, FALSE)) < 0 + ) + log_write(0, LOG_MAIN|LOG_PANIC, + "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. */ @@ -1832,6 +2300,28 @@ if (!sender_host_unknown) if (smtp_batched_input) return TRUE; +#ifdef EXPERIMENTAL_PROXY +/* If valid Proxy Protocol source is connecting, set up session. + * Failure will not allow any SMTP function other than QUIT. */ +proxy_session = FALSE; +proxy_session_failed = FALSE; +if (check_proxy_protocol_host()) + { + if (setup_proxy_protocol_host() == FALSE) + { + 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(); + } + } +#endif + /* Run the ACL if it exists */ user_msg = NULL; @@ -1974,7 +2464,7 @@ if (++synprot_error_count > smtp_max_synprot_errors) yield = 1; log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many " "syntax or protocol errors (last command was \"%s\")", - host_and_ident(FALSE), smtp_cmd_buffer); + host_and_ident(FALSE), string_printing(smtp_cmd_buffer)); } if (code > 0) @@ -2211,7 +2701,7 @@ uschar *what = #endif (where == ACL_WHERE_PREDATA)? US"DATA" : (where == ACL_WHERE_DATA)? US"after DATA" : -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR (where == ACL_WHERE_PRDR)? US"after DATA PRDR" : #endif (smtp_cmd_data == NULL)? @@ -2334,9 +2824,17 @@ the connection is not forcibly to be dropped, return 0. Otherwise, log why it is closing if required and return 2. */ if (log_reject_target != 0) - log_write(0, log_reject_target, "%s %s%srejected %s%s", - host_and_ident(TRUE), + { +#ifdef SUPPORT_TLS + uschar * s = s_tlslog(NULL, NULL, NULL); + if (!s) s = US""; +#else + uschar * s = US""; +#endif + log_write(0, log_reject_target, "%s%s %s%srejected %s%s", + host_and_ident(TRUE), s, sender_info, (rc == FAIL)? US"" : US"temporarily ", what, log_msg); + } if (!drop) return 0; @@ -2511,6 +3009,7 @@ else if (helo_verified) { + /*XXX have sender_host_dnssec */ HDEBUG(D_receive) debug_printf("matched host name\n"); } else @@ -2520,6 +3019,7 @@ else { helo_verified = strcmpic(*aliases++, sender_helo_name) == 0; if (helo_verified) break; + /*XXX have sender_host_dnssec */ } HDEBUG(D_receive) { @@ -2541,6 +3041,8 @@ else h.next = NULL; HDEBUG(D_receive) debug_printf("getting IP address for %s\n", sender_helo_name); +/*XXX would like to determine dnssec status here */ +/* need to change to bydns */ rc = host_find_byname(&h, NULL, 0, NULL, TRUE); if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL) { @@ -2594,7 +3096,6 @@ smtp_respond(code, len, TRUE, user_msg); - /************************************************* * Initialize for SMTP incoming message * *************************************************/ @@ -2661,7 +3162,7 @@ value. The values are 2 larger than the required yield of the function. */ while (done <= 0) { - uschar **argv; + const uschar **argv; uschar *etrn_command; uschar *etrn_serialize_key; uschar *errmess; @@ -2669,7 +3170,7 @@ while (done <= 0) uschar *user_msg = NULL; uschar *recipient = NULL; uschar *hello = NULL; - uschar *set_id = NULL; + const uschar *set_id = NULL; uschar *s, *ss; BOOL was_rej_mail = FALSE; BOOL was_rcpt = FALSE; @@ -2679,6 +3180,8 @@ while (done <= 0) int ptr, size, rc; int c, i; auth_instance *au; + uschar *orcpt = NULL; + int flags; switch(smtp_read_command(TRUE)) { @@ -2822,9 +3325,10 @@ while (done <= 0) sender_host_authenticated = au->name; authentication_failed = FALSE; authenticated_fail_id = NULL; /* Impossible to already be set? */ + received_protocol = - protocols[pextend + pauthed + ((tls_in.active >= 0)? pcrpted:0)] + - ((sender_host_address != NULL)? pnlocal : 0); + (sender_host_address ? protocols : protocols_local) + [pextend + pauthed + (tls_in.active >= 0 ? pcrpted:0)]; s = ss = US"235 Authentication succeeded"; authenticated_by = au; break; @@ -2924,7 +3428,7 @@ while (done <= 0) { log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many " "syntax or protocol errors (last command was \"%s\")", - host_and_ident(FALSE), smtp_cmd_buffer); + host_and_ident(FALSE), string_printing(smtp_cmd_buffer)); done = 1; } @@ -2951,7 +3455,7 @@ while (done <= 0) if (sender_host_name == NULL && (deliver_domain = sender_helo_name, /* set $domain */ - match_isinlist(sender_helo_name, &helo_lookup_domains, 0, + match_isinlist(sender_helo_name, CUSS &helo_lookup_domains, 0, &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL)) == OK) (void)host_name_lookup(); @@ -3020,9 +3524,13 @@ while (done <= 0) auth_advertised = FALSE; pipelining_advertised = FALSE; - #ifdef SUPPORT_TLS +#ifdef SUPPORT_TLS tls_advertised = FALSE; - #endif +#endif + dsn_advertised = FALSE; +#ifdef EXPERIMENTAL_INTERNATIONAL + smtputf8_advertised = FALSE; +#endif smtp_code = US"250 "; /* Default response code plus space*/ if (user_msg == NULL) @@ -3106,6 +3614,14 @@ while (done <= 0) s = string_cat(s, &size, &ptr, US"-8BITMIME\r\n", 11); } + /* Advertise DSN support if configured to do so. */ + if (verify_check_host(&dsn_advertise_hosts) != FAIL) + { + s = string_cat(s, &size, &ptr, smtp_code, 3); + s = string_cat(s, &size, &ptr, US"-DSN\r\n", 6); + dsn_advertised = TRUE; + } + /* Advertise ETRN if there's an ACL checking whether a host is permitted to issue it; a check is made when any host actually tries. */ @@ -3185,7 +3701,7 @@ while (done <= 0) tls_advertise_hosts. We must *not* advertise if we are already in a secure connection. */ - #ifdef SUPPORT_TLS +#ifdef SUPPORT_TLS if (tls_in.active < 0 && verify_check_host(&tls_advertise_hosts) != FAIL) { @@ -3193,15 +3709,26 @@ while (done <= 0) s = string_cat(s, &size, &ptr, US"-STARTTLS\r\n", 11); tls_advertised = TRUE; } - #endif +#endif - #ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR /* Per Recipient Data Response, draft by Eric A. Hall extending RFC */ - if (prdr_enable) { + if (prdr_enable) + { s = string_cat(s, &size, &ptr, smtp_code, 3); s = string_cat(s, &size, &ptr, US"-PRDR\r\n", 7); - } - #endif + } +#endif + +#ifdef EXPERIMENTAL_INTERNATIONAL + if ( accept_8bitmime + && verify_check_host(&smtputf8_advertise_hosts) != FAIL) + { + s = string_cat(s, &size, &ptr, smtp_code, 3); + s = string_cat(s, &size, &ptr, US"-SMTPUTF8\r\n", 11); + smtputf8_advertised = TRUE; + } +#endif /* Finish off the multiline reply with one that is always available. */ @@ -3214,9 +3741,9 @@ while (done <= 0) s[ptr] = 0; - #ifdef SUPPORT_TLS +#ifdef SUPPORT_TLS if (tls_in.active >= 0) (void)tls_write(TRUE, s, ptr); else - #endif +#endif { int i = fwrite(s, 1, ptr, smtp_out); i = i; /* compiler quietening */ @@ -3231,16 +3758,13 @@ while (done <= 0) helo_seen = TRUE; /* Reset the protocol and the state, abandoning any previous message. */ - - received_protocol = (esmtp? - protocols[pextend + - ((sender_host_authenticated != NULL)? pauthed : 0) + - ((tls_in.active >= 0)? pcrpted : 0)] - : - protocols[pnormal + ((tls_in.active >= 0)? pcrpted : 0)]) - + - ((sender_host_address != NULL)? pnlocal : 0); - + received_protocol = + (sender_host_address ? protocols : protocols_local) + [ (esmtp + ? pextend + (sender_host_authenticated ? pauthed : 0) + : pnormal) + + (tls_in.active >= 0 ? pcrpted : 0) + ]; smtp_reset(reset_point); toomany = FALSE; break; /* HELO/EHLO */ @@ -3313,10 +3837,8 @@ while (done <= 0) (char *)mail_args < (char *)env_mail_type_list + sizeof(env_mail_type_list); mail_args++ ) - { if (strcmpic(name, mail_args->name) == 0) break; - } if (mail_args->need_value && strcmpic(value, US"") == 0) break; @@ -3344,22 +3866,67 @@ while (done <= 0) and "7BIT" as body types, but take no action. */ case ENV_MAIL_OPT_BODY: if (accept_8bitmime) { - if (strcmpic(value, US"8BITMIME") == 0) { + if (strcmpic(value, US"8BITMIME") == 0) body_8bitmime = 8; - } else if (strcmpic(value, US"7BIT") == 0) { + else if (strcmpic(value, US"7BIT") == 0) body_8bitmime = 7; - } else { + else + { body_8bitmime = 0; done = synprot_error(L_smtp_syntax_error, 501, NULL, US"invalid data for BODY"); goto COMMAND_LOOP; - } + } DEBUG(D_receive) debug_printf("8BITMIME: %d\n", body_8bitmime); break; } arg_error = TRUE; break; + /* Handle the two DSN options, but only if configured to do so (which + will have caused "DSN" to be given in the EHLO response). The code itself + is included only if configured in at build time. */ + + case ENV_MAIL_OPT_RET: + if (dsn_advertised) + { + /* Check if RET has already been set */ + if (dsn_ret > 0) + { + synprot_error(L_smtp_syntax_error, 501, NULL, + US"RET can be specified once only"); + goto COMMAND_LOOP; + } + dsn_ret = strcmpic(value, US"HDRS") == 0 + ? dsn_ret_hdrs + : strcmpic(value, US"FULL") == 0 + ? dsn_ret_full + : 0; + DEBUG(D_receive) debug_printf("DSN_RET: %d\n", dsn_ret); + /* Check for invalid invalid value, and exit with error */ + if (dsn_ret == 0) + { + synprot_error(L_smtp_syntax_error, 501, NULL, + US"Value for RET is invalid"); + goto COMMAND_LOOP; + } + } + break; + case ENV_MAIL_OPT_ENVID: + if (dsn_advertised) + { + /* Check if the dsn envid has been already set */ + if (dsn_envid != NULL) + { + synprot_error(L_smtp_syntax_error, 501, NULL, + US"ENVID can be specified once only"); + goto COMMAND_LOOP; + } + dsn_envid = string_copy(value); + DEBUG(D_receive) debug_printf("DSN_ENVID: %s\n", dsn_envid); + } + break; + /* Handle the AUTH extension. If the value given is not "<>" and either the ACL says "yes" or there is no ACL but the sending host is authenticated, we set it up as the authenticated sender. However, if the @@ -3397,45 +3964,55 @@ while (done <= 0) switch (rc) { case OK: - if (authenticated_by == NULL || - authenticated_by->mail_auth_condition == NULL || - expand_check_condition(authenticated_by->mail_auth_condition, - authenticated_by->name, US"authenticator")) - break; /* Accept the AUTH */ - - ignore_msg = US"server_mail_auth_condition failed"; - if (authenticated_id != NULL) - ignore_msg = string_sprintf("%s: authenticated ID=\"%s\"", - ignore_msg, authenticated_id); + if (authenticated_by == NULL || + authenticated_by->mail_auth_condition == NULL || + expand_check_condition(authenticated_by->mail_auth_condition, + authenticated_by->name, US"authenticator")) + break; /* Accept the AUTH */ + + ignore_msg = US"server_mail_auth_condition failed"; + if (authenticated_id != NULL) + ignore_msg = string_sprintf("%s: authenticated ID=\"%s\"", + ignore_msg, authenticated_id); /* Fall through */ case FAIL: - authenticated_sender = NULL; - log_write(0, LOG_MAIN, "ignoring AUTH=%s from %s (%s)", - value, host_and_ident(TRUE), ignore_msg); - break; + authenticated_sender = NULL; + log_write(0, LOG_MAIN, "ignoring AUTH=%s from %s (%s)", + value, host_and_ident(TRUE), ignore_msg); + break; /* Should only get DEFER or ERROR here. Put back terminator overrides for error message */ default: - value[-1] = '='; - name[-1] = ' '; - (void)smtp_handle_acl_fail(ACL_WHERE_MAILAUTH, rc, user_msg, - log_msg); - goto COMMAND_LOOP; + value[-1] = '='; + name[-1] = ' '; + (void)smtp_handle_acl_fail(ACL_WHERE_MAILAUTH, rc, user_msg, + log_msg); + goto COMMAND_LOOP; } } break; -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR case ENV_MAIL_OPT_PRDR: - if ( prdr_enable ) + if (prdr_enable) prdr_requested = TRUE; break; #endif +#ifdef EXPERIMENTAL_INTERNATIONAL + case ENV_MAIL_OPT_UTF8: + if (smtputf8_advertised) + { + DEBUG(D_receive) debug_printf("smtputf8 requested\n"); + message_smtputf8 = allow_utf8_domains = TRUE; + received_protocol = string_sprintf("utf8%s", received_protocol); + } + break; +#endif /* Unknown option. Stick back the terminator characters and break the loop. Do the name-terminator second as extract_option sets value==name when it found no equal-sign. @@ -3468,9 +4045,10 @@ while (done <= 0) /* Now extract the address, first applying any SMTP-time rewriting. The TRUE flag allows "<>" as a sender address. */ - raw_sender = ((rewrite_existflags & rewrite_smtp) != 0)? - rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"", - global_rewrite_rules) : smtp_cmd_data; + raw_sender = rewrite_existflags & rewrite_smtp + ? rewrite_one(smtp_cmd_data, rewrite_smtp, NULL, FALSE, US"", + global_rewrite_rules) + : smtp_cmd_data; /* rfc821_domains = TRUE; << no longer needed */ raw_sender = @@ -3556,29 +4134,32 @@ while (done <= 0) when pipelining is not advertised, do another sync check in case the ACL delayed and the client started sending in the meantime. */ - if (acl_smtp_mail == NULL) rc = OK; else + if (acl_smtp_mail) { rc = acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg); if (rc == OK && !pipelining_advertised && !check_sync()) goto SYNC_FAILURE; } + else + rc = OK; if (rc == OK || rc == DISCARD) { - if (user_msg == NULL) + if (!user_msg) smtp_printf("%s%s%s", US"250 OK", - #ifdef EXPERIMENTAL_PRDR - prdr_requested == TRUE ? US", PRDR Requested" : - #endif + #ifndef DISABLE_PRDR + prdr_requested ? US", PRDR Requested" : US"", + #else US"", + #endif US"\r\n"); else { - #ifdef EXPERIMENTAL_PRDR - if ( prdr_requested == TRUE ) + #ifndef DISABLE_PRDR + if (prdr_requested) user_msg = string_sprintf("%s%s", user_msg, US", PRDR Requested"); #endif - smtp_user_msg(US"250",user_msg); + smtp_user_msg(US"250", user_msg); } smtp_delay_rcpt = smtp_rlr_base; recipients_discarded = (rc == DISCARD); @@ -3634,6 +4215,89 @@ while (done <= 0) break; } + /* Set the DSN flags orcpt and dsn_flags from the session*/ + orcpt = NULL; + flags = 0; + + if (esmtp) for(;;) + { + uschar *name, *value; + + if (!extract_option(&name, &value)) + break; + + if (dsn_advertised && strcmpic(name, US"ORCPT") == 0) + { + /* Check whether orcpt has been already set */ + if (orcpt) + { + synprot_error(L_smtp_syntax_error, 501, NULL, + US"ORCPT can be specified once only"); + goto COMMAND_LOOP; + } + orcpt = string_copy(value); + DEBUG(D_receive) debug_printf("DSN orcpt: %s\n", orcpt); + } + + else if (dsn_advertised && strcmpic(name, US"NOTIFY") == 0) + { + /* Check if the notify flags have been already set */ + if (flags > 0) + { + synprot_error(L_smtp_syntax_error, 501, NULL, + US"NOTIFY can be specified once only"); + goto COMMAND_LOOP; + } + if (strcmpic(value, US"NEVER") == 0) + flags |= rf_notify_never; + else + { + uschar *p = value; + while (*p != 0) + { + uschar *pp = p; + while (*pp != 0 && *pp != ',') pp++; + if (*pp == ',') *pp++ = 0; + if (strcmpic(p, US"SUCCESS") == 0) + { + DEBUG(D_receive) debug_printf("DSN: Setting notify success\n"); + flags |= rf_notify_success; + } + else if (strcmpic(p, US"FAILURE") == 0) + { + DEBUG(D_receive) debug_printf("DSN: Setting notify failure\n"); + flags |= rf_notify_failure; + } + else if (strcmpic(p, US"DELAY") == 0) + { + DEBUG(D_receive) debug_printf("DSN: Setting notify delay\n"); + flags |= rf_notify_delay; + } + else + { + /* Catch any strange values */ + synprot_error(L_smtp_syntax_error, 501, NULL, + US"Invalid value for NOTIFY parameter"); + goto COMMAND_LOOP; + } + p = pp; + } + DEBUG(D_receive) debug_printf("DSN Flags: %x\n", flags); + } + } + + /* Unknown option. Stick back the terminator characters and break + the loop. An error for a malformed address will occur. */ + + else + { + DEBUG(D_receive) debug_printf("Invalid RCPT option: %s : %s\n", name, value); + name[-1] = ' '; + value[-1] = '='; + break; + } + } + /* Apply SMTP rewriting then extract the working address. Don't allow "<>" as a recipient address */ @@ -3747,6 +4411,14 @@ while (done <= 0) if (user_msg == NULL) smtp_printf("250 Accepted\r\n"); else smtp_user_msg(US"250", user_msg); receive_add_recipient(recipient, -1); + + /* Set the dsn flags in the recipients_list */ + recipients_list[recipients_count-1].orcpt = orcpt; + recipients_list[recipients_count-1].dsn_flags = flags; + + DEBUG(D_receive) debug_printf("DSN: orcpt: %s flags: %d\n", + recipients_list[recipients_count-1].orcpt, + recipients_list[recipients_count-1].dsn_flags); } /* The recipient was discarded */ @@ -3757,13 +4429,11 @@ while (done <= 0) else smtp_user_msg(US"250", user_msg); rcpt_fail_count++; discarded = TRUE; - log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> rejected RCPT %s: " + log_write(0, LOG_MAIN|LOG_REJECT, "%s F=<%s> RCPT %s: " "discarded by %s ACL%s%s", host_and_ident(TRUE), - (sender_address_unrewritten != NULL)? - sender_address_unrewritten : sender_address, + sender_address_unrewritten? sender_address_unrewritten : sender_address, smtp_cmd_argument, recipients_discarded? "MAIL" : "RCPT", - (log_msg == NULL)? US"" : US": ", - (log_msg == NULL)? US"" : log_msg); + log_msg ? US": " : US"", log_msg ? log_msg : US""); } /* Either the ACL failed the address, or it was deferred. */ @@ -3831,7 +4501,7 @@ while (done <= 0) ACL may have delayed. To handle cutthrough delivery enforce a dummy call to get the DATA command sent. */ - if (acl_smtp_predata == NULL && cutthrough_fd < 0) rc = OK; else + if (acl_smtp_predata == NULL && cutthrough.fd < 0) rc = OK; else { uschar * acl= acl_smtp_predata ? acl_smtp_predata : US"accept"; enable_dollar_recipients = TRUE; @@ -4007,13 +4677,13 @@ while (done <= 0) set_process_info("handling incoming TLS connection from %s", host_and_ident(FALSE)); } - received_protocol = (esmtp? - protocols[pextend + pcrpted + - ((sender_host_authenticated != NULL)? pauthed : 0)] - : - protocols[pnormal + pcrpted]) - + - ((sender_host_address != NULL)? pnlocal : 0); + received_protocol = + (sender_host_address ? protocols : protocols_local) + [ (esmtp + ? pextend + (sender_host_authenticated ? pauthed : 0) + : pnormal) + + (tls_in.active >= 0 ? pcrpted : 0) + ]; sender_host_authenticated = NULL; authenticated_id = NULL; @@ -4233,7 +4903,7 @@ while (done <= 0) break; } etrn_command = US"exim -R"; - argv = child_exec_exim(CEE_RETURN_ARGV, TRUE, NULL, TRUE, 2, US"-R", + argv = CUSS child_exec_exim(CEE_RETURN_ARGV, TRUE, NULL, TRUE, 2, US"-R", smtp_cmd_data); } @@ -4379,6 +5049,11 @@ while (done <= 0) done = 1; /* Pretend eof - drops connection */ break; + #ifdef EXPERIMENTAL_PROXY + case PROXY_FAIL_IGNORE_CMD: + smtp_printf("503 Command refused, required Proxy negotiation failed\r\n"); + break; + #endif default: if (unknown_command_count++ >= smtp_max_unknown_commands) @@ -4393,7 +5068,7 @@ while (done <= 0) done = 2; log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many " "unrecognized commands (last was \"%s\")", host_and_ident(FALSE), - smtp_cmd_buffer); + string_printing(smtp_cmd_buffer)); } else done = synprot_error(L_smtp_syntax_error, 500, NULL, @@ -4413,4 +5088,6 @@ while (done <= 0) return done - 2; /* Convert yield values */ } +/* vi: aw ai sw=2 +*/ /* End of smtp_in.c */