X-Git-Url: https://vcs.fsf.org/?p=exim.git;a=blobdiff_plain;f=src%2Fsrc%2Fsmtp_in.c;h=9fa2ae6edc1473f2201df0942517794a192176c1;hp=23bc5315e8f22978a6809c46bdd113d8af0cc0d8;hb=6023a6ad2ac0294879b14127f62795095da573b5;hpb=2416c261306ff5e1248c5501b736b031a27367f5;ds=sidebyside diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index 23bc5315e..9fa2ae6ed 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 - 2009 */ +/* 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,12 +210,52 @@ 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, +#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 + }; +typedef struct { + uschar * name; /* option requested during MAIL cmd */ + int value; /* enum type */ + BOOL need_value; /* TRUE requires value (name=value pair format) + FALSE is a singleton */ + } env_mail_type_t; +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 }, +#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 } + }; /* When reading SMTP from a remote host, we have to use our own versions of the C input-reading functions, in order to be able to flush the SMTP output only @@ -437,9 +487,10 @@ if (rcpt_in_progress) /* Now write the string */ #ifdef SUPPORT_TLS -if (tls_active >= 0) +if (tls_in.active >= 0) { - if (tls_write(big_buffer, Ustrlen(big_buffer)) < 0) smtp_write_error = -1; + if (tls_write(TRUE, big_buffer, Ustrlen(big_buffer)) < 0) + smtp_write_error = -1; } else #endif @@ -465,7 +516,7 @@ Returns: 0 for no error; -1 after an error int smtp_fflush(void) { -if (tls_active < 0 && fflush(smtp_out) != 0) smtp_write_error = -1; +if (tls_in.active < 0 && fflush(smtp_out) != 0) smtp_write_error = -1; return smtp_write_error; } @@ -488,7 +539,7 @@ command_timeout_handler(int sig) sig = sig; /* Keep picky compilers happy */ log_write(L_lost_incoming_connection, LOG_MAIN, "SMTP command timeout on%s connection from %s", - (tls_active >= 0)? " TLS" : "", + (tls_in.active >= 0)? " TLS" : "", host_and_ident(FALSE)); if (smtp_batched_input) moan_smtp_batch(NULL, "421 SMTP command timeout"); /* Does not return */ @@ -524,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 * *************************************************/ @@ -597,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 || @@ -644,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 */ @@ -689,7 +1123,7 @@ fd_set fds; struct timeval tzero; if (!smtp_enforce_sync || sender_host_address == NULL || - sender_host_notsocket || tls_active >= 0) + sender_host_notsocket || tls_in.active >= 0) return TRUE; fd = fileno(smtp_in); @@ -800,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 * *************************************************/ @@ -832,15 +1305,7 @@ if (sender_host_authenticated != NULL) } #ifdef SUPPORT_TLS -if ((log_extra_selector & LX_tls_cipher) != 0 && tls_cipher != NULL) - s = string_append(s, &size, &ptr, 2, US" X=", tls_cipher); -if ((log_extra_selector & LX_tls_certificate_verified) != 0 && - tls_cipher != NULL) - s = string_append(s, &size, &ptr, 2, US" CV=", - tls_certificate_verified? "yes":"no"); -if ((log_extra_selector & LX_tls_peerdn) != 0 && tls_peerdn != NULL) - s = string_append(s, &size, &ptr, 3, US" DN=\"", - string_printing(tls_peerdn), US"\""); +s = s_tlslog(s, &size, &ptr); #endif sep = (smtp_connection_had[SMTP_HBUFF_SIZE-1] != SCH_NONE)? @@ -864,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); } @@ -976,19 +1442,23 @@ 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--; -if (*v != '=') return FALSE; n = v; -while(isalpha(n[-1])) n--; - -/* RFC says SP, but TAB seen in wild and other major MTAs accept it */ -if (!isspace(n[-1])) return FALSE; - -n[-1] = 0; -*name = n; +if (*v == '=') +{ + while(isalpha(n[-1])) n--; + /* RFC says SP, but TAB seen in wild and other major MTAs accept it */ + if (!isspace(n[-1])) return FALSE; + n[-1] = 0; +} +else +{ + n++; + if (v == smtp_cmd_data) return FALSE; +} *v++ = 0; +*name = n; *value = v; return TRUE; } @@ -1015,9 +1485,11 @@ 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; +acl_removed_headers = NULL; queue_only_policy = FALSE; rcpt_smtp_response = NULL; rcpt_smtp_response_same = TRUE; @@ -1029,7 +1501,7 @@ fake_response = OK; /* Can be set by ACL */ no_mbox_unspool = FALSE; /* Can be set by ACL */ #endif submission_mode = FALSE; /* Can be set by ACL */ -suppress_local_fixups = 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; @@ -1039,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; @@ -1055,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; @@ -1382,9 +1864,16 @@ if (!host_checking && !sender_host_notsocket) sender_host_authenticated = NULL; authenticated_by = NULL; #ifdef SUPPORT_TLS -tls_cipher = tls_peerdn = NULL; +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 */ @@ -1412,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. */ @@ -1666,13 +2155,24 @@ 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. */ #ifdef SUPPORT_TLS - if (tls_on_connect && - tls_server_start(tls_require_ciphers, - gnutls_require_mac, gnutls_require_kx, gnutls_require_proto) != OK) + if (tls_in.on_connect && tls_server_start(tls_require_ciphers) != OK) return FALSE; #endif @@ -1800,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; @@ -1942,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) @@ -2179,6 +2701,9 @@ uschar *what = #endif (where == ACL_WHERE_PREDATA)? US"DATA" : (where == ACL_WHERE_DATA)? US"after DATA" : +#ifndef DISABLE_PRDR + (where == ACL_WHERE_PRDR)? US"after DATA PRDR" : +#endif (smtp_cmd_data == NULL)? string_sprintf("%s in \"connect\" ACL", acl_wherenames[where]) : string_sprintf("%s %s", acl_wherenames[where], smtp_cmd_data); @@ -2299,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; @@ -2559,7 +3092,6 @@ smtp_respond(code, len, TRUE, user_msg); - /************************************************* * Initialize for SMTP incoming message * *************************************************/ @@ -2626,7 +3158,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; @@ -2634,7 +3166,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; @@ -2644,6 +3176,8 @@ while (done <= 0) int ptr, size, rc; int c, i; auth_instance *au; + uschar *orcpt = NULL; + int flags; switch(smtp_read_command(TRUE)) { @@ -2786,9 +3320,11 @@ while (done <= 0) if (set_id != NULL) authenticated_id = string_copy_malloc(set_id); sender_host_authenticated = au->name; authentication_failed = FALSE; + authenticated_fail_id = NULL; /* Impossible to already be set? */ + received_protocol = - protocols[pextend + pauthed + ((tls_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; @@ -2801,6 +3337,7 @@ while (done <= 0) /* Fall through */ case DEFER: + if (set_id != NULL) authenticated_fail_id = string_copy_malloc(set_id); s = string_sprintf("435 Unable to authenticate at present%s", auth_defer_user_msg); ss = string_sprintf("435 Unable to authenticate at present%s: %s", @@ -2820,11 +3357,13 @@ while (done <= 0) break; case FAIL: + if (set_id != NULL) authenticated_fail_id = string_copy_malloc(set_id); s = US"535 Incorrect authentication data"; ss = string_sprintf("535 Incorrect authentication data%s", set_id); break; default: + if (set_id != NULL) authenticated_fail_id = string_copy_malloc(set_id); s = US"435 Internal error"; ss = string_sprintf("435 Internal error%s: return %d from authentication " "check", set_id, c); @@ -2885,7 +3424,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; } @@ -2912,7 +3451,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(); @@ -2921,7 +3460,7 @@ while (done <= 0) host_build_sender_fullhost(); /* Rebuild */ set_process_info("handling%s incoming connection from %s", - (tls_active >= 0)? " TLS" : "", host_and_ident(FALSE)); + (tls_in.active >= 0)? " TLS" : "", host_and_ident(FALSE)); /* Verify if configured. This doesn't give much security, but it does make some people happy to be able to do it. If helo_required is set, @@ -2981,9 +3520,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) @@ -3067,6 +3610,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. */ @@ -3097,6 +3648,7 @@ while (done <= 0) pipelining_advertised = TRUE; } + /* If any server authentication mechanisms are configured, advertise them if the current host is in auth_advertise_hosts. The problem with advertising always is that some clients then require users to @@ -3145,15 +3697,34 @@ while (done <= 0) tls_advertise_hosts. We must *not* advertise if we are already in a secure connection. */ - #ifdef SUPPORT_TLS - if (tls_active < 0 && +#ifdef SUPPORT_TLS + if (tls_in.active < 0 && verify_check_host(&tls_advertise_hosts) != FAIL) { s = string_cat(s, &size, &ptr, smtp_code, 3); s = string_cat(s, &size, &ptr, US"-STARTTLS\r\n", 11); tls_advertised = TRUE; } - #endif +#endif + +#ifndef DISABLE_PRDR + /* Per Recipient Data Response, draft by Eric A. Hall extending RFC */ + if (prdr_enable) + { + s = string_cat(s, &size, &ptr, smtp_code, 3); + s = string_cat(s, &size, &ptr, US"-PRDR\r\n", 7); + } +#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. */ @@ -3166,11 +3737,13 @@ while (done <= 0) s[ptr] = 0; - #ifdef SUPPORT_TLS - if (tls_active >= 0) (void)tls_write(s, ptr); else - #endif +#ifdef SUPPORT_TLS + if (tls_in.active >= 0) (void)tls_write(TRUE, s, ptr); else +#endif - (void)fwrite(s, 1, ptr, smtp_out); + { + int i = fwrite(s, 1, ptr, smtp_out); i = i; /* compiler quietening */ + } DEBUG(D_receive) { uschar *cr; @@ -3181,16 +3754,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_active >= 0)? pcrpted : 0)] - : - protocols[pnormal + ((tls_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 */ @@ -3206,6 +3776,7 @@ while (done <= 0) HAD(SCH_MAIL); smtp_mailcmd_count++; /* Count for limit and ratelimit */ was_rej_mail = TRUE; /* Reset if accepted */ + env_mail_type_t * mail_args; /* Sanity check & validate args */ if (helo_required && !helo_seen) { @@ -3254,113 +3825,203 @@ while (done <= 0) { uschar *name, *value, *end; unsigned long int size; + BOOL arg_error = FALSE; if (!extract_option(&name, &value)) break; - /* Handle SIZE= by reading the value. We don't do the check till later, - in order to be able to log the sender address on failure. */ - - if (strcmpic(name, US"SIZE") == 0 && - ((size = Ustrtoul(value, &end, 10)), *end == 0)) - { - if ((size == ULONG_MAX && errno == ERANGE) || size > INT_MAX) - size = INT_MAX; - message_size = (int)size; - } + for (mail_args = env_mail_type_list; + (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; - /* If this session was initiated with EHLO and accept_8bitmime is set, - Exim will have indicated that it supports the BODY=8BITMIME option. In - fact, it does not support this according to the RFCs, in that it does not - take any special action for forwarding messages containing 8-bit - characters. That is why accept_8bitmime is not the default setting, but - some sites want the action that is provided. We recognize both "8BITMIME" - and "7BIT" as body types, but take no action. */ - - else if (accept_8bitmime && strcmpic(name, US"BODY") == 0 && - (strcmpic(value, US"8BITMIME") == 0 || - strcmpic(value, US"7BIT") == 0)) {} - - /* 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 - authenticator set a condition to be tested, we ignore AUTH on MAIL unless - the condition is met. The value of AUTH is an xtext, which means that +, - = and cntrl chars are coded in hex; however "<>" is unaffected by this - coding. */ - - else if (strcmpic(name, US"AUTH") == 0) + switch(mail_args->value) { - if (Ustrcmp(value, "<>") != 0) - { - int rc; - uschar *ignore_msg; - - if (auth_xtextdecode(value, &authenticated_sender) < 0) + /* Handle SIZE= by reading the value. We don't do the check till later, + in order to be able to log the sender address on failure. */ + case ENV_MAIL_OPT_SIZE: + if (((size = Ustrtoul(value, &end, 10)), *end == 0)) { - /* Put back terminator overrides for error message */ - name[-1] = ' '; - value[-1] = '='; - done = synprot_error(L_smtp_syntax_error, 501, NULL, - US"invalid data for AUTH"); - goto COMMAND_LOOP; - } - - if (acl_smtp_mailauth == NULL) - { - ignore_msg = US"client not authenticated"; - rc = (sender_host_authenticated != NULL)? OK : FAIL; + if ((size == ULONG_MAX && errno == ERANGE) || size > INT_MAX) + size = INT_MAX; + message_size = (int)size; } else - { - ignore_msg = US"rejected by ACL"; - rc = acl_check(ACL_WHERE_MAILAUTH, NULL, acl_smtp_mailauth, - &user_msg, &log_msg); - } + arg_error = TRUE; + break; - 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); - - /* 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; + /* If this session was initiated with EHLO and accept_8bitmime is set, + Exim will have indicated that it supports the BODY=8BITMIME option. In + fact, it does not support this according to the RFCs, in that it does not + take any special action for forwarding messages containing 8-bit + characters. That is why accept_8bitmime is not the default setting, but + some sites want the action that is provided. We recognize both "8BITMIME" + and "7BIT" as body types, but take no action. */ + case ENV_MAIL_OPT_BODY: + if (accept_8bitmime) { + if (strcmpic(value, US"8BITMIME") == 0) + body_8bitmime = 8; + else if (strcmpic(value, US"7BIT") == 0) + body_8bitmime = 7; + 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; - /* Should only get DEFER or ERROR here. Put back terminator - overrides for error message */ + /* 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; - default: - name[-1] = ' '; - value[-1] = '='; - (void)smtp_handle_acl_fail(ACL_WHERE_MAILAUTH, rc, user_msg, - log_msg); - goto COMMAND_LOOP; + /* 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 + authenticator set a condition to be tested, we ignore AUTH on MAIL unless + the condition is met. The value of AUTH is an xtext, which means that +, + = and cntrl chars are coded in hex; however "<>" is unaffected by this + coding. */ + case ENV_MAIL_OPT_AUTH: + if (Ustrcmp(value, "<>") != 0) + { + int rc; + uschar *ignore_msg; + + if (auth_xtextdecode(value, &authenticated_sender) < 0) + { + /* Put back terminator overrides for error message */ + value[-1] = '='; + name[-1] = ' '; + done = synprot_error(L_smtp_syntax_error, 501, NULL, + US"invalid data for AUTH"); + goto COMMAND_LOOP; + } + if (acl_smtp_mailauth == NULL) + { + ignore_msg = US"client not authenticated"; + rc = (sender_host_authenticated != NULL)? OK : FAIL; + } + else + { + ignore_msg = US"rejected by ACL"; + rc = acl_check(ACL_WHERE_MAILAUTH, NULL, acl_smtp_mailauth, + &user_msg, &log_msg); + } + + 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); + + /* 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; + + /* 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; + } } - } - } + break; - /* Unknown option. Stick back the terminator characters and break - the loop. An error for a malformed address will occur. */ +#ifndef DISABLE_PRDR + case ENV_MAIL_OPT_PRDR: + if (prdr_enable) + prdr_requested = TRUE; + break; +#endif - else - { - name[-1] = ' '; - value[-1] = '='; - break; +#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. + An error for a malformed address will occur. */ + default: + value[-1] = '='; + name[-1] = ' '; + arg_error = TRUE; + break; } + /* Break out of for loop if switch() had bad argument or + when start of the email address is reached */ + if (arg_error) break; } /* If we have passed the threshold for rate limiting, apply the current @@ -3380,9 +4041,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 = @@ -3468,17 +4130,33 @@ 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) smtp_printf("250 OK\r\n"); - else smtp_user_msg(US"250", user_msg); + if (!user_msg) + smtp_printf("%s%s%s", US"250 OK", + #ifndef DISABLE_PRDR + prdr_requested ? US", PRDR Requested" : US"", + #else + US"", + #endif + US"\r\n"); + else + { + #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_delay_rcpt = smtp_rlr_base; recipients_discarded = (rc == DISCARD); was_rej_mail = FALSE; @@ -3533,6 +4211,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 */ @@ -3646,6 +4407,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 */ @@ -3727,12 +4496,14 @@ while (done <= 0) } /* If there is an ACL, re-check the synchronization afterwards, since the - ACL may have delayed. */ + ACL may have delayed. To handle cutthrough delivery enforce a dummy call + to get the DATA command sent. */ - if (acl_smtp_predata == NULL) 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; - rc = acl_check(ACL_WHERE_PREDATA, NULL, acl_smtp_predata, &user_msg, + rc = acl_check(ACL_WHERE_PREDATA, NULL, acl, &user_msg, &log_msg); enable_dollar_recipients = FALSE; if (rc == OK && !check_sync()) goto SYNC_FAILURE; @@ -3740,9 +4511,11 @@ while (done <= 0) if (rc == OK) { + uschar * code; + code = US"354"; if (user_msg == NULL) - smtp_printf("354 Enter message, ending with \".\" on a line by itself\r\n"); - else smtp_user_msg(US"354", user_msg); + smtp_printf("%s Enter message, ending with \".\" on a line by itself\r\n", code); + else smtp_user_msg(code, user_msg); done = 3; message_ended = END_NOTENDED; /* Indicate in middle of data */ } @@ -3866,7 +4639,7 @@ while (done <= 0) { DEBUG(D_any) debug_printf("Non-empty input buffer after STARTTLS; naive attack?"); - if (tls_active < 0) + if (tls_in.active < 0) smtp_inend = smtp_inptr = smtp_inbuffer; /* and if TLS is already active, tls_server_start() should fail */ } @@ -3888,8 +4661,7 @@ 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, gnutls_require_mac, - gnutls_require_kx, gnutls_require_proto)) == OK) + if ((rc = tls_server_start(tls_require_ciphers)) == OK) { if (!tls_remember_esmtp) helo_seen = esmtp = auth_advertised = pipelining_advertised = FALSE; @@ -3903,13 +4675,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; @@ -3928,7 +4700,7 @@ while (done <= 0) } /* Hard failure. Reject everything except QUIT or closed connection. One - cause for failure is a nested STARTTLS, in which case tls_active remains + cause for failure is a nested STARTTLS, in which case tls_in.active remains set, but we must still reject all incoming commands. */ DEBUG(D_tls) debug_printf("TLS failed to start\n"); @@ -3944,7 +4716,7 @@ while (done <= 0) break; /* It is perhaps arguable as to which exit ACL should be called here, - but as it is probably a situtation that almost never arises, it + but as it is probably a situation that almost never arises, it probably doesn't matter. We choose to call the real QUIT ACL, which in some sense is perhaps "right". */ @@ -3972,7 +4744,7 @@ while (done <= 0) break; } } - tls_close(TRUE); + tls_close(TRUE, TRUE); break; #endif @@ -3997,7 +4769,7 @@ while (done <= 0) smtp_respond(US"221", 3, TRUE, user_msg); #ifdef SUPPORT_TLS - tls_close(TRUE); + tls_close(TRUE, TRUE); #endif done = 2; @@ -4035,7 +4807,7 @@ while (done <= 0) buffer[0] = 0; Ustrcat(buffer, " AUTH"); #ifdef SUPPORT_TLS - if (tls_active < 0 && + if (tls_in.active < 0 && verify_check_host(&tls_advertise_hosts) != FAIL) Ustrcat(buffer, " STARTTLS"); #endif @@ -4129,7 +4901,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); } @@ -4270,11 +5042,16 @@ while (done <= 0) incomplete_transaction_log(US"too many non-mail commands"); log_write(0, LOG_MAIN|LOG_REJECT, "SMTP call from %s dropped: too many " "nonmail commands (last was \"%.*s\")", host_and_ident(FALSE), - s - smtp_cmd_buffer, smtp_cmd_buffer); + (int)(s - smtp_cmd_buffer), smtp_cmd_buffer); smtp_notquit_exit(US"bad-commands", US"554", US"Too many nonmail commands"); 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) @@ -4289,7 +5066,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, @@ -4309,4 +5086,6 @@ while (done <= 0) return done - 2; /* Convert yield values */ } +/* vi: aw ai sw=2 +*/ /* End of smtp_in.c */