*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
/* See the file NOTICE for conditions of use and distribution. */
/* Code for handling Access Control Lists (ACLs) */
#define CALLOUT_TIMEOUT_DEFAULT 30
+/* Default quota cache TTLs */
+
+#define QUOTA_POS_DEFAULT (5*60)
+#define QUOTA_NEG_DEFAULT (60*60)
+
+
/* ACL verb codes - keep in step with the table of verbs that follows */
enum { ACL_ACCEPT, ACL_DEFER, ACL_DENY, ACL_DISCARD, ACL_DROP, ACL_REQUIRE,
{
int rc;
-user_msgptr = user_msgptr; /* stop compiler warning */
-
/* Previous success */
if (sender_host_name != NULL) return OK;
/* If something bad happened (most commonly DNS_AGAIN), defer. */
default:
- return t->data.val = CSA_DEFER_SRV;
+ 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;
+ return t->data.val = CSA_UNKNOWN;
/* We got something! Go on to look at the reply in more detail. */
case DNS_SUCCEED:
- break;
+ break;
}
/* Scan the reply for well-formed CSA SRV records. */
{ US"certificate", VERIFY_CERT, (unsigned)~0, TRUE, 0 },
{ US"helo", VERIFY_HELO, (unsigned)~0, TRUE, 0 },
{ US"csa", VERIFY_CSA, (unsigned)~0, FALSE, 0 },
- { US"header_syntax", VERIFY_HDR_SYNTAX, ACL_BIT_DATA | ACL_BIT_NOTSMTP, TRUE, 0 },
- { US"not_blind", VERIFY_NOT_BLIND, ACL_BIT_DATA | ACL_BIT_NOTSMTP, FALSE, 0 },
- { US"header_sender", VERIFY_HDR_SNDR, ACL_BIT_DATA | ACL_BIT_NOTSMTP, FALSE, 0 },
+ { US"header_syntax", VERIFY_HDR_SYNTAX, ACL_BITS_HAVEDATA, TRUE, 0 },
+ { US"not_blind", VERIFY_NOT_BLIND, ACL_BITS_HAVEDATA, FALSE, 0 },
+ { US"header_sender", VERIFY_HDR_SNDR, ACL_BITS_HAVEDATA, FALSE, 0 },
{ US"sender", VERIFY_SNDR, ACL_BIT_MAIL | ACL_BIT_RCPT
- |ACL_BIT_PREDATA | ACL_BIT_DATA | ACL_BIT_NOTSMTP,
+ | ACL_BIT_PREDATA | ACL_BIT_DATA | ACL_BIT_NOTSMTP,
FALSE, 6 },
{ US"recipient", VERIFY_RCPT, ACL_BIT_RCPT, FALSE, 0 },
- { US"header_names_ascii", VERIFY_HDR_NAMES_ASCII, ACL_BIT_DATA | ACL_BIT_NOTSMTP, TRUE, 0 },
+ { US"header_names_ascii", VERIFY_HDR_NAMES_ASCII, ACL_BITS_HAVEDATA, TRUE, 0 },
#ifdef EXPERIMENTAL_ARC
{ US"arc", VERIFY_ARC, ACL_BIT_DATA, FALSE , 0 },
#endif
+static int
+v_period(const uschar * s, const uschar * arg, uschar ** log_msgptr)
+{
+int period;
+if ((period = readconf_readtime(s, 0, FALSE)) < 0)
+ {
+ *log_msgptr = string_sprintf("bad time value in ACL condition "
+ "\"verify %s\"", arg);
+ }
+return period;
+}
+
+
+
/* This function implements the "verify" condition. It is called when
encountered in any ACL, because some tests are almost always permitted. Some
just don't make sense, and always fail (for example, an attempt to test a host
BOOL callout_defer_ok = FALSE;
BOOL no_details = FALSE;
BOOL success_on_redirect = FALSE;
+BOOL quota = FALSE;
+int quota_pos_cache = QUOTA_POS_DEFAULT, quota_neg_cache = QUOTA_NEG_DEFAULT;
address_item *sender_vaddr = NULL;
uschar *verify_sender_address = NULL;
uschar *pm_mailfrom = NULL;
in place of the actual sender (rare special-case requirement). */
{
uschar *s = ss + 6;
- if (*s == 0)
+ if (!*s)
verify_sender_address = sender_address;
else
{
/* 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)))
+while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
{
if (strcmpic(ss, US"defer_ok") == 0) defer_ok = TRUE;
else if (strcmpic(ss, US"no_details") == 0) no_details = TRUE;
else if (strncmpic(ss, US"callout", 7) == 0)
{
callout = CALLOUT_TIMEOUT_DEFAULT;
- ss += 7;
- if (*ss != 0)
+ if (*(ss += 7))
{
while (isspace(*ss)) ss++;
if (*ss++ == '=')
{
const uschar * sublist = ss;
int optsep = ',';
- uschar buffer[256];
- uschar * opt;
while (isspace(*sublist)) sublist++;
- while ((opt = string_nextinlist(&sublist, &optsep, buffer, sizeof(buffer))))
+ for (uschar * opt; opt = string_nextinlist(&sublist, &optsep, NULL, 0); )
{
callout_opt_t * op;
double period = 1.0F;
}
while (isspace(*opt)) opt++;
}
- if (op->timeval && (period = readconf_readtime(opt, 0, FALSE)) < 0)
- {
- *log_msgptr = string_sprintf("bad time value in ACL condition "
- "\"verify %s\"", arg);
+ if (op->timeval && (period = v_period(opt, arg, log_msgptr)) < 0)
return ERROR;
- }
switch(op->value)
{
}
}
+ /* The quota option has sub-options, comma-separated */
+
+ else if (strncmpic(ss, US"quota", 5) == 0)
+ {
+ quota = TRUE;
+ if (*(ss += 5))
+ {
+ while (isspace(*ss)) ss++;
+ if (*ss++ == '=')
+ {
+ const uschar * sublist = ss;
+ int optsep = ',';
+ int period;
+
+ while (isspace(*sublist)) sublist++;
+ for (uschar * opt; opt = string_nextinlist(&sublist, &optsep, NULL, 0); )
+ if (Ustrncmp(opt, "cachepos=", 9) == 0)
+ if ((period = v_period(opt += 9, arg, log_msgptr)) < 0)
+ return ERROR;
+ else
+ quota_pos_cache = period;
+ else if (Ustrncmp(opt, "cacheneg=", 9) == 0)
+ if ((period = v_period(opt += 9, arg, log_msgptr)) < 0)
+ return ERROR;
+ else
+ quota_neg_cache = period;
+ else if (Ustrcmp(opt, "no_cache") == 0)
+ quota_pos_cache = quota_neg_cache = 0;
+ }
+ }
+ }
+
/* Option not recognized */
else
return ERROR;
}
+/* Handle quota verification */
+if (quota)
+ {
+ if (vp->value != VERIFY_RCPT)
+ {
+ *log_msgptr = US"can only verify quota of recipient";
+ return ERROR;
+ }
+
+ if ((rc = verify_quota_call(addr->address,
+ quota_pos_cache, quota_neg_cache, log_msgptr)) != OK)
+ {
+ *basic_errno = errno;
+ if (smtp_return_error_details)
+ {
+ if (!*user_msgptr && *log_msgptr)
+ *user_msgptr = string_sprintf("Rejected after %s: %s",
+ smtp_names[smtp_connection_had[SMTP_HBUFF_PREV(smtp_ch_index)]],
+ *log_msgptr);
+ if (rc == DEFER) f.acl_temp_details = TRUE;
+ }
+ }
+
+ return rc;
+ }
+
/* Handle sender-in-header verification. Default the user message to the log
message if giving out verification details. */
}
sender_vaddr = verify_checked_sender(verify_sender_address);
- if (sender_vaddr != NULL && /* Previously checked */
- callout <= 0) /* No callout needed this time */
+ if ( sender_vaddr /* Previously checked */
+ && callout <= 0) /* No callout needed this time */
{
/* If the "routed" flag is set, it means that routing worked before, so
this check can give OK (the saved return code value, if set, belongs to a
*basic_errno = sender_vaddr->basic_errno;
else
DEBUG(D_acl)
- {
if (Ustrcmp(sender_vaddr->address, verify_sender_address) != 0)
debug_printf_indent("sender %s verified ok as %s\n",
verify_sender_address, sender_vaddr->address);
else
debug_printf_indent("sender %s verified ok\n",
verify_sender_address);
- }
}
else
rc = OK; /* Null sender */
*basic_errno = addr2.basic_errno;
*log_msgptr = addr2.message;
- *user_msgptr = (addr2.user_message != NULL)?
- addr2.user_message : addr2.message;
+ *user_msgptr = addr2.user_message ? addr2.user_message : addr2.message;
/* Allow details for temporary error if the address is so flagged. */
if (testflag((&addr2), af_pass_message)) f.acl_temp_details = TRUE;
/* We have a result from the relevant test. Handle defer overrides first. */
-if (rc == DEFER && (defer_ok ||
- (callout_defer_ok && *basic_errno == ERRNO_CALLOUTDEFER)))
+if ( rc == DEFER
+ && ( defer_ok
+ || callout_defer_ok && *basic_errno == ERRNO_CALLOUTDEFER
+ ) )
{
HDEBUG(D_acl) debug_printf_indent("verify defer overridden by %s\n",
defer_ok? "defer_ok" : "callout_defer_ok");
/* If we've failed a sender, set up a recipient message, and point
sender_verified_failed to the address item that actually failed. */
-if (rc != OK && verify_sender_address != NULL)
+if (rc != OK && verify_sender_address)
{
if (rc != DEFER)
*log_msgptr = *user_msgptr = US"Sender verify failed";
/* Verifying an address messes up the values of $domain and $local_part,
so reset them before returning if this is a RCPT ACL. */
-if (addr != NULL)
+if (addr)
{
deliver_domain = addr->domain;
deliver_localpart = addr->local_part;
/* Parse the other options. */
-while ((ss = string_nextinlist(&arg, &sep, big_buffer, big_buffer_size)))
+while ((ss = string_nextinlist(&arg, &sep, NULL, 0)))
{
if (strcmpic(ss, US"leaky") == 0) leaky = TRUE;
else if (strcmpic(ss, US"strict") == 0) strict = TRUE;
{
const uschar *pp = p + 6;
while (*pp) pp++;
- submission_name = string_copy(parse_fix_phrase(p+6, pp-p-6,
- big_buffer, big_buffer_size));
+ submission_name = parse_fix_phrase(p+6, pp-p-6);
p = pp;
}
else break;
the case where both sides handle prdr and this-node prdr acl
is "accept" */
ignored = US"PRDR active";
+ else if (f.deliver_freeze)
+ ignored = US"frozen";
+ else if (f.queue_only_policy)
+ ignored = US"queue-only";
+ else if (fake_response == FAIL)
+ ignored = US"fakereject";
+ else if (rcpt_count != 1)
+ ignored = US"nonfirst rcpt";
+ else if (cutthrough.delivery)
+ ignored = US"repeated";
+ else if (cutthrough.callout_hold_only)
+ {
+ DEBUG(D_acl)
+ debug_printf_indent(" cutthrough request upgrades callout hold\n");
+ cutthrough.callout_hold_only = FALSE;
+ cutthrough.delivery = TRUE; /* control accepted */
+ }
else
{
- if (f.deliver_freeze)
- ignored = US"frozen";
- else if (f.queue_only_policy)
- ignored = US"queue-only";
- else if (fake_response == FAIL)
- ignored = US"fakereject";
- else
+ cutthrough.delivery = TRUE; /* control accepted */
+ while (*p == '/')
{
- if (rcpt_count == 1)
+ const uschar * pp = p+1;
+ if (Ustrncmp(pp, "defer=", 6) == 0)
{
- cutthrough.delivery = TRUE; /* control accepted */
- while (*p == '/')
- {
- const uschar * pp = p+1;
- if (Ustrncmp(pp, "defer=", 6) == 0)
- {
- pp += 6;
- if (Ustrncmp(pp, "pass", 4) == 0) cutthrough.defer_pass = TRUE;
- /* else if (Ustrncmp(pp, "spool") == 0) ; default */
- }
- else
- while (*pp && *pp != '/') pp++;
- p = pp;
- }
+ pp += 6;
+ if (Ustrncmp(pp, "pass", 4) == 0) cutthrough.defer_pass = TRUE;
+ /* else if (Ustrncmp(pp, "spool") == 0) ; default */
}
else
- ignored = US"nonfirst rcpt";
+ while (*pp && *pp != '/') pp++;
+ p = pp;
}
}
+
DEBUG(D_acl) if (ignored)
debug_printf(" cutthrough request ignored on %s item\n", ignored);
}
{
/* Separate the regular expression and any optional parameters. */
const uschar * list = arg;
- uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
+ uschar *ss = string_nextinlist(&list, &sep, NULL, 0);
/* Run the dcc backend. */
rc = dcc_process(&ss);
/* Modify return code based upon the existence of options. */
- while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
+ while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER)
rc = FAIL; /* FAIL so that the message is passed to the next ACL */
}
case ACLC_DKIM_SIGNER:
if (dkim_cur_signer)
rc = match_isinlist(dkim_cur_signer,
- &arg,0,NULL,NULL,MCL_STRING,TRUE,NULL);
+ &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
else
rc = FAIL;
break;
case ACLC_DKIM_STATUS:
rc = match_isinlist(dkim_verify_status,
- &arg,0,NULL,NULL,MCL_STRING,TRUE,NULL);
+ &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
break;
#endif
/* 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);
+ &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL);
break;
#endif
int sep = 0;
const uschar *s = arg;
uschar * ss;
- while ((ss = string_nextinlist(&s, &sep, big_buffer, big_buffer_size)))
+ while ((ss = string_nextinlist(&s, &sep, NULL, 0)))
{
if (Ustrcmp(ss, "main") == 0) logbits |= LOG_MAIN;
else if (Ustrcmp(ss, "panic") == 0) logbits |= LOG_PANIC;
{
/* Separate the regular expression and any optional parameters. */
const uschar * list = arg;
- uschar * ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
+ uschar * ss = string_nextinlist(&list, &sep, NULL, 0);
uschar * opt;
BOOL defer_ok = FALSE;
int timeout = 0;
{
/* Separate the regular expression and any optional parameters. */
const uschar * list = arg;
- uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size);
+ uschar *ss = string_nextinlist(&list, &sep, NULL, 0);
rc = spam(CUSS &ss);
/* Modify return code based upon the existence of options. */
- while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size)))
+ while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER)
rc = FAIL; /* FAIL so that the message is passed to the next ACL */
}
/* Drop cutthrough conns, and drop heldopen verify conns if
the previous was not DATA */
{
- uschar prev = smtp_connection_had[smtp_ch_index-2];
+ uschar prev =
+ smtp_connection_had[SMTP_HBUFF_PREV(SMTP_HBUFF_PREV(smtp_ch_index))];
BOOL dropverify = !(prev == SCH_DATA || prev == SCH_BDAT);
cancel_cutthrough_connection(dropverify, US"quit or conndrop");