X-Git-Url: https://vcs.fsf.org/?p=exim.git;a=blobdiff_plain;f=src%2Fsrc%2Fsmtp_in.c;h=cf0a5d64276e32623dd6f26af3d46da4e511d092;hp=db7f133ca052ee1188d7af0b5c145cddb11624a8;hb=2f460950af2d4379deaa804c43caf24a9fb055a0;hpb=24f66b4d1c46e638edbd1af06d3705cf0767a3c7 diff --git a/src/src/smtp_in.c b/src/src/smtp_in.c index db7f133ca..cf0a5d642 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 - 2015 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions for handling an incoming SMTP call. */ @@ -71,6 +71,7 @@ enum { VRFY_CMD, EXPN_CMD, NOOP_CMD, /* RFC as requiring synchronization */ ETRN_CMD, /* This by analogy with TURN from the RFC */ STARTTLS_CMD, /* Required by the STARTTLS RFC */ + TLS_AUTH_CMD, /* auto-command at start of SSL */ /* This is a dummy to identify the non-sync commands when pipelining */ @@ -94,6 +95,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 +122,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 +134,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; @@ -150,15 +159,21 @@ AUTH is already forbidden. After a TLS session is started, AUTH's flag is again forced TRUE, to allow for the re-authentication that can happen at that point. 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. */ +count of non-mail commands and possibly provoke an error. + +tls_auth is a pseudo-command, never expected in input. It is activated +on TLS startup and looks for a tls authenticator. */ 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 }, { "auth", sizeof("auth")-1, AUTH_CMD, TRUE, TRUE }, #ifdef SUPPORT_TLS { "starttls", sizeof("starttls")-1, STARTTLS_CMD, FALSE, FALSE }, + { "tls_auth", 0, TLS_AUTH_CMD, FALSE, TRUE }, #endif /* If you change anything above here, also fix the definitions below. */ @@ -182,6 +197,7 @@ static smtp_cmd_list *cmd_list_end = #define CMD_LIST_EHLO 2 #define CMD_LIST_AUTH 3 #define CMD_LIST_STARTTLS 4 +#define CMD_LIST_TLS_AUTH 5 /* This list of names is used for performing the smtp_no_mail logging action. It must be kept in step with the SCH_xxx enumerations. */ @@ -192,7 +208,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,17 +216,31 @@ 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, - ENV_MAIL_OPT_PRDR, ENV_MAIL_OPT_NULL +#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 */ @@ -222,7 +252,15 @@ 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 }, - { US"NULL", ENV_MAIL_OPT_NULL, FALSE } /* Placeholder for ending */ +#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 @@ -543,6 +581,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 * *************************************************/ @@ -616,10 +1023,20 @@ if required. */ for (p = cmd_list; p < cmd_list_end; p++) { - 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 || - smtp_cmd_buffer[p->len] == ' ')) + #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 ( p->len + && 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 + || smtp_cmd_buffer[p->len] == ' ' + ) ) { if (smtp_inptr < smtp_inend && /* Outstanding input */ p->cmd < sync_cmd_limit && /* Command should sync */ @@ -663,6 +1080,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 */ @@ -819,6 +1242,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 * *************************************************/ @@ -851,18 +1313,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)? @@ -886,7 +1337,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); } @@ -998,19 +1450,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; } @@ -1041,6 +1497,7 @@ 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; @@ -1062,6 +1519,15 @@ 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)); + +#ifndef DISABLE_PRDR +prdr_requested = FALSE; +#endif + +/* Reset the DSN flags */ +dsn_ret = 0; +dsn_envid = NULL; + authenticated_sender = NULL; #ifdef EXPERIMENTAL_BRIGHTMAIL bmi_run = 0; @@ -1078,6 +1544,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; @@ -1186,6 +1655,7 @@ while (done <= 0) it is the canonical extracted address which is all that is kept. */ case MAIL_CMD: + smtp_mailcmd_count++; /* Count for no-mail log */ if (sender_address != NULL) /* The function moan_smtp_batch() does not return. */ moan_smtp_batch(smtp_cmd_buffer, "503 Sender already given"); @@ -1406,8 +1876,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 */ @@ -1435,7 +1912,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. */ @@ -1689,6 +2166,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. */ @@ -1821,6 +2311,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; @@ -1963,7 +2475,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) @@ -2200,6 +2712,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); @@ -2320,9 +2835,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; @@ -2491,29 +3014,25 @@ else /* If a host name is known, check it and all its aliases. */ - if (sender_host_name != NULL) - { - helo_verified = strcmpic(sender_host_name, sender_helo_name) == 0; - - if (helo_verified) + if (sender_host_name) + if ((helo_verified = strcmpic(sender_host_name, sender_helo_name) == 0)) { + sender_helo_dnssec = sender_host_dnssec; HDEBUG(D_receive) debug_printf("matched host name\n"); } else { uschar **aliases = sender_host_aliases; - while (*aliases != NULL) - { - helo_verified = strcmpic(*aliases++, sender_helo_name) == 0; - if (helo_verified) break; - } - HDEBUG(D_receive) - { - if (helo_verified) + while (*aliases) + if ((helo_verified = strcmpic(*aliases++, sender_helo_name) == 0)) + { + sender_helo_dnssec = sender_host_dnssec; + break; + } + + HDEBUG(D_receive) if (helo_verified) debug_printf("matched alias %s\n", *(--aliases)); - } } - } /* Final attempt: try a forward lookup of the helo name */ @@ -2521,29 +3040,34 @@ else { int rc; host_item h; + dnssec_domains d; + host_item *hh; + h.name = sender_helo_name; h.address = NULL; h.mx = MX_NONE; h.next = NULL; + d.request = US"*"; + d.require = US""; + HDEBUG(D_receive) debug_printf("getting IP address for %s\n", sender_helo_name); - rc = host_find_byname(&h, NULL, 0, NULL, TRUE); + rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A, + NULL, NULL, NULL, &d, NULL, NULL); if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL) - { - host_item *hh = &h; - while (hh != NULL) - { + for (hh = &h; hh; hh = hh->next) if (Ustrcmp(hh->address, sender_host_address) == 0) { helo_verified = TRUE; + if (h.dnssec == DS_YES) sender_helo_dnssec = TRUE; HDEBUG(D_receive) - debug_printf("IP address for %s matches calling address\n", - sender_helo_name); + { + debug_printf("IP address for %s matches calling address\n" + "Forward DNS security status: %sverified\n", + sender_helo_name, sender_helo_dnssec ? "" : "un"); + } break; } - hh = hh->next; - } - } } } @@ -2580,6 +3104,112 @@ smtp_respond(code, len, TRUE, user_msg); +static int +smtp_in_auth(auth_instance *au, uschar ** s, uschar ** ss) +{ +const uschar *set_id = NULL; +int rc, i; + +/* Run the checking code, passing the remainder of the command line as +data. Initials the $auth variables as empty. Initialize $0 empty and set +it as the only set numerical variable. The authenticator may set $auth +and also set other numeric variables. The $auth variables are preferred +nowadays; the numerical variables remain for backwards compatibility. + +Afterwards, have a go at expanding the set_id string, even if +authentication failed - for bad passwords it can be useful to log the +userid. On success, require set_id to expand and exist, and put it in +authenticated_id. Save this in permanent store, as the working store gets +reset at HELO, RSET, etc. */ + +for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL; +expand_nmax = 0; +expand_nlength[0] = 0; /* $0 contains nothing */ + +rc = (au->info->servercode)(au, smtp_cmd_data); +if (au->set_id) set_id = expand_string(au->set_id); +expand_nmax = -1; /* Reset numeric variables */ +for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL; /* Reset $auth */ + +/* The value of authenticated_id is stored in the spool file and printed in +log lines. It must not contain binary zeros or newline characters. In +normal use, it never will, but when playing around or testing, this error +can (did) happen. To guard against this, ensure that the id contains only +printing characters. */ + +if (set_id) set_id = string_printing(set_id); + +/* For the non-OK cases, set up additional logging data if set_id +is not empty. */ + +if (rc != OK) + set_id = set_id && *set_id + ? string_sprintf(" (set_id=%s)", set_id) : US""; + +/* Switch on the result */ + +switch(rc) + { + case OK: + if (!au->set_id || set_id) /* Complete success */ + { + if (set_id) 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 = + (sender_host_address ? protocols : protocols_local) + [pextend + pauthed + (tls_in.active >= 0 ? pcrpted:0)]; + *s = *ss = US"235 Authentication succeeded"; + authenticated_by = au; + break; + } + + /* Authentication succeeded, but we failed to expand the set_id string. + Treat this as a temporary error. */ + + auth_defer_msg = expand_string_message; + /* Fall through */ + + case DEFER: + if (set_id) 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", + set_id, auth_defer_msg); + break; + + case BAD64: + *s = *ss = US"501 Invalid base64 data"; + break; + + case CANCELLED: + *s = *ss = US"501 Authentication cancelled"; + break; + + case UNEXPECTED: + *s = *ss = US"553 Initial data not expected"; + break; + + case FAIL: + if (set_id) 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) 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, rc); + break; + } + +return rc; +} + + /************************************************* * Initialize for SMTP incoming message * @@ -2632,6 +3262,7 @@ cmd_list[CMD_LIST_HELO].is_mail_cmd = TRUE; cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE; #ifdef SUPPORT_TLS cmd_list[CMD_LIST_STARTTLS].is_mail_cmd = TRUE; +cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE; #endif /* Set the local signal handler for SIGTERM - it tries to end off tidily */ @@ -2647,7 +3278,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; @@ -2655,7 +3286,6 @@ while (done <= 0) uschar *user_msg = NULL; uschar *recipient = NULL; uschar *hello = NULL; - uschar *set_id = NULL; uschar *s, *ss; BOOL was_rej_mail = FALSE; BOOL was_rcpt = FALSE; @@ -2665,6 +3295,43 @@ while (done <= 0) int ptr, size, rc; int c, i; auth_instance *au; + uschar *orcpt = NULL; + int flags; + +#if defined(SUPPORT_TLS) && defined(AUTH_TLS) + /* Check once per STARTTLS or SSL-on-connect for a TLS AUTH */ + if ( tls_in.active >= 0 + && tls_in.peercert + && tls_in.certificate_verified + && cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd + ) + { + cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = FALSE; + if (acl_smtp_auth) + { + rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth, &user_msg, &log_msg); + if (rc != OK) + { + done = smtp_handle_acl_fail(ACL_WHERE_AUTH, rc, user_msg, log_msg); + continue; + } + } + + for (au = auths; au; au = au->next) + if (strcmpic(US"tls", au->driver_name) == 0) + { + smtp_cmd_data = NULL; + + if ((c = smtp_in_auth(au, &s, &ss)) != OK) + log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s", + au->name, host_and_ident(FALSE), ss); + else + DEBUG(D_auth) debug_printf("tls auth succeeded\n"); + + break; + } + } +#endif switch(smtp_read_command(TRUE)) { @@ -2693,13 +3360,13 @@ while (done <= 0) US"AUTH command used when not advertised"); break; } - if (sender_host_authenticated != NULL) + if (sender_host_authenticated) { done = synprot_error(L_smtp_protocol_error, 503, NULL, US"already authenticated"); break; } - if (sender_address != NULL) + if (sender_address) { done = synprot_error(L_smtp_protocol_error, 503, NULL, US"not permitted in mail transaction"); @@ -2708,7 +3375,7 @@ while (done <= 0) /* Check the ACL */ - if (acl_smtp_auth != NULL) + if (acl_smtp_auth) { rc = acl_check(ACL_WHERE_AUTH, NULL, acl_smtp_auth, &user_msg, &log_msg); if (rc != OK) @@ -2745,117 +3412,23 @@ while (done <= 0) as a server and which has been advertised (unless, sigh, allow_auth_ unadvertised is set). */ - for (au = auths; au != NULL; au = au->next) - { + for (au = auths; au; au = au->next) if (strcmpic(s, au->public_name) == 0 && au->server && - (au->advertised || allow_auth_unadvertised)) break; - } + (au->advertised || allow_auth_unadvertised)) + break; - if (au == NULL) + if (au) { - done = synprot_error(L_smtp_protocol_error, 504, NULL, - string_sprintf("%s authentication mechanism not supported", s)); - break; - } - - /* Run the checking code, passing the remainder of the command line as - data. Initials the $auth variables as empty. Initialize $0 empty and set - it as the only set numerical variable. The authenticator may set $auth - and also set other numeric variables. The $auth variables are preferred - nowadays; the numerical variables remain for backwards compatibility. - - Afterwards, have a go at expanding the set_id string, even if - authentication failed - for bad passwords it can be useful to log the - userid. On success, require set_id to expand and exist, and put it in - authenticated_id. Save this in permanent store, as the working store gets - reset at HELO, RSET, etc. */ - - for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL; - expand_nmax = 0; - expand_nlength[0] = 0; /* $0 contains nothing */ - - c = (au->info->servercode)(au, smtp_cmd_data); - if (au->set_id != NULL) set_id = expand_string(au->set_id); - expand_nmax = -1; /* Reset numeric variables */ - for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL; /* Reset $auth */ - - /* The value of authenticated_id is stored in the spool file and printed in - log lines. It must not contain binary zeros or newline characters. In - normal use, it never will, but when playing around or testing, this error - can (did) happen. To guard against this, ensure that the id contains only - printing characters. */ - - if (set_id != NULL) set_id = string_printing(set_id); + c = smtp_in_auth(au, &s, &ss); - /* For the non-OK cases, set up additional logging data if set_id - is not empty. */ - - if (c != OK) - { - if (set_id != NULL && *set_id != 0) - set_id = string_sprintf(" (set_id=%s)", set_id); - else set_id = US""; - } - - /* Switch on the result */ - - switch(c) - { - case OK: - if (au->set_id == NULL || set_id != NULL) /* Complete success */ - { - if (set_id != NULL) authenticated_id = string_copy_malloc(set_id); - sender_host_authenticated = au->name; - authentication_failed = FALSE; - received_protocol = - protocols[pextend + pauthed + ((tls_in.active >= 0)? pcrpted:0)] + - ((sender_host_address != NULL)? pnlocal : 0); - s = ss = US"235 Authentication succeeded"; - authenticated_by = au; - break; - } - - /* Authentication succeeded, but we failed to expand the set_id string. - Treat this as a temporary error. */ - - auth_defer_msg = expand_string_message; - /* Fall through */ - - case DEFER: - 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", - set_id, auth_defer_msg); - break; - - case BAD64: - s = ss = US"501 Invalid base64 data"; - break; - - case CANCELLED: - s = ss = US"501 Authentication cancelled"; - break; - - case UNEXPECTED: - s = ss = US"553 Initial data not expected"; - break; - - case FAIL: - s = US"535 Incorrect authentication data"; - ss = string_sprintf("535 Incorrect authentication data%s", set_id); - break; - - default: - s = US"435 Internal error"; - ss = string_sprintf("435 Internal error%s: return %d from authentication " - "check", set_id, c); - break; + smtp_printf("%s\r\n", s); + if (c != OK) + log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s", + au->name, host_and_ident(FALSE), ss); } - - smtp_printf("%s\r\n", s); - if (c != OK) - log_write(0, LOG_MAIN|LOG_REJECT, "%s authenticator failed for %s: %s", - au->name, host_and_ident(FALSE), ss); + else + done = synprot_error(L_smtp_protocol_error, 504, NULL, + string_sprintf("%s authentication mechanism not supported", s)); break; /* AUTH_CMD */ @@ -2906,7 +3479,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; } @@ -2933,7 +3506,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(); @@ -2951,7 +3524,7 @@ while (done <= 0) now obsolescent, since the verification can now be requested selectively at ACL time. */ - helo_verified = helo_verify_failed = FALSE; + helo_verified = helo_verify_failed = sender_helo_dnssec = FALSE; if (helo_required || helo_verify) { BOOL tempfail = !smtp_verify_helo(); @@ -3002,9 +3575,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) @@ -3088,6 +3665,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. */ @@ -3118,6 +3703,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 @@ -3128,45 +3714,47 @@ while (done <= 0) letters, so output the names in upper case, though we actually recognize them in either case in the AUTH command. */ - if (auths != NULL) - { - if (verify_check_host(&auth_advertise_hosts) == OK) - { - auth_instance *au; - BOOL first = TRUE; - for (au = auths; au != NULL; au = au->next) - { - if (au->server && (au->advertise_condition == NULL || - expand_check_condition(au->advertise_condition, au->name, - US"authenticator"))) - { - int saveptr; - if (first) - { - s = string_cat(s, &size, &ptr, smtp_code, 3); - s = string_cat(s, &size, &ptr, US"-AUTH", 5); - first = FALSE; - auth_advertised = TRUE; - } - saveptr = ptr; - s = string_cat(s, &size, &ptr, US" ", 1); - s = string_cat(s, &size, &ptr, au->public_name, - Ustrlen(au->public_name)); - while (++saveptr < ptr) s[saveptr] = toupper(s[saveptr]); - au->advertised = TRUE; - } - else au->advertised = FALSE; - } - if (!first) s = string_cat(s, &size, &ptr, US"\r\n", 2); - } - } + if ( auths +#if defined(SUPPORT_TLS) && defined(AUTH_TLS) + && !sender_host_authenticated +#endif + && verify_check_host(&auth_advertise_hosts) == OK + ) + { + auth_instance *au; + BOOL first = TRUE; + for (au = auths; au; au = au->next) + if (au->server && (au->advertise_condition == NULL || + expand_check_condition(au->advertise_condition, au->name, + US"authenticator"))) + { + int saveptr; + if (first) + { + s = string_cat(s, &size, &ptr, smtp_code, 3); + s = string_cat(s, &size, &ptr, US"-AUTH", 5); + first = FALSE; + auth_advertised = TRUE; + } + saveptr = ptr; + s = string_cat(s, &size, &ptr, US" ", 1); + s = string_cat(s, &size, &ptr, au->public_name, + Ustrlen(au->public_name)); + while (++saveptr < ptr) s[saveptr] = toupper(s[saveptr]); + au->advertised = TRUE; + } + else + au->advertised = FALSE; + + if (!first) s = string_cat(s, &size, &ptr, US"\r\n", 2); + } /* Advertise TLS (Transport Level Security) aka SSL (Secure Socket Layer) if it has been included in the binary, and the host matches 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) { @@ -3174,7 +3762,26 @@ while (done <= 0) 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. */ @@ -3187,11 +3794,13 @@ 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 - (void)fwrite(s, 1, ptr, smtp_out); + { + int i = fwrite(s, 1, ptr, smtp_out); i = i; /* compiler quietening */ + } DEBUG(D_receive) { uschar *cr; @@ -3202,16 +3811,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 */ @@ -3284,23 +3890,16 @@ 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; - /* This doesn't seem right to use - if ((char *)mail_args >= (char *)env_mail_type_list + sizeof(env_mail_type_list)) - goto BAD_MAIL_ARGS; - */ switch(mail_args->value) { /* 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 (strcmpic(name, US"SIZE") == 0 && */ if (((size = Ustrtoul(value, &end, 10)), *end == 0)) { if ((size == ULONG_MAX && errno == ERANGE) || size > INT_MAX) @@ -3319,13 +3918,68 @@ while (done <= 0) 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 && - (strcmpic(value, US"8BITMIME") == 0 || - strcmpic(value, US"7BIT") == 0) ) - break; + 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; + /* 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 @@ -3342,8 +3996,8 @@ while (done <= 0) if (auth_xtextdecode(value, &authenticated_sender) < 0) { /* Put back terminator overrides for error message */ - name[-1] = ' '; value[-1] = '='; + name[-1] = ' '; done = synprot_error(L_smtp_syntax_error, 501, NULL, US"invalid data for AUTH"); goto COMMAND_LOOP; @@ -3363,45 +4017,63 @@ 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: - name[-1] = ' '; - value[-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; - + +#ifndef DISABLE_PRDR + case ENV_MAIL_OPT_PRDR: + 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. An error for a malformed address will occur. */ + 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: - - /* BAD_MAIL_ARGS: */ - name[-1] = ' '; value[-1] = '='; + name[-1] = ' '; + arg_error = TRUE; break; } /* Break out of for loop if switch() had bad argument or @@ -3426,9 +4098,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 = @@ -3514,17 +4187,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; @@ -3579,6 +4268,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 */ @@ -3692,6 +4464,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 */ @@ -3702,13 +4482,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. */ @@ -3776,7 +4554,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; @@ -3788,9 +4566,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 */ } @@ -3942,6 +4722,7 @@ while (done <= 0) helo_seen = esmtp = auth_advertised = pipelining_advertised = FALSE; cmd_list[CMD_LIST_EHLO].is_mail_cmd = TRUE; cmd_list[CMD_LIST_AUTH].is_mail_cmd = TRUE; + cmd_list[CMD_LIST_TLS_AUTH].is_mail_cmd = TRUE; if (sender_helo_name != NULL) { store_free(sender_helo_name); @@ -3950,13 +4731,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; @@ -4176,7 +4957,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); } @@ -4317,11 +5098,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) @@ -4336,7 +5122,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, @@ -4356,4 +5142,6 @@ while (done <= 0) return done - 2; /* Convert yield values */ } +/* vi: aw ai sw=2 +*/ /* End of smtp_in.c */