* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2013 */
+/* Copyright (c) University of Cambridge 1995 - 2014 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for handling an incoming SMTP call. */
#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;
/* Sanity check and validate optional args to MAIL FROM: envelope */
enum {
ENV_MAIL_OPT_SIZE, ENV_MAIL_OPT_BODY, ENV_MAIL_OPT_AUTH,
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
ENV_MAIL_OPT_PRDR,
#endif
+ ENV_MAIL_OPT_RET, ENV_MAIL_OPT_ENVID,
ENV_MAIL_OPT_NULL
};
typedef struct {
{ US"SIZE", ENV_MAIL_OPT_SIZE, TRUE },
{ US"BODY", ENV_MAIL_OPT_BODY, TRUE },
{ US"AUTH", ENV_MAIL_OPT_AUTH, TRUE },
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
{ US"PRDR", ENV_MAIL_OPT_PRDR, FALSE },
#endif
+ { US"RET", ENV_MAIL_OPT_RET, TRUE },
+ { US"ENVID", ENV_MAIL_OPT_ENVID, TRUE },
{ US"NULL", ENV_MAIL_OPT_NULL, FALSE }
};
}
-/*************************************************
-* Flush waiting input string *
-*************************************************/
-static void
-flush_input()
-{
-int rc;
-
-rc = smtp_getc();
-while (rc != '\n') /* End of input string */
- {
- rc = smtp_getc();
- }
-}
-
-
/*************************************************
* Setup host for proxy protocol *
*************************************************/
} v1;
struct {
uschar sig[12];
- uschar ver;
- uschar cmd;
- uschar fam;
- uschar len;
+ uint8_t ver_cmd;
+ uint8_t fam;
+ uint16_t len;
union {
struct { /* TCP/UDP over IPv4, len = 12 */
uint32_t src_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;
-uschar *tmpip;
-const char v2sig[13] = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A\x02";
+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;
-int get_ok = 0;
socklen_t vslen = 0;
struct timeval tvtmp;
do
{
- ret = recv(fd, &hdr, sizeof(hdr), MSG_PEEK);
+ /* 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 >= 16 &&
- memcmp(&hdr.v2, v2sig, 13) == 0)
+ 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\n");
+ DEBUG(D_receive) debug_printf("Truncated or too large PROXYv2 header (%d/%d)\n",
+ ret, size);
goto proxyfail;
}
- switch (hdr.v2.cmd)
+ switch (cmd)
{
case 0x01: /* PROXY command */
switch (hdr.v2.fam)
{
- case 0x11: /* TCPv4 */
- tmpip = string_sprintf("%s", hdr.v2.addr.ip4.src_addr);
- if (!string_is_ip_address(tmpip,NULL))
+ 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;
- sender_host_address = tmpip;
- sender_host_port = hdr.v2.addr.ip4.src_port;
+ }
+ proxy_target_address = string_copy(US tmpip);
+ tmpport = ntohs(hdr.v2.addr.ip4.dst_port);
+ proxy_target_port = tmpport;
goto done;
- case 0x21: /* TCPv6 */
- tmpip = string_sprintf("%s", hdr.v2.addr.ip6.src_addr);
- if (!string_is_ip_address(tmpip,NULL))
+ 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;
- sender_host_address = tmpip;
- sender_host_port = hdr.v2.addr.ip6.src_port;
+ }
+ 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;
/* Keep local connection address for LOCAL */
break;
default:
- DEBUG(D_receive) debug_printf("Unsupported PROXYv2 command\n");
+ DEBUG(D_receive)
+ debug_printf("Unsupported PROXYv2 command: 0x%x\n", cmd);
goto proxyfail;
}
}
debug_printf("Proxy dest arg is not an %s address\n", iptype);
goto proxyfail;
}
- /* Should save dest ip somewhere? */
+ proxy_target_address = p;
p = sp + 1;
if ((sp = Ustrchr(p, ' ')) == NULL)
{
debug_printf("Proxy dest port '%s' not an integer\n", p);
goto proxyfail;
}
- /* Should save dest port somewhere? */
+ 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("Wrong proxy protocol specified\n");
+ 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 or we just don't want to speak SMTP to them */
+ synchronization failure */
return FALSE;
done:
restore_socket_timeout(fd, get_ok, tvtmp, vslen);
-flush_input();
DEBUG(D_receive)
- debug_printf("Valid %s sender from Proxy Protocol header\n",
- iptype);
+ debug_printf("Valid %s sender from Proxy Protocol header\n", iptype);
return proxy_session;
}
#endif
#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)
{
s = string_append(s, &size, &ptr, 3, US" SNI=\"",
string_printing(tls_in.sni), US"\"");
- if (sizep) *sizep = size;
- if (ptrp) *ptrp = ptr;
+ if (s)
+ {
+ s[ptr] = '\0';
+ if (sizep) *sizep = size;
+ if (ptrp) *ptrp = ptr;
+ }
return s;
}
#endif
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);
}
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));
+
+/* Reset the DSN flags */
+dsn_ret = 0;
+dsn_envid = NULL;
+
authenticated_sender = NULL;
#ifdef EXPERIMENTAL_BRIGHTMAIL
bmi_run = 0;
#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;
/* Reset ACL connection variables */
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. */
#endif
(where == ACL_WHERE_PREDATA)? US"DATA" :
(where == ACL_WHERE_DATA)? US"after DATA" :
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
(where == ACL_WHERE_PRDR)? US"after DATA PRDR" :
#endif
(smtp_cmd_data == NULL)?
is closing if required and return 2. */
if (log_reject_target != 0)
- log_write(0, log_reject_target, "%s%s %s%srejected %s%s",
- host_and_ident(TRUE),
+ {
#ifdef SUPPORT_TLS
- s_tlslog(NULL, NULL, NULL),
+ 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;
while (done <= 0)
{
- uschar **argv;
+ const uschar **argv;
uschar *etrn_command;
uschar *etrn_serialize_key;
uschar *errmess;
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;
int ptr, size, rc;
int c, i;
auth_instance *au;
+ uschar *orcpt = NULL;
+ int flags;
switch(smtp_read_command(TRUE))
{
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();
#ifdef SUPPORT_TLS
tls_advertised = FALSE;
#endif
+ dsn_advertised = FALSE;
smtp_code = US"250 "; /* Default response code plus space*/
if (user_msg == NULL)
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. */
}
#endif
- #ifdef EXPERIMENTAL_PRDR
+ #ifndef DISABLE_PRDR
/* Per Recipient Data Response, draft by Eric A. Hall extending RFC */
- if (prdr_enable) {
+ if (prdr_enable)
+ {
s = string_cat(s, &size, &ptr, smtp_code, 3);
s = string_cat(s, &size, &ptr, US"-PRDR\r\n", 7);
- }
+ }
#endif
/* Finish off the multiline reply with one that is always available. */
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
}
break;
-#ifdef EXPERIMENTAL_PRDR
+#ifndef DISABLE_PRDR
case ENV_MAIL_OPT_PRDR:
- if ( prdr_enable )
+ if (prdr_enable)
prdr_requested = TRUE;
break;
#endif
when pipelining is not advertised, do another sync check in case the ACL
delayed and the client started sending in the meantime. */
- if (acl_smtp_mail == NULL) rc = OK; else
+ if (acl_smtp_mail)
{
rc = acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg);
if (rc == OK && !pipelining_advertised && !check_sync())
goto SYNC_FAILURE;
}
+ else
+ rc = OK;
if (rc == OK || rc == DISCARD)
{
- if (user_msg == NULL)
+ if (!user_msg)
smtp_printf("%s%s%s", US"250 OK",
- #ifdef EXPERIMENTAL_PRDR
- prdr_requested == TRUE ? US", PRDR Requested" :
- #endif
+ #ifndef DISABLE_PRDR
+ prdr_requested ? US", PRDR Requested" : US"",
+ #else
US"",
+ #endif
US"\r\n");
else
{
- #ifdef EXPERIMENTAL_PRDR
- if ( prdr_requested == TRUE )
+ #ifndef DISABLE_PRDR
+ if (prdr_requested)
user_msg = string_sprintf("%s%s", user_msg, US", PRDR Requested");
#endif
- smtp_user_msg(US"250",user_msg);
+ smtp_user_msg(US"250", user_msg);
}
smtp_delay_rcpt = smtp_rlr_base;
recipients_discarded = (rc == DISCARD);
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 */
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 */
+ if (orcpt != NULL)
+ recipients_list[recipients_count-1].orcpt = orcpt;
+ else
+ recipients_list[recipients_count-1].orcpt = NULL;
+
+ if (flags != 0)
+ recipients_list[recipients_count-1].dsn_flags = flags;
+ else
+ recipients_list[recipients_count-1].dsn_flags = 0;
+ 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 */
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;
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);
}