-/* $Cambridge: exim/src/src/acl.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
+/* $Cambridge: exim/src/src/acl.c,v 1.33 2005/05/23 15:28:38 fanf2 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2005 */
/* See the file NOTICE for conditions of use and distribution. */
/* Code for handling Access Control Lists (ACLs) */
/* ACL condition and modifier codes - keep in step with the table that
follows. */
-enum { ACLC_ACL, ACLC_AUTHENTICATED, ACLC_CONDITION, ACLC_CONTROL, ACLC_DELAY,
- ACLC_DNSLISTS, ACLC_DOMAINS, ACLC_ENCRYPTED, ACLC_ENDPASS, ACLC_HOSTS,
- ACLC_LOCAL_PARTS, ACLC_LOG_MESSAGE, ACLC_LOGWRITE, ACLC_MESSAGE,
- ACLC_RECIPIENTS, ACLC_SENDER_DOMAINS, ACLC_SENDERS, ACLC_SET, ACLC_VERIFY };
+enum { ACLC_ACL, ACLC_AUTHENTICATED,
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+ ACLC_BMI_OPTIN,
+#endif
+ACLC_CONDITION, ACLC_CONTROL,
+#ifdef WITH_CONTENT_SCAN
+ ACLC_DECODE,
+#endif
+ ACLC_DELAY,
+#ifdef WITH_OLD_DEMIME
+ ACLC_DEMIME,
+#endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+ ACLC_DK_DOMAIN_SOURCE,
+ ACLC_DK_POLICY,
+ ACLC_DK_SENDER_DOMAINS,
+ ACLC_DK_SENDER_LOCAL_PARTS,
+ ACLC_DK_SENDERS,
+ ACLC_DK_STATUS,
+#endif
+ ACLC_DNSLISTS, ACLC_DOMAINS, ACLC_ENCRYPTED, ACLC_ENDPASS,
+ ACLC_HOSTS, ACLC_LOCAL_PARTS, ACLC_LOG_MESSAGE, ACLC_LOGWRITE,
+#ifdef WITH_CONTENT_SCAN
+ ACLC_MALWARE,
+#endif
+ ACLC_MESSAGE,
+#ifdef WITH_CONTENT_SCAN
+ ACLC_MIME_REGEX,
+#endif
+ ACLC_RECIPIENTS,
+#ifdef WITH_CONTENT_SCAN
+ ACLC_REGEX,
+#endif
+ ACLC_SENDER_DOMAINS, ACLC_SENDERS, ACLC_SET,
+#ifdef WITH_CONTENT_SCAN
+ ACLC_SPAM,
+#endif
+#ifdef EXPERIMENTAL_SPF
+ ACLC_SPF,
+#endif
+ ACLC_VERIFY };
/* ACL conditions/modifiers: "delay", "control", "endpass", "message",
"log_message", "logwrite", and "set" are modifiers that look like conditions
but always return TRUE. They are used for their side effects. */
-static uschar *conditions[] = { US"acl", US"authenticated", US"condition",
- US"control", US"delay", US"dnslists", US"domains", US"encrypted",
+static uschar *conditions[] = { US"acl", US"authenticated",
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+ US"bmi_optin",
+#endif
+ US"condition",
+ US"control",
+#ifdef WITH_CONTENT_SCAN
+ US"decode",
+#endif
+ US"delay",
+#ifdef WITH_OLD_DEMIME
+ US"demime",
+#endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+ US"dk_domain_source",
+ US"dk_policy",
+ US"dk_sender_domains",
+ US"dk_sender_local_parts",
+ US"dk_senders",
+ US"dk_status",
+#endif
+ US"dnslists", US"domains", US"encrypted",
US"endpass", US"hosts", US"local_parts", US"log_message", US"logwrite",
- US"message", US"recipients", US"sender_domains", US"senders", US"set",
+#ifdef WITH_CONTENT_SCAN
+ US"malware",
+#endif
+ US"message",
+#ifdef WITH_CONTENT_SCAN
+ US"mime_regex",
+#endif
+ US"recipients",
+#ifdef WITH_CONTENT_SCAN
+ US"regex",
+#endif
+ US"sender_domains", US"senders", US"set",
+#ifdef WITH_CONTENT_SCAN
+ US"spam",
+#endif
+#ifdef EXPERIMENTAL_SPF
+ US"spf",
+#endif
US"verify" };
+/* ACL control names */
+
+static uschar *controls[] = { US"error", US"caseful_local_part",
+ US"caselower_local_part", US"enforce_sync", US"no_enforce_sync", US"freeze",
+ US"queue_only", US"submission", US"no_multiline"};
+
/* Flags to indicate for which conditions /modifiers a string expansion is done
at the outer level. In the other cases, expansion already occurs in the
checking functions. */
static uschar cond_expand_at_top[] = {
TRUE, /* acl */
FALSE, /* authenticated */
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+ TRUE, /* bmi_optin */
+#endif
TRUE, /* condition */
TRUE, /* control */
+#ifdef WITH_CONTENT_SCAN
+ TRUE, /* decode */
+#endif
TRUE, /* delay */
+#ifdef WITH_OLD_DEMIME
+ TRUE, /* demime */
+#endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+ TRUE, /* dk_domain_source */
+ TRUE, /* dk_policy */
+ TRUE, /* dk_sender_domains */
+ TRUE, /* dk_sender_local_parts */
+ TRUE, /* dk_senders */
+ TRUE, /* dk_status */
+#endif
TRUE, /* dnslists */
FALSE, /* domains */
FALSE, /* encrypted */
FALSE, /* local_parts */
TRUE, /* log_message */
TRUE, /* logwrite */
+#ifdef WITH_CONTENT_SCAN
+ TRUE, /* malware */
+#endif
TRUE, /* message */
+#ifdef WITH_CONTENT_SCAN
+ TRUE, /* mime_regex */
+#endif
FALSE, /* recipients */
+#ifdef WITH_CONTENT_SCAN
+ TRUE, /* regex */
+#endif
FALSE, /* sender_domains */
FALSE, /* senders */
TRUE, /* set */
+#ifdef WITH_CONTENT_SCAN
+ TRUE, /* spam */
+#endif
+#ifdef EXPERIMENTAL_SPF
+ TRUE, /* spf */
+#endif
TRUE /* verify */
};
static uschar cond_modifiers[] = {
FALSE, /* acl */
FALSE, /* authenticated */
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+ TRUE, /* bmi_optin */
+#endif
FALSE, /* condition */
TRUE, /* control */
+#ifdef WITH_CONTENT_SCAN
+ FALSE, /* decode */
+#endif
TRUE, /* delay */
+#ifdef WITH_OLD_DEMIME
+ FALSE, /* demime */
+#endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+ FALSE, /* dk_domain_source */
+ FALSE, /* dk_policy */
+ FALSE, /* dk_sender_domains */
+ FALSE, /* dk_sender_local_parts */
+ FALSE, /* dk_senders */
+ FALSE, /* dk_status */
+#endif
FALSE, /* dnslists */
FALSE, /* domains */
FALSE, /* encrypted */
FALSE, /* hosts */
FALSE, /* local_parts */
TRUE, /* log_message */
- TRUE, /* log_write */
+ TRUE, /* logwrite */
+#ifdef WITH_CONTENT_SCAN
+ FALSE, /* malware */
+#endif
TRUE, /* message */
+#ifdef WITH_CONTENT_SCAN
+ FALSE, /* mime_regex */
+#endif
FALSE, /* recipients */
+#ifdef WITH_CONTENT_SCAN
+ FALSE, /* regex */
+#endif
FALSE, /* sender_domains */
FALSE, /* senders */
TRUE, /* set */
+#ifdef WITH_CONTENT_SCAN
+ FALSE, /* spam */
+#endif
+#ifdef EXPERIMENTAL_SPF
+ FALSE, /* spf */
+#endif
FALSE /* verify */
};
-/* Bit map of which conditions are not allowed at certain times. For each
-condition, there's a bitmap of dis-allowed times. */
+/* Bit map vector of which conditions are not allowed at certain times. For
+each condition, there's a bitmap of dis-allowed times. For some, it is easier
+to specify the negation of a small number of allowed times. */
static unsigned int cond_forbids[] = {
0, /* acl */
+
(1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_CONNECT)| /* authenticated */
(1<<ACL_WHERE_HELO),
+
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+ (1<<ACL_WHERE_AUTH)| /* bmi_optin */
+ (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
+ (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_MIME)|
+ (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+ (1<<ACL_WHERE_MAILAUTH)|
+ (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
+ (1<<ACL_WHERE_VRFY)|(1<<ACL_WHERE_PREDATA),
+#endif
+
0, /* condition */
/* Certain types of control are always allowed, so we let it through
- always and check in the control processing itself */
+ always and check in the control processing itself. */
0, /* control */
+
+#ifdef WITH_CONTENT_SCAN
+ (unsigned int)
+ ~(1<<ACL_WHERE_MIME), /* decode */
+#endif
+
0, /* delay */
- (1<<ACL_WHERE_NOTSMTP), /* dnslists */
- (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_AUTH)| /* domains */
+#ifdef WITH_OLD_DEMIME
+ (unsigned int)
+ ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)), /* demime */
+#endif
+
+#ifdef EXPERIMENTAL_DOMAINKEYS
+ (1<<ACL_WHERE_AUTH)| /* dk_domain_source */
(1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
- (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_PREDATA)|
+ (1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
(1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
(1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
(1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
(1<<ACL_WHERE_VRFY),
- (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_CONNECT)| /* encrypted */
- (1<<ACL_WHERE_HELO),
- 0, /* endpass */
- (1<<ACL_WHERE_NOTSMTP), /* hosts */
+ (1<<ACL_WHERE_AUTH)| /* dk_policy */
+ (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
+ (1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
+ (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+ (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
+ (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
+ (1<<ACL_WHERE_VRFY),
- (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_AUTH)| /* local_parts */
+ (1<<ACL_WHERE_AUTH)| /* dk_sender_domains */
(1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
- (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_PREDATA)|
+ (1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
(1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
(1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
(1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
(1<<ACL_WHERE_VRFY),
- 0, /* log_message */
- 0, /* logwrite */
- 0, /* message */
+ (1<<ACL_WHERE_AUTH)| /* dk_sender_local_parts */
+ (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
+ (1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
+ (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+ (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
+ (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
+ (1<<ACL_WHERE_VRFY),
- (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_AUTH)| /* recipients */
+ (1<<ACL_WHERE_AUTH)| /* dk_senders */
(1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
- (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_PREDATA)|
+ (1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
(1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
(1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
(1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
(1<<ACL_WHERE_VRFY),
+ (1<<ACL_WHERE_AUTH)| /* dk_status */
+ (1<<ACL_WHERE_CONNECT)|(1<<ACL_WHERE_HELO)|
+ (1<<ACL_WHERE_RCPT)|(1<<ACL_WHERE_PREDATA)|
+ (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+ (1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
+ (1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_STARTTLS)|
+ (1<<ACL_WHERE_VRFY),
+#endif
+
+ (1<<ACL_WHERE_NOTSMTP), /* dnslists */
+
+ (unsigned int)
+ ~(1<<ACL_WHERE_RCPT), /* domains */
+
+ (1<<ACL_WHERE_NOTSMTP)|(1<<ACL_WHERE_CONNECT)| /* encrypted */
+ (1<<ACL_WHERE_HELO),
+
+ 0, /* endpass */
+
+ (1<<ACL_WHERE_NOTSMTP), /* hosts */
+
+ (unsigned int)
+ ~(1<<ACL_WHERE_RCPT), /* local_parts */
+
+ 0, /* log_message */
+
+ 0, /* logwrite */
+
+#ifdef WITH_CONTENT_SCAN
+ (unsigned int)
+ ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)), /* malware */
+#endif
+
+ 0, /* message */
+
+#ifdef WITH_CONTENT_SCAN
+ (unsigned int)
+ ~(1<<ACL_WHERE_MIME), /* mime_regex */
+#endif
+
+ (unsigned int)
+ ~(1<<ACL_WHERE_RCPT), /* recipients */
+
+#ifdef WITH_CONTENT_SCAN
+ (unsigned int)
+ ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)| /* regex */
+ (1<<ACL_WHERE_MIME)),
+#endif
+
(1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)| /* sender_domains */
(1<<ACL_WHERE_HELO)|
(1<<ACL_WHERE_MAILAUTH)|(1<<ACL_WHERE_QUIT)|
0, /* set */
+#ifdef WITH_CONTENT_SCAN
+ (unsigned int)
+ ~((1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP)), /* spam */
+#endif
+
+#ifdef EXPERIMENTAL_SPF
+ (1<<ACL_WHERE_AUTH)|(1<<ACL_WHERE_CONNECT)| /* spf */
+ (1<<ACL_WHERE_HELO)|
+ (1<<ACL_WHERE_MAILAUTH)|
+ (1<<ACL_WHERE_ETRN)|(1<<ACL_WHERE_EXPN)|
+ (1<<ACL_WHERE_STARTTLS)|(1<<ACL_WHERE_VRFY),
+#endif
+
/* Certain types of verify are always allowed, so we let it through
always and check in the verify function itself */
0 /* verify */
-
};
/* Return values from decode_control() */
-enum { CONTROL_ERROR, CONTROL_CASEFUL_LOCAL_PART, CONTROL_CASELOWER_LOCAL_PART,
+enum {
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+ CONTROL_BMI_RUN,
+#endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+ CONTROL_DK_VERIFY,
+#endif
+ CONTROL_ERROR, CONTROL_CASEFUL_LOCAL_PART, CONTROL_CASELOWER_LOCAL_PART,
CONTROL_ENFORCE_SYNC, CONTROL_NO_ENFORCE_SYNC, CONTROL_FREEZE,
- CONTROL_QUEUE_ONLY, CONTROL_SUBMISSION, CONTROL_NO_MULTILINE };
+ CONTROL_QUEUE_ONLY, CONTROL_SUBMISSION,
+#ifdef WITH_CONTENT_SCAN
+ CONTROL_NO_MBOX_UNSPOOL,
+#endif
+ CONTROL_FAKEDEFER, CONTROL_FAKEREJECT, CONTROL_NO_MULTILINE };
+
+/* Bit map vector of which controls are not allowed at certain times. For
+each control, there's a bitmap of dis-allowed times. For some, it is easier to
+specify the negation of a small number of allowed times. */
+
+static unsigned int control_forbids[] = {
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+ 0, /* bmi_run */
+#endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+ (1<<ACL_WHERE_DATA)|(1<<ACL_WHERE_NOTSMTP), /* dk_verify */
+#endif
+
+ 0, /* error */
+
+ (unsigned int)
+ ~(1<<ACL_WHERE_RCPT), /* caseful_local_part */
+
+ (unsigned int)
+ ~(1<<ACL_WHERE_RCPT), /* caselower_local_part */
+
+ (1<<ACL_WHERE_NOTSMTP), /* enforce_sync */
+
+ (1<<ACL_WHERE_NOTSMTP), /* no_enforce_sync */
+
+ (unsigned int)
+ ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)| /* freeze */
+ (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+ (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_NOTSMTP)|(1<<ACL_WHERE_MIME)),
+
+ (unsigned int)
+ ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)| /* submission */
+ (1<<ACL_WHERE_PREDATA)),
+
+#ifdef WITH_CONTENT_SCAN
+ (unsigned int)
+ ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)| /* no_mbox_unspool */
+ (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+ (1<<ACL_WHERE_MIME)),
+#endif
+
+ (unsigned int)
+ ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)| /* fakedefer */
+ (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+ (1<<ACL_WHERE_MIME)),
+
+ (unsigned int)
+ ~((1<<ACL_WHERE_MAIL)|(1<<ACL_WHERE_RCPT)| /* fakereject */
+ (1<<ACL_WHERE_PREDATA)|(1<<ACL_WHERE_DATA)|
+ (1<<ACL_WHERE_MIME)),
+
+ (1<<ACL_WHERE_NOTSMTP) /* no_multiline */
+};
-/* Structure listing various control arguments, with their characteristics.
-The maximum "where" value controls the ACLs in which the various controls are
-permitted to occur. Specifying ACL_WHERE_RCPT limits it to just the RCPT ACL;
-specifying ACL_WHERE_NOTSMTP limits it to "message" ACLs. */
+/* Structure listing various control arguments, with their characteristics. */
typedef struct control_def {
uschar *name;
int value; /* CONTROL_xxx value */
- int where_max; /* Maximum "where" value */
BOOL has_option; /* Has /option(s) following */
} control_def;
static control_def controls_list[] = {
- { US"caseful_local_part", CONTROL_CASEFUL_LOCAL_PART,
- ACL_WHERE_RCPT, FALSE },
- { US"caselower_local_part", CONTROL_CASELOWER_LOCAL_PART,
- ACL_WHERE_RCPT, FALSE },
- { US"enforce_sync", CONTROL_ENFORCE_SYNC,
- INT_MAX, FALSE },
- { US"freeze", CONTROL_FREEZE,
- ACL_WHERE_NOTSMTP, FALSE },
- { US"no_enforce_sync", CONTROL_NO_ENFORCE_SYNC,
- INT_MAX, FALSE },
- { US"no_multiline_responses", CONTROL_NO_MULTILINE,
- INT_MAX, FALSE },
- { US"queue_only", CONTROL_QUEUE_ONLY,
- ACL_WHERE_NOTSMTP, FALSE },
- { US"submission", CONTROL_SUBMISSION,
- ACL_WHERE_NOTSMTP, TRUE }
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+ { US"bmi_run", CONTROL_BMI_RUN, FALSE},
+#endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+ { US"dk_verify", CONTROL_DK_VERIFY, FALSE},
+#endif
+ { US"caseful_local_part", CONTROL_CASEFUL_LOCAL_PART, FALSE},
+ { US"caselower_local_part", CONTROL_CASELOWER_LOCAL_PART, FALSE},
+ { US"enforce_sync", CONTROL_ENFORCE_SYNC, FALSE},
+ { US"freeze", CONTROL_FREEZE, FALSE},
+ { US"no_enforce_sync", CONTROL_NO_ENFORCE_SYNC, FALSE},
+ { US"no_multiline_responses", CONTROL_NO_MULTILINE, FALSE},
+ { US"queue_only", CONTROL_QUEUE_ONLY, FALSE},
+#ifdef WITH_CONTENT_SCAN
+ { US"no_mbox_unspool", CONTROL_NO_MBOX_UNSPOOL, FALSE},
+#endif
+ { US"fakedefer", CONTROL_FAKEDEFER, TRUE},
+ { US"fakereject", CONTROL_FAKEREJECT, TRUE},
+ { US"submission", CONTROL_SUBMISSION, TRUE}
};
+/* Support data structures for Client SMTP Authorization. acl_verify_csa()
+caches its result in a tree to avoid repeated DNS queries. The result is an
+integer code which is used as an index into the following tables of
+explanatory strings and verification return codes. */
+
+static tree_node *csa_cache = NULL;
+
+enum { CSA_UNKNOWN, CSA_OK, CSA_DEFER_SRV, CSA_DEFER_ADDR,
+ CSA_FAIL_EXPLICIT, CSA_FAIL_DOMAIN, CSA_FAIL_NOADDR, CSA_FAIL_MISMATCH };
+
+/* The acl_verify_csa() return code is translated into an acl_verify() return
+code using the following table. It is OK unless the client is definitely not
+authorized. This is because CSA is supposed to be optional for sending sites,
+so recipients should not be too strict about checking it - especially because
+DNS problems are quite likely to occur. It's possible to use $csa_status in
+further ACL conditions to distinguish ok, unknown, and defer if required, but
+the aim is to make the usual configuration simple. */
+
+static int csa_return_code[] = {
+ OK, OK, OK, OK,
+ FAIL, FAIL, FAIL, FAIL
+};
+
+static uschar *csa_status_string[] = {
+ US"unknown", US"ok", US"defer", US"defer",
+ US"fail", US"fail", US"fail", US"fail"
+};
+
+static uschar *csa_reason_string[] = {
+ US"unknown",
+ US"ok",
+ US"deferred (SRV lookup failed)",
+ US"deferred (target address lookup failed)",
+ US"failed (explicit authorization required)",
+ US"failed (host name not authorized)",
+ US"failed (no authorized addresses)",
+ US"failed (client address mismatch)"
+};
+
/* Enable recursion between acl_check_internal() and acl_check_condition() */
static int acl_check_internal(int, address_item *, uschar *, int, uschar **,
s++;
}
- /* Read the name of a verb or a condition, or the start of a new ACL */
+ /* Read the name of a verb or a condition, or the start of a new ACL, which
+ can be started by a name, or by a macro definition. */
s = readconf_readname(name, sizeof(name), s);
- if (*s == ':')
- {
- if (negated || name[0] == 0)
- {
- *error = string_sprintf("malformed ACL name in \"%s\"", saveline);
- return NULL;
- }
- break;
- }
+ if (*s == ':' || isupper(name[0] && *s == '=')) return yield;
/* If a verb is unrecognized, it may be another condition or modifier that
continues the previous verb. */
strcmpic(log_message, US"sender verify failed") == 0)
text = string_sprintf("%s: %s", text, sender_verified_failed->message);
- /* Search previously logged warnings. They are kept in malloc store so they
- can be freed at the start of a new message. */
+ /* Search previously logged warnings. They are kept in malloc
+ store so they can be freed at the start of a new message. */
for (logged = acl_warn_logged; logged != NULL; logged = logged->next)
if (Ustrcmp(logged->text, text) == 0) break;
newtype = htype_add_rec;
p += 16;
}
+ else if (strncmpic(p, US":at_start_rfc:", 14) == 0)
+ {
+ newtype = htype_add_rfc;
+ p += 14;
+ }
else if (strncmpic(p, US":at_start:", 10) == 0)
{
newtype = htype_add_top;
+/*************************************************
+* Check client IP address matches CSA target *
+*************************************************/
+
+/* Called from acl_verify_csa() below. This routine scans a section of a DNS
+response for address records belonging to the CSA target hostname. The section
+is specified by the reset argument, either RESET_ADDITIONAL or RESET_ANSWERS.
+If one of the addresses matches the client's IP address, then the client is
+authorized by CSA. If there are target IP addresses but none of them match
+then the client is using an unauthorized IP address. If there are no target IP
+addresses then the client cannot be using an authorized IP address. (This is
+an odd configuration - why didn't the SRV record have a weight of 1 instead?)
+
+Arguments:
+ dnsa the DNS answer block
+ dnss a DNS scan block for us to use
+ reset option specifing what portion to scan, as described above
+ target the target hostname to use for matching RR names
+
+Returns: CSA_OK successfully authorized
+ CSA_FAIL_MISMATCH addresses found but none matched
+ CSA_FAIL_NOADDR no target addresses found
+*/
+
+static int
+acl_verify_csa_address(dns_answer *dnsa, dns_scan *dnss, int reset,
+ uschar *target)
+{
+dns_record *rr;
+dns_address *da;
+
+BOOL target_found = FALSE;
+
+for (rr = dns_next_rr(dnsa, dnss, reset);
+ rr != NULL;
+ rr = dns_next_rr(dnsa, dnss, RESET_NEXT))
+ {
+ /* Check this is an address RR for the target hostname. */
+
+ if (rr->type != T_A
+ #if HAVE_IPV6
+ && rr->type != T_AAAA
+ #ifdef SUPPORT_A6
+ && rr->type != T_A6
+ #endif
+ #endif
+ ) continue;
+
+ if (strcmpic(target, rr->name) != 0) continue;
+
+ target_found = TRUE;
+
+ /* Turn the target address RR into a list of textual IP addresses and scan
+ the list. There may be more than one if it is an A6 RR. */
+
+ for (da = dns_address_from_rr(dnsa, rr); da != NULL; da = da->next)
+ {
+ /* If the client IP address matches the target IP address, it's good! */
+
+ DEBUG(D_acl) debug_printf("CSA target address is %s\n", da->address);
+
+ if (strcmpic(sender_host_address, da->address) == 0) return CSA_OK;
+ }
+ }
+
+/* If we found some target addresses but none of them matched, the client is
+using an unauthorized IP address, otherwise the target has no authorized IP
+addresses. */
+
+if (target_found) return CSA_FAIL_MISMATCH;
+else return CSA_FAIL_NOADDR;
+}
+
+
+
+/*************************************************
+* Verify Client SMTP Authorization *
+*************************************************/
+
+/* Called from acl_verify() below. This routine calls dns_lookup_special()
+to find the CSA SRV record corresponding to the domain argument, or
+$sender_helo_name if no argument is provided. It then checks that the
+client is authorized, and that its IP address corresponds to the SRV
+target's address by calling acl_verify_csa_address() above. The address
+should have been returned in the DNS response's ADDITIONAL section, but if
+not we perform another DNS lookup to get it.
+
+Arguments:
+ domain pointer to optional parameter following verify = csa
+
+Returns: CSA_UNKNOWN no valid CSA record found
+ CSA_OK successfully authorized
+ CSA_FAIL_* client is definitely not authorized
+ CSA_DEFER_* there was a DNS problem
+*/
+
+static int
+acl_verify_csa(uschar *domain)
+{
+tree_node *t;
+uschar *found, *p;
+int priority, weight, port;
+dns_answer dnsa;
+dns_scan dnss;
+dns_record *rr;
+int rc, type;
+uschar target[256];
+
+/* Work out the domain we are using for the CSA lookup. The default is the
+client's HELO domain. If the client has not said HELO, use its IP address
+instead. If it's a local client (exim -bs), CSA isn't applicable. */
+
+while (isspace(*domain) && *domain != '\0') ++domain;
+if (*domain == '\0') domain = sender_helo_name;
+if (domain == NULL) domain = sender_host_address;
+if (sender_host_address == NULL) return CSA_UNKNOWN;
+
+/* If we have an address literal, strip off the framing ready for turning it
+into a domain. The framing consists of matched square brackets possibly
+containing a keyword and a colon before the actual IP address. */
+
+if (domain[0] == '[')
+ {
+ uschar *start = Ustrchr(domain, ':');
+ if (start == NULL) start = domain;
+ domain = string_copyn(start + 1, Ustrlen(start) - 2);
+ }
+
+/* Turn domains that look like bare IP addresses into domains in the reverse
+DNS. This code also deals with address literals and $sender_host_address. It's
+not quite kosher to treat bare domains such as EHLO 192.0.2.57 the same as
+address literals, but it's probably the most friendly thing to do. This is an
+extension to CSA, so we allow it to be turned off for proper conformance. */
+
+if (string_is_ip_address(domain, NULL))
+ {
+ if (!dns_csa_use_reverse) return CSA_UNKNOWN;
+ dns_build_reverse(domain, target);
+ domain = target;
+ }
+
+/* Find out if we've already done the CSA check for this domain. If we have,
+return the same result again. Otherwise build a new cached result structure
+for this domain. The name is filled in now, and the value is filled in when
+we return from this function. */
+
+t = tree_search(csa_cache, domain);
+if (t != NULL) return t->data.val;
+
+t = store_get_perm(sizeof(tree_node) + Ustrlen(domain));
+Ustrcpy(t->name, domain);
+(void)tree_insertnode(&csa_cache, t);
+
+/* Now we are ready to do the actual DNS lookup(s). */
+
+switch (dns_special_lookup(&dnsa, domain, T_CSA, &found))
+ {
+ /* If something bad happened (most commonly DNS_AGAIN), defer. */
+
+ default:
+ return t->data.val = CSA_DEFER_SRV;
+
+ /* If we found nothing, the client's authorization is unknown. */
+
+ case DNS_NOMATCH:
+ case DNS_NODATA:
+ return t->data.val = CSA_UNKNOWN;
+
+ /* We got something! Go on to look at the reply in more detail. */
+
+ case DNS_SUCCEED:
+ break;
+ }
+
+/* Scan the reply for well-formed CSA SRV records. */
+
+for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
+ rr != NULL;
+ rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
+ {
+ if (rr->type != T_SRV) continue;
+
+ /* Extract the numerical SRV fields (p is incremented) */
+
+ p = rr->data;
+ GETSHORT(priority, p);
+ GETSHORT(weight, p);
+ GETSHORT(port, p);
+
+ DEBUG(D_acl)
+ debug_printf("CSA priority=%d weight=%d port=%d\n", priority, weight, port);
+
+ /* Check the CSA version number */
+
+ if (priority != 1) continue;
+
+ /* If the domain does not have a CSA SRV record of its own (i.e. the domain
+ found by dns_special_lookup() is a parent of the one we asked for), we check
+ the subdomain assertions in the port field. At the moment there's only one
+ assertion: legitimate SMTP clients are all explicitly authorized with CSA
+ SRV records of their own. */
+
+ if (found != domain)
+ {
+ if (port & 1)
+ return t->data.val = CSA_FAIL_EXPLICIT;
+ else
+ return t->data.val = CSA_UNKNOWN;
+ }
+
+ /* This CSA SRV record refers directly to our domain, so we check the value
+ in the weight field to work out the domain's authorization. 0 and 1 are
+ unauthorized; 3 means the client is authorized but we can't check the IP
+ address in order to authenticate it, so we treat it as unknown; values
+ greater than 3 are undefined. */
+
+ if (weight < 2) return t->data.val = CSA_FAIL_DOMAIN;
+
+ if (weight > 2) continue;
+
+ /* Weight == 2, which means the domain is authorized. We must check that the
+ client's IP address is listed as one of the SRV target addresses. Save the
+ target hostname then break to scan the additional data for its addresses. */
+
+ (void)dn_expand(dnsa.answer, dnsa.answer + dnsa.answerlen, p,
+ (DN_EXPAND_ARG4_TYPE)target, sizeof(target));
+
+ DEBUG(D_acl) debug_printf("CSA target is %s\n", target);
+
+ break;
+ }
+
+/* If we didn't break the loop then no appropriate records were found. */
+
+if (rr == NULL) return t->data.val = CSA_UNKNOWN;
+
+/* Do not check addresses if the target is ".", in accordance with RFC 2782.
+A target of "." indicates there are no valid addresses, so the client cannot
+be authorized. (This is an odd configuration because weight=2 target=. is
+equivalent to weight=1, but we check for it in order to keep load off the
+root name servers.) Note that dn_expand() turns "." into "". */
+
+if (Ustrcmp(target, "") == 0) return t->data.val = CSA_FAIL_NOADDR;
+
+/* Scan the additional section of the CSA SRV reply for addresses belonging
+to the target. If the name server didn't return any additional data (e.g.
+because it does not fully support SRV records), we need to do another lookup
+to obtain the target addresses; otherwise we have a definitive result. */
+
+rc = acl_verify_csa_address(&dnsa, &dnss, RESET_ADDITIONAL, target);
+if (rc != CSA_FAIL_NOADDR) return t->data.val = rc;
+
+/* The DNS lookup type corresponds to the IP version used by the client. */
+
+#if HAVE_IPV6
+if (Ustrchr(sender_host_address, ':') != NULL)
+ type = T_AAAA;
+else
+#endif /* HAVE_IPV6 */
+ type = T_A;
+
+
+#if HAVE_IPV6 && defined(SUPPORT_A6)
+DNS_LOOKUP_AGAIN:
+#endif
+
+switch (dns_lookup(&dnsa, target, type, NULL))
+ {
+ /* If something bad happened (most commonly DNS_AGAIN), defer. */
+
+ default:
+ return t->data.val = CSA_DEFER_ADDR;
+
+ /* If the query succeeded, scan the addresses and return the result. */
+
+ case DNS_SUCCEED:
+ rc = acl_verify_csa_address(&dnsa, &dnss, RESET_ANSWERS, target);
+ if (rc != CSA_FAIL_NOADDR) return t->data.val = rc;
+ /* else fall through */
+
+ /* If the target has no IP addresses, the client cannot have an authorized
+ IP address. However, if the target site uses A6 records (not AAAA records)
+ we have to do yet another lookup in order to check them. */
+
+ case DNS_NOMATCH:
+ case DNS_NODATA:
+
+ #if HAVE_IPV6 && defined(SUPPORT_A6)
+ if (type == T_AAAA) { type = T_A6; goto DNS_LOOKUP_AGAIN; }
+ #endif
+
+ return t->data.val = CSA_FAIL_NOADDR;
+ }
+}
+
+
+
/*************************************************
* Handle verification (address & other) *
*************************************************/
int sep = '/';
int callout = -1;
int callout_overall = -1;
+int callout_connect = -1;
int verify_options = 0;
int rc;
BOOL verify_header_sender = FALSE;
uschar *verify_sender_address = NULL;
uschar *pm_mailfrom = NULL;
uschar *se_mailfrom = NULL;
+
+/* Some of the verify items have slash-separated options; some do not. Diagnose
+an error if options are given for items that don't expect them. This code has
+now got very message. Refactoring to use a table would be a good idea one day.
+*/
+
+uschar *slash = Ustrchr(arg, '/');
uschar *list = arg;
uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
if (strcmpic(ss, US"reverse_host_lookup") == 0)
{
+ if (slash != NULL) goto NO_OPTIONS;
if (sender_host_address == NULL) return OK;
return acl_verify_reverse(user_msgptr, log_msgptr);
}
if (strcmpic(ss, US"certificate") == 0)
{
+ if (slash != NULL) goto NO_OPTIONS;
if (tls_certificate_verified) return OK;
*user_msgptr = US"no verified certificate";
return FAIL;
/* We can test the result of optional HELO verification */
-if (strcmpic(ss, US"helo") == 0) return helo_verified? OK : FAIL;
+if (strcmpic(ss, US"helo") == 0)
+ {
+ if (slash != NULL) goto NO_OPTIONS;
+ return helo_verified? OK : FAIL;
+ }
+
+/* Do Client SMTP Authorization checks in a separate function, and turn the
+result code into user-friendly strings. */
+
+if (strcmpic(ss, US"csa") == 0)
+ {
+ rc = acl_verify_csa(list);
+ *log_msgptr = *user_msgptr = string_sprintf("client SMTP authorization %s",
+ csa_reason_string[rc]);
+ csa_status = csa_status_string[rc];
+ DEBUG(D_acl) debug_printf("CSA result %s\n", csa_status);
+ return csa_return_code[rc];
+ }
-/* Handle header verification options - permitted only after DATA or a non-SMTP
-message. */
+/* Check that all relevant header lines have the correct syntax. If there is
+a syntax error, we return details of the error to the sender if configured to
+send out full details. (But a "message" setting on the ACL can override, as
+always). */
-if (strncmpic(ss, US"header_", 7) == 0)
+if (strcmpic(ss, US"header_syntax") == 0)
{
+ if (slash != NULL) goto NO_OPTIONS;
if (where != ACL_WHERE_DATA && where != ACL_WHERE_NOTSMTP)
{
*log_msgptr = string_sprintf("cannot check header contents in ACL for %s "
"(only possible in ACL for DATA)", acl_wherenames[where]);
return ERROR;
}
+ rc = verify_check_headers(log_msgptr);
+ if (rc != OK && smtp_return_error_details && *log_msgptr != NULL)
+ *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
+ return rc;
+ }
- /* Check that all relevant header lines have the correct syntax. If there is
- a syntax error, we return details of the error to the sender if configured to
- send out full details. (But a "message" setting on the ACL can override, as
- always). */
- if (strcmpic(ss+7, US"syntax") == 0)
- {
- int rc = verify_check_headers(log_msgptr);
- if (rc != OK && smtp_return_error_details && *log_msgptr != NULL)
- *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
- return rc;
- }
+/* The remaining verification tests check recipient and sender addresses,
+either from the envelope or from the header. There are a number of
+slash-separated options that are common to all of them. */
- /* Check that there is at least one verifiable sender address in the relevant
- header lines. This can be followed by callout and defer options, just like
- sender and recipient. */
- else if (strcmpic(ss+7, US"sender") == 0) verify_header_sender = TRUE;
+/* Check that there is at least one verifiable sender address in the relevant
+header lines. This can be followed by callout and defer options, just like
+sender and recipient. */
- /* Unknown verify argument starting with "header_" */
-
- else goto BAD_VERIFY;
+if (strcmpic(ss, US"header_sender") == 0)
+ {
+ if (where != ACL_WHERE_DATA && where != ACL_WHERE_NOTSMTP)
+ {
+ *log_msgptr = string_sprintf("cannot check header contents in ACL for %s "
+ "(only possible in ACL for DATA)", acl_wherenames[where]);
+ return ERROR;
+ }
+ verify_header_sender = TRUE;
}
/* Otherwise, first item in verify argument must be "sender" or "recipient".
}
}
-/* Remaining items are optional */
+/* Remaining items are optional; they apply to sender and recipient
+verification, including "header sender" verification. */
while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))
!= NULL)
uschar *opt;
uschar buffer[256];
while (isspace(*ss)) ss++;
+
+ /* This callout option handling code has become a mess as new options
+ have been added in an ad hoc manner. It should be tidied up into some
+ kind of table-driven thing. */
+
while ((opt = string_nextinlist(&ss, &optsep, buffer, sizeof(buffer)))
!= NULL)
{
return ERROR;
}
}
+ else if (strncmpic(opt, US"connect", 7) == 0)
+ {
+ opt += 7;
+ while (isspace(*opt)) opt++;
+ if (*opt++ != '=')
+ {
+ *log_msgptr = string_sprintf("'=' expected after "
+ "\"callout_overaall\" in ACL condition \"%s\"", arg);
+ return ERROR;
+ }
+ while (isspace(*opt)) opt++;
+ callout_connect = readconf_readtime(opt, 0, FALSE);
+ if (callout_connect < 0)
+ {
+ *log_msgptr = string_sprintf("bad time value in ACL condition "
+ "\"verify %s\"", arg);
+ return ERROR;
+ }
+ }
else /* Plain time is callout connect/command timeout */
{
callout = readconf_readtime(opt, 0, FALSE);
if (verify_header_sender)
{
+ int verrno;
rc = verify_check_header_address(user_msgptr, log_msgptr, callout,
- callout_overall, se_mailfrom, pm_mailfrom, verify_options);
- if (smtp_return_error_details)
+ callout_overall, callout_connect, se_mailfrom, pm_mailfrom, verify_options,
+ &verrno);
+ if (rc != OK)
{
- if (*user_msgptr == NULL && *log_msgptr != NULL)
- *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
- if (rc == DEFER) acl_temp_details = TRUE;
+ *basic_errno = verrno;
+ if (smtp_return_error_details)
+ {
+ if (*user_msgptr == NULL && *log_msgptr != NULL)
+ *user_msgptr = string_sprintf("Rejected after DATA: %s", *log_msgptr);
+ if (rc == DEFER) acl_temp_details = TRUE;
+ }
}
}
else
{
BOOL routed = TRUE;
+ uschar *save_address_data = deliver_address_data;
+
sender_vaddr = deliver_make_addr(verify_sender_address, TRUE);
if (no_details) setflag(sender_vaddr, af_sverify_told);
if (verify_sender_address[0] != 0)
verify_options. */
rc = verify_address(sender_vaddr, NULL, verify_options, callout,
- callout_overall, se_mailfrom, pm_mailfrom, &routed);
+ callout_overall, callout_connect, se_mailfrom, pm_mailfrom, &routed);
HDEBUG(D_acl) debug_printf("----------- end verify ------------\n");
sender_vaddr->special_action = rc;
sender_vaddr->next = sender_verified_list;
sender_verified_list = sender_vaddr;
+
+ /* Restore the recipient address data, which might have been clobbered by
+ the sender verification. */
+
+ deliver_address_data = save_address_data;
}
+
+ /* Put the sender address_data value into $sender_address_data */
+
+ sender_address_data = sender_vaddr->p.address_data;
}
/* A recipient address just gets a straightforward verify; again we must handle
addr2 = *addr;
rc = verify_address(&addr2, NULL, verify_options|vopt_is_recipient, callout,
- callout_overall, se_mailfrom, pm_mailfrom, NULL);
+ callout_overall, callout_connect, se_mailfrom, pm_mailfrom, NULL);
HDEBUG(D_acl) debug_printf("----------- end verify ------------\n");
+
*log_msgptr = addr2.message;
- *user_msgptr = addr2.user_message;
+ *user_msgptr = (addr2.user_message != NULL)?
+ addr2.user_message : addr2.message;
*basic_errno = addr2.basic_errno;
/* Make $address_data visible */
BAD_VERIFY:
*log_msgptr = string_sprintf("expected \"sender[=address]\", \"recipient\", "
- "\"header_syntax\" or \"header_sender\" at start of ACL condition "
+ "\"helo\", \"header_syntax\", \"header_sender\" or "
+ "\"reverse_host_lookup\" at start of ACL condition "
"\"verify %s\"", arg);
return ERROR;
+
+/* Options supplied when not allowed come here */
+
+NO_OPTIONS:
+*log_msgptr = string_sprintf("unexpected '/' found in \"%s\" "
+ "(this verify item has no options)", arg);
+return ERROR;
}
return CONTROL_ERROR;
}
-if (where > d->where_max)
- {
- *log_msgptr = string_sprintf("cannot use \"control=%s\" in %s ACL",
- arg, acl_wherenames[where]);
- return CONTROL_ERROR;
- }
-
*pptr = arg + len;
return d->value;
}
uschar *log_message = NULL;
uschar *p;
int rc = OK;
+#ifdef WITH_CONTENT_SCAN
+int sep = '/';
+#endif
for (; cb != NULL; cb = cb->next)
{
uschar *arg;
+ int control_type;
/* The message and log_message items set up messages to be used in
case of rejection. They are expanded later. */
TRUE, NULL);
break;
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+ case ACLC_BMI_OPTIN:
+ {
+ int old_pool = store_pool;
+ store_pool = POOL_PERM;
+ bmi_current_optin = string_copy(arg);
+ store_pool = old_pool;
+ }
+ break;
+#endif
+
case ACLC_CONDITION:
if (Ustrspn(arg, "0123456789") == Ustrlen(arg)) /* Digits, or empty */
rc = (Uatoi(arg) == 0)? FAIL : OK;
break;
case ACLC_CONTROL:
- switch (decode_control(arg, &p, where, log_msgptr))
+ control_type = decode_control(arg, &p, where, log_msgptr);
+
+ /* Check if this control makes sense at this time */
+
+ if ((control_forbids[control_type] & (1 << where)) != 0)
+ {
+ *log_msgptr = string_sprintf("cannot use \"control=%s\" in %s ACL",
+ controls[control_type], acl_wherenames[where]);
+ return ERROR;
+ }
+
+ switch(control_type)
{
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+ case CONTROL_BMI_RUN:
+ bmi_run = 1;
+ break;
+#endif
+#ifdef EXPERIMENTAL_DOMAINKEYS
+ case CONTROL_DK_VERIFY:
+ dk_do_verify = 1;
+ break;
+#endif
case CONTROL_ERROR:
return ERROR;
smtp_enforce_sync = FALSE;
break;
+#ifdef WITH_CONTENT_SCAN
+ case CONTROL_NO_MBOX_UNSPOOL:
+ no_mbox_unspool = TRUE;
+ break;
+#endif
+
case CONTROL_NO_MULTILINE:
no_multiline_responses = TRUE;
break;
+ case CONTROL_FAKEDEFER:
+ case CONTROL_FAKEREJECT:
+ fake_response = (control_type == CONTROL_FAKEDEFER) ? DEFER : FAIL;
+ if (*p == '/')
+ {
+ uschar *pp = p + 1;
+ while (*pp != 0) pp++;
+ fake_response_text = expand_string(string_copyn(p+1, pp-p-1));
+ p = pp;
+ }
+ else
+ {
+ /* Explicitly reset to default string */
+ fake_response_text = US"Your message has been rejected but is being kept for evaluation.\nIf it was a legitimate message, it may still be delivered to the target recipient(s).";
+ }
+ break;
+
case CONTROL_FREEZE:
deliver_freeze = TRUE;
deliver_frozen_at = time(NULL);
break;
case CONTROL_SUBMISSION:
+ originator_name = US"";
submission_mode = TRUE;
- if (Ustrncmp(p, "/domain=", 8) == 0)
+ while (*p == '/')
{
- submission_domain = string_copy(p+8);
+ if (Ustrncmp(p, "/sender_retain", 14) == 0)
+ {
+ p += 14;
+ active_local_sender_retain = TRUE;
+ active_local_from_check = FALSE;
+ }
+ else if (Ustrncmp(p, "/domain=", 8) == 0)
+ {
+ uschar *pp = p + 8;
+ while (*pp != 0 && *pp != '/') pp++;
+ submission_domain = string_copyn(p+8, pp-p-8);
+ p = pp;
+ }
+ else if (Ustrncmp(p, "/name=", 6) == 0)
+ {
+ uschar *pp = p + 6;
+ while (*pp != 0 && *pp != '/') pp++;
+ originator_name = string_copy(parse_fix_phrase(p+6, pp-p-6,
+ big_buffer, big_buffer_size));
+ p = pp;
+ }
+ else break;
}
- else if (*p != 0)
+ if (*p != 0)
{
- *log_msgptr = string_sprintf("syntax error in argument for "
- "\"control\" modifier \"%s\"", arg);
+ *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg);
return ERROR;
}
break;
}
break;
+#ifdef WITH_CONTENT_SCAN
+ case ACLC_DECODE:
+ rc = mime_decode(&arg);
+ break;
+#endif
+
case ACLC_DELAY:
{
int delay = readconf_readtime(arg, 0, FALSE);
HDEBUG(D_acl)
debug_printf("delay skipped in -bh checking mode\n");
}
- else sleep(delay);
+
+ /* It appears to be impossible to detect that a TCP/IP connection has
+ gone away without reading from it. This means that we cannot shorten
+ the delay below if the client goes away, because we cannot discover
+ that the client has closed its end of the connection. (The connection
+ is actually in a half-closed state, waiting for the server to close its
+ end.) It would be nice to be able to detect this state, so that the
+ Exim process is not held up unnecessarily. However, it seems that we
+ can't. The poll() function does not do the right thing, and in any case
+ it is not always available.
+
+ NOTE: If ever this state of affairs changes, remember that we may be
+ dealing with stdin/stdout here, in addition to TCP/IP connections.
+ Whatever is done must work in both cases. To detected the stdin/stdout
+ case, check for smtp_in or smtp_out being NULL. */
+
+ else
+ {
+ while (delay > 0) delay = sleep(delay);
+ }
}
}
break;
+#ifdef WITH_OLD_DEMIME
+ case ACLC_DEMIME:
+ rc = demime(&arg);
+ break;
+#endif
+
+#ifdef EXPERIMENTAL_DOMAINKEYS
+ case ACLC_DK_DOMAIN_SOURCE:
+ if (dk_verify_block == NULL) { rc = FAIL; break; };
+ /* check header source of domain against given string */
+ switch (dk_verify_block->address_source) {
+ case DK_EXIM_ADDRESS_FROM_FROM:
+ rc = match_isinlist(US"from", &arg, 0, NULL,
+ NULL, MCL_STRING, TRUE, NULL);
+ break;
+ case DK_EXIM_ADDRESS_FROM_SENDER:
+ rc = match_isinlist(US"sender", &arg, 0, NULL,
+ NULL, MCL_STRING, TRUE, NULL);
+ break;
+ case DK_EXIM_ADDRESS_NONE:
+ rc = match_isinlist(US"none", &arg, 0, NULL,
+ NULL, MCL_STRING, TRUE, NULL);
+ break;
+ }
+ break;
+ case ACLC_DK_POLICY:
+ if (dk_verify_block == NULL) { rc = FAIL; break; };
+ /* check policy against given string, default FAIL */
+ rc = FAIL;
+ if (dk_verify_block->signsall)
+ rc = match_isinlist(US"signsall", &arg, 0, NULL,
+ NULL, MCL_STRING, TRUE, NULL);
+ if (dk_verify_block->testing)
+ rc = match_isinlist(US"testing", &arg, 0, NULL,
+ NULL, MCL_STRING, TRUE, NULL);
+ break;
+ case ACLC_DK_SENDER_DOMAINS:
+ if (dk_verify_block == NULL) { rc = FAIL; break; };
+ if (dk_verify_block->domain != NULL)
+ rc = match_isinlist(dk_verify_block->domain, &arg, 0, &domainlist_anchor,
+ NULL, MCL_DOMAIN, TRUE, NULL);
+ else rc = FAIL;
+ break;
+ case ACLC_DK_SENDER_LOCAL_PARTS:
+ if (dk_verify_block == NULL) { rc = FAIL; break; };
+ if (dk_verify_block->local_part != NULL)
+ rc = match_isinlist(dk_verify_block->local_part, &arg, 0, &localpartlist_anchor,
+ NULL, MCL_LOCALPART, TRUE, NULL);
+ else rc = FAIL;
+ break;
+ case ACLC_DK_SENDERS:
+ if (dk_verify_block == NULL) { rc = FAIL; break; };
+ if (dk_verify_block->address != NULL)
+ rc = match_address_list(dk_verify_block->address, TRUE, TRUE, &arg, NULL, -1, 0, NULL);
+ else rc = FAIL;
+ break;
+ case ACLC_DK_STATUS:
+ if (dk_verify_block == NULL) { rc = FAIL; break; };
+ if (dk_verify_block->result > 0) {
+ switch(dk_verify_block->result) {
+ case DK_EXIM_RESULT_BAD_FORMAT:
+ rc = match_isinlist(US"bad format", &arg, 0, NULL,
+ NULL, MCL_STRING, TRUE, NULL);
+ break;
+ case DK_EXIM_RESULT_NO_KEY:
+ rc = match_isinlist(US"no key", &arg, 0, NULL,
+ NULL, MCL_STRING, TRUE, NULL);
+ break;
+ case DK_EXIM_RESULT_NO_SIGNATURE:
+ rc = match_isinlist(US"no signature", &arg, 0, NULL,
+ NULL, MCL_STRING, TRUE, NULL);
+ break;
+ case DK_EXIM_RESULT_REVOKED:
+ rc = match_isinlist(US"revoked", &arg, 0, NULL,
+ NULL, MCL_STRING, TRUE, NULL);
+ break;
+ case DK_EXIM_RESULT_NON_PARTICIPANT:
+ rc = match_isinlist(US"non-participant", &arg, 0, NULL,
+ NULL, MCL_STRING, TRUE, NULL);
+ break;
+ case DK_EXIM_RESULT_GOOD:
+ rc = match_isinlist(US"good", &arg, 0, NULL,
+ NULL, MCL_STRING, TRUE, NULL);
+ break;
+ case DK_EXIM_RESULT_BAD:
+ rc = match_isinlist(US"bad", &arg, 0, NULL,
+ NULL, MCL_STRING, TRUE, NULL);
+ break;
+ }
+ }
+ break;
+#endif
+
case ACLC_DNSLISTS:
rc = verify_check_dnsbl(&arg);
break;
}
break;
+#ifdef WITH_CONTENT_SCAN
+ case ACLC_MALWARE:
+ {
+ /* Seperate the regular expression and any optional parameters. */
+ uschar *ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size);
+ /* Run the malware backend. */
+ rc = malware(&ss);
+ /* Modify return code based upon the existance of options. */
+ while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size))
+ != NULL) {
+ if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER)
+ {
+ /* FAIL so that the message is passed to the next ACL */
+ rc = FAIL;
+ }
+ }
+ }
+ break;
+
+ case ACLC_MIME_REGEX:
+ rc = mime_regex(&arg);
+ break;
+#endif
+
case ACLC_RECIPIENTS:
rc = match_address_list(addr->address, TRUE, TRUE, &arg, NULL, -1, 0,
&recipient_data);
break;
+#ifdef WITH_CONTENT_SCAN
+ case ACLC_REGEX:
+ rc = regex(&arg);
+ break;
+#endif
+
case ACLC_SENDER_DOMAINS:
{
uschar *sdomain;
}
break;
+#ifdef WITH_CONTENT_SCAN
+ case ACLC_SPAM:
+ {
+ /* Seperate the regular expression and any optional parameters. */
+ uschar *ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size);
+ /* Run the spam backend. */
+ rc = spam(&ss);
+ /* Modify return code based upon the existance of options. */
+ while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size))
+ != NULL) {
+ if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER)
+ {
+ /* FAIL so that the message is passed to the next ACL */
+ rc = FAIL;
+ }
+ }
+ }
+ break;
+#endif
+
+#ifdef EXPERIMENTAL_SPF
+ case ACLC_SPF:
+ rc = spf_process(&arg, sender_address);
+ break;
+#endif
+
/* 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. */
+ only from explicit "message" modifiers. However, put the user message into
+ $acl_verify_message so it can be used in subsequent conditions or modifiers
+ (until something changes it). */
case ACLC_VERIFY:
rc = acl_verify(where, addr, arg, user_msgptr, log_msgptr, basic_errno);
+ acl_verify_message = *user_msgptr;
if (verb == ACL_WARN) *user_msgptr = NULL;
break;
if (cond == OK)
acl_warn(where, *user_msgptr, *log_msgptr);
else if (cond == DEFER)
- acl_warn(where, NULL, string_sprintf("ACL \"warn\" statement skipped: "
- "condition test deferred: %s",
- (*log_msgptr == NULL)? US"" : *log_msgptr));
+ log_write(0, LOG_MAIN, "%s Warning: ACL \"warn\" statement skipped: "
+ "condition test deferred%s%s", host_and_ident(TRUE),
+ (*log_msgptr == NULL)? US"" : US": ",
+ (*log_msgptr == NULL)? US"" : *log_msgptr);
*log_msgptr = *user_msgptr = NULL; /* In case implicit DENY follows */
break;
rc = acl_check_internal(where, addr, s, 0, user_msgptr, log_msgptr);
smtp_command_argument = deliver_domain =
- deliver_localpart = deliver_address_data = NULL;
+ deliver_localpart = deliver_address_data = sender_address_data = NULL;
/* A DISCARD response is permitted only for message ACLs, excluding the PREDATA
ACL, which is really in the middle of an SMTP command. */