* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2013 */
/* See the file NOTICE for conditions of use and distribution. */
/* Code for handling Access Control Lists (ACLs) */
/* ACL verbs */
-static uschar *verbs[] =
- { US"accept", US"defer", US"deny", US"discard", US"drop", US"require",
+static uschar *verbs[] = {
+ US"accept",
+ US"defer",
+ US"deny",
+ US"discard",
+ US"drop",
+ US"require",
US"warn" };
/* For each verb, the conditions for which "message" or "log_message" are used
#ifndef DISABLE_DKIM
ACLC_DKIM_SIGNER,
ACLC_DKIM_STATUS,
+#endif
+#ifdef EXPERIMENTAL_DMARC
+ ACLC_DMARC_STATUS,
#endif
ACLC_DNSLISTS,
ACLC_DOMAINS,
ACLC_SPF,
ACLC_SPF_GUESS,
#endif
+ ACLC_UDPSEND,
ACLC_VERIFY };
/* ACL conditions/modifiers: "delay", "control", "continue", "endpass",
#ifndef DISABLE_DKIM
US"dkim_signers",
US"dkim_status",
+#endif
+#ifdef EXPERIMENTAL_DMARC
+ US"dmarc_status",
#endif
US"dnslists",
US"domains",
US"spf",
US"spf_guess",
#endif
+ US"udpsend",
US"verify" };
#ifndef DISABLE_DKIM
CONTROL_DKIM_VERIFY,
#endif
+ #ifdef EXPERIMENTAL_DMARC
+ CONTROL_DMARC_VERIFY,
+ CONTROL_DMARC_FORENSIC,
+ #endif
CONTROL_DSCP,
CONTROL_ERROR,
CONTROL_CASEFUL_LOCAL_PART,
#ifndef DISABLE_DKIM
US"dkim_disable_verify",
#endif
+ #ifdef EXPERIMENTAL_DMARC
+ US"dmarc_disable_verify",
+ US"dmarc_enable_forensic",
+ #endif
US"dscp",
US"error",
US"caseful_local_part",
#ifndef DISABLE_DKIM
TRUE, /* dkim_signers */
TRUE, /* dkim_status */
+#endif
+#ifdef EXPERIMENTAL_DMARC
+ TRUE, /* dmarc_status */
#endif
TRUE, /* dnslists */
FALSE, /* domains */
TRUE, /* spf */
TRUE, /* spf_guess */
#endif
+ TRUE, /* udpsend */
TRUE /* verify */
};
#ifndef DISABLE_DKIM
FALSE, /* dkim_signers */
FALSE, /* dkim_status */
+#endif
+#ifdef EXPERIMENTAL_DMARC
+ FALSE, /* dmarc_status */
#endif
FALSE, /* dnslists */
FALSE, /* domains */
FALSE, /* spf */
FALSE, /* spf_guess */
#endif
+ TRUE, /* udpsend */
FALSE /* verify */
};
(unsigned int)
~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)| /* add_header */
(1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+ #ifdef EXPERIMENTAL_PRDR
+ (1<<ACL_WHERE_PRDR)|
+ #endif
(1<<ACL_WHERE_MIME)|(1<<ACL_WHERE_NOTSMTP)|
(1<<ACL_WHERE_DKIM)|
(1<<ACL_WHERE_NOTSMTP_START)),
(1<<ACL_WHERE_AUTH)| /* bmi_optin */
(1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
(1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_MIME)|
+ #ifdef EXPERIMENTAL_PRDR
+ (1<<ACL_WHERE_PRDR)|
+ #endif
(1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
(1<<ACL_WHERE_MAILAUTH)|
(1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
#ifdef EXPERIMENTAL_DCC
(unsigned int)
- ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)), /* dcc */
+ ~((1<<ACL_WHERE_DATA)| /* dcc */
+ #ifdef EXPERIMENTAL_PRDR
+ (1<<ACL_WHERE_PRDR)|
+ #endif /* EXPERIMENTAL_PRDR */
+ (1<<ACL_WHERE_NOTSMTP)),
#endif
#ifdef WITH_CONTENT_SCAN
#ifdef WITH_OLD_DEMIME
(unsigned int)
- ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)), /* demime */
+ ~((1<<ACL_WHERE_DATA)| /* demime */
+ #ifdef EXPERIMENTAL_PRDR
+ (1<<ACL_WHERE_PRDR)|
+ #endif /* EXPERIMENTAL_PRDR */
+ (1<<ACL_WHERE_NOTSMTP)),
#endif
#ifndef DISABLE_DKIM
~(1<<ACL_WHERE_DKIM), /* dkim_status */
#endif
+ #ifdef EXPERIMENTAL_DMARC
+ (unsigned int)
+ ~(1<<ACL_WHERE_DATA), /* dmarc_status */
+ #endif
+
(1<<ACL_WHERE_NOTSMTP)| /* dnslists */
(1<<ACL_WHERE_NOTSMTP_START),
(unsigned int)
- ~(1<<ACL_WHERE_RCPT), /* domains */
+ ~((1<<ACL_WHERE_RCPT) /* domains */
+ #ifdef EXPERIMENTAL_PRDR
+ |(1<<ACL_WHERE_PRDR)
+ #endif
+ ),
(1<<ACL_WHERE_NOTSMTP)| /* encrypted */
(1<<ACL_WHERE_CONNECT)|
(1<<ACL_WHERE_NOTSMTP_START),
(unsigned int)
- ~(1<<ACL_WHERE_RCPT), /* local_parts */
+ ~((1<<ACL_WHERE_RCPT) /* local_parts */
+ #ifdef EXPERIMENTAL_PRDR
+ |(1<<ACL_WHERE_PRDR)
+ #endif
+ ),
0, /* log_message */
#ifdef WITH_CONTENT_SCAN
(unsigned int)
- ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)), /* malware */
+ ~((1<<ACL_WHERE_DATA)| /* malware */
+ #ifdef EXPERIMENTAL_PRDR
+ (1<<ACL_WHERE_PRDR)|
+ #endif /* EXPERIMENTAL_PRDR */
+ (1<<ACL_WHERE_NOTSMTP)),
#endif
0, /* message */
#ifdef WITH_CONTENT_SCAN
(unsigned int)
- ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)| /* regex */
+ ~((1<<ACL_WHERE_DATA)| /* regex */
+ #ifdef EXPERIMENTAL_PRDR
+ (1<<ACL_WHERE_PRDR)|
+ #endif /* EXPERIMENTAL_PRDR */
+ (1<<ACL_WHERE_NOTSMTP)|
(1<<ACL_WHERE_MIME)),
#endif
(unsigned int)
~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)| /* remove_header */
(1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+ #ifdef EXPERIMENTAL_PRDR
+ (1<<ACL_WHERE_PRDR)|
+ #endif
(1<<ACL_WHERE_MIME)|(1<<ACL_WHERE_NOTSMTP)|
(1<<ACL_WHERE_NOTSMTP_START)),
#ifdef WITH_CONTENT_SCAN
(unsigned int)
- ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)), /* spam */
+ ~((1<<ACL_WHERE_DATA)| /* spam */
+ #ifdef EXPERIMENTAL_PRDR
+ (1<<ACL_WHERE_PRDR)|
+ #endif /* EXPERIMENTAL_PRDR */
+ (1<<ACL_WHERE_NOTSMTP)),
#endif
#ifdef EXPERIMENTAL_SPF
(1<<ACL_WHERE_NOTSMTP_START),
#endif
+ 0, /* udpsend */
+
/* Certain types of verify are always allowed, so we let it through
always and check in the verify function itself */
#ifndef DISABLE_DKIM
(1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)| /* dkim_disable_verify */
+ #ifdef EXPERIMENTAL_PRDR
+ (1<<ACL_WHERE_PRDR)|
+ #endif /* EXPERIMENTAL_PRDR */
+ (1<<ACL_WHERE_NOTSMTP_START),
+ #endif
+
+ #ifdef EXPERIMENTAL_DMARC
+ (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)| /* dmarc_disable_verify */
+ (1<<ACL_WHERE_NOTSMTP_START),
+ (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)| /* dmarc_enable_forensic */
(1<<ACL_WHERE_NOTSMTP_START),
#endif
(unsigned int)
~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)| /* freeze */
(1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+ // (1<<ACL_WHERE_PRDR)| /* Not allow one user to freeze for all */
(1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_MIME)),
(unsigned int)
~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)| /* queue_only */
(1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+ // (1<<ACL_WHERE_PRDR)| /* Not allow one user to freeze for all */
(1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_MIME)),
(unsigned int)
(unsigned int)
~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)| /* no_mbox_unspool */
(1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+ // (1<<ACL_WHERE_PRDR)| /* Not allow one user to freeze for all */
(1<<ACL_WHERE_MIME)),
#endif
(unsigned int)
~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)| /* fakedefer */
(1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+ #ifdef EXPERIMENTAL_PRDR
+ (1<<ACL_WHERE_PRDR)|
+ #endif /* EXPERIMENTAL_PRDR */
(1<<ACL_WHERE_MIME)),
(unsigned int)
~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)| /* fakereject */
(1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+ #ifdef EXPERIMENTAL_PRDR
+ (1<<ACL_WHERE_PRDR)|
+ #endif /* EXPERIMENTAL_PRDR */
(1<<ACL_WHERE_MIME)),
(1<<ACL_WHERE_NOTSMTP)| /* no_multiline */
{ US"debug", CONTROL_DEBUG, TRUE },
#ifndef DISABLE_DKIM
{ US"dkim_disable_verify", CONTROL_DKIM_VERIFY, FALSE },
+#endif
+#ifdef EXPERIMENTAL_DMARC
+ { US"dmarc_disable_verify", CONTROL_DMARC_VERIFY, FALSE },
+ { US"dmarc_enable_forensic", CONTROL_DMARC_FORENSIC, FALSE },
#endif
{ US"dscp", CONTROL_DSCP, TRUE },
{ US"caseful_local_part", CONTROL_CASEFUL_LOCAL_PART, FALSE },
/* contains embedded newline; needs doubling */
ret = string_cat(ret, &size, &ptr, s, cp-s+1);
- ret = string_cat(ret, &size, &ptr, "\n", 1);
+ ret = string_cat(ret, &size, &ptr, US"\n", 1);
s = cp+1;
}
/* last bit of header */
ret = string_cat(ret, &size, &ptr, s, cp-s+1); /* newline-sep list */
}
-while(h = h->next);
+while((h = h->next));
ret[ptr-1] = '\0'; /* overwrite last newline */
return ret;
+/*************************************************
+* The udpsend ACL modifier *
+*************************************************/
+
+/* Called by acl_check_condition() below.
+
+Arguments:
+ arg the option string for udpsend=
+ log_msgptr for error messages
+
+Returns: OK - Completed.
+ DEFER - Problem with DNS lookup.
+ ERROR - Syntax error in options.
+*/
+
+static int
+acl_udpsend(uschar *arg, uschar **log_msgptr)
+{
+int sep = 0;
+uschar *hostname;
+uschar *portstr;
+uschar *portend;
+host_item *h;
+int portnum;
+int host_af;
+int len;
+int r, s;
+
+hostname = string_nextinlist(&arg, &sep, NULL, 0);
+portstr = string_nextinlist(&arg, &sep, NULL, 0);
+
+if (hostname == NULL)
+ {
+ *log_msgptr = US"missing destination host in \"udpsend\" modifier";
+ return ERROR;
+ }
+if (portstr == NULL)
+ {
+ *log_msgptr = US"missing destination port in \"udpsend\" modifier";
+ return ERROR;
+ }
+if (arg == NULL)
+ {
+ *log_msgptr = US"missing datagram payload in \"udpsend\" modifier";
+ return ERROR;
+ }
+portnum = Ustrtol(portstr, &portend, 10);
+if (*portend != '\0')
+ {
+ *log_msgptr = US"bad destination port in \"udpsend\" modifier";
+ return ERROR;
+ }
+
+/* Make a single-item host list. */
+h = store_get(sizeof(host_item));
+memset(h, 0, sizeof(host_item));
+h->name = hostname;
+h->port = portnum;
+h->mx = MX_NONE;
+
+if (string_is_ip_address(hostname, NULL))
+ h->address = hostname, r = HOST_FOUND;
+else
+ r = host_find_byname(h, NULL, 0, NULL, FALSE);
+if (r == HOST_FIND_FAILED || r == HOST_FIND_AGAIN)
+ {
+ *log_msgptr = US"DNS lookup failed in \"udpsend\" modifier";
+ return DEFER;
+ }
+
+HDEBUG(D_acl)
+ debug_printf("udpsend [%s]:%d %s\n", h->address, portnum, arg);
+
+host_af = (Ustrchr(h->address, ':') == NULL)? AF_INET:AF_INET6;
+r = s = ip_socket(SOCK_DGRAM, host_af);
+if (r < 0) goto defer;
+r = ip_connect(s, host_af, h->address, portnum, 1);
+if (r < 0) goto defer;
+len = Ustrlen(arg);
+r = send(s, arg, len, 0);
+if (r < 0) goto defer;
+if (r < len)
+ {
+ *log_msgptr =
+ string_sprintf("\"udpsend\" truncated from %d to %d octets", len, r);
+ return DEFER;
+ }
+
+HDEBUG(D_acl)
+ debug_printf("udpsend %d bytes\n", r);
+
+return OK;
+
+defer:
+*log_msgptr = string_sprintf("\"udpsend\" failed: %s", strerror(errno));
+return DEFER;
+}
+
+
+
/*************************************************
* Handle conditions/modifiers on an ACL item *
*************************************************/
#ifndef DISABLE_DKIM
case CONTROL_DKIM_VERIFY:
dkim_disable_verify = TRUE;
+ #ifdef EXPERIMENTAL_DMARC
+ /* Since DKIM was blocked, skip DMARC too */
+ dmarc_disable_verify = TRUE;
+ dmarc_enable_forensic = FALSE;
+ #endif
+ break;
+ #endif
+
+ #ifdef EXPERIMENTAL_DMARC
+ case CONTROL_DMARC_VERIFY:
+ dmarc_disable_verify = TRUE;
+ break;
+
+ case CONTROL_DMARC_FORENSIC:
+ dmarc_enable_forensic = TRUE;
break;
#endif
break;
#endif
+ #ifdef EXPERIMENTAL_DMARC
+ case ACLC_DMARC_STATUS:
+ if (!dmarc_has_been_checked)
+ dmarc_process();
+ dmarc_has_been_checked = TRUE;
+ /* used long way of dmarc_exim_expand_query() in case we need more
+ * view into the process in the future. */
+ rc = match_isinlist(dmarc_exim_expand_query(DMARC_VERIFY_STATUS),
+ &arg,0,NULL,NULL,MCL_STRING,TRUE,NULL);
+ break;
+ #endif
+
case ACLC_DNSLISTS:
rc = verify_check_dnsbl(&arg);
break;
break;
#endif
+ case ACLC_UDPSEND:
+ rc = acl_udpsend(arg, log_msgptr);
+ break;
+
/* If the verb is WARN, discard any user message from verification, because
such messages are SMTP responses, not header additions. The latter come
only from explicit "message" modifiers. However, put the user message into
* Check access using an ACL *
*************************************************/
+/* Alternate interface for ACL, used by expansions */
+int
+acl_eval(int where, uschar *s, uschar **user_msgptr, uschar **log_msgptr)
+{
+address_item adb;
+address_item *addr = NULL;
+
+*user_msgptr = *log_msgptr = NULL;
+sender_verified_failed = NULL;
+ratelimiters_cmd = NULL;
+log_reject_target = LOG_MAIN|LOG_REJECT;
+
+if (where == ACL_WHERE_RCPT)
+ {
+ adb = address_defaults;
+ addr = &adb;
+ addr->address = expand_string(US"$local_part@$domain");
+ addr->domain = deliver_domain;
+ addr->local_part = deliver_localpart;
+ addr->cc_local_part = deliver_localpart;
+ addr->lc_local_part = deliver_localpart;
+ }
+
+return acl_check_internal(where, addr, s, 0, user_msgptr, log_msgptr);
+}
+
+
+
/* This is the external interface for ACL checks. It sets up an address and the
expansions for $domain and $local_part when called after RCPT, then calls
acl_check_internal() to do the actual work.
DEFER can't tell at the moment
ERROR disaster
*/
+int acl_where = ACL_WHERE_UNKNOWN;
int
acl_check(int where, uschar *recipient, uschar *s, uschar **user_msgptr,
ratelimiters_cmd = NULL;
log_reject_target = LOG_MAIN|LOG_REJECT;
-if (where == ACL_WHERE_RCPT)
+#ifdef EXPERIMENTAL_PRDR
+if (where == ACL_WHERE_RCPT || where == ACL_WHERE_PRDR )
+#else
+if (where == ACL_WHERE_RCPT )
+#endif
{
adb = address_defaults;
addr = &adb;
deliver_localpart = addr->local_part;
}
+acl_where = where;
rc = acl_check_internal(where, addr, s, 0, user_msgptr, log_msgptr);
+acl_where = ACL_WHERE_UNKNOWN;
/* Cutthrough - if requested,
and WHERE_RCPT and not yet opened conn as result of recipient-verify,
switch (where)
{
case ACL_WHERE_RCPT:
+#ifdef EXPERIMENTAL_PRDR
+case ACL_WHERE_PRDR:
+#endif
if( rcpt_count > 1 )
cancel_cutthrough_connection("more than one recipient");
else if (rc == OK && cutthrough_delivery && cutthrough_fd < 0)
}
-
/*************************************************
* Create ACL variable *
*************************************************/