* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2014 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for handling an incoming SMTP call. */
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,
#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,
- ENV_MAIL_OPT_PRDR, ENV_MAIL_OPT_NULL
+#ifndef DISABLE_PRDR
+ ENV_MAIL_OPT_PRDR,
+#endif
+ ENV_MAIL_OPT_RET, ENV_MAIL_OPT_ENVID,
+ ENV_MAIL_OPT_NULL
};
typedef struct {
uschar * name; /* option requested during MAIL cmd */
{ 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 },
+ { US"NULL", ENV_MAIL_OPT_NULL, FALSE }
};
/* When reading SMTP from a remote host, we have to use our own versions of the
+#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 *
*************************************************/
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 ||
}
}
+#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 */
+#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 *
*************************************************/
}
#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)?
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);
}
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;
}
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;
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. */
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;
#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);
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;
-
/*************************************************
* Initialize for SMTP incoming message *
*************************************************/
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 (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_in.active >= 0)? pcrpted:0)] +
((sender_host_address != NULL)? pnlocal : 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",
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);
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. */
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
}
#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
+
/* Finish off the multiline reply with one that is always available. */
s = string_cat(s, &size, &ptr, smtp_code, 3);
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;
}
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)
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
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;
overrides for error message */
default:
- name[-1] = ' ';
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
+
/* 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
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;
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;
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 */
}
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);
}
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)
return done - 2; /* Convert yield values */
}
+/* vi: aw ai sw=2
+*/
/* End of smtp_in.c */