ACLC_DKIM_SIGNER,
ACLC_DKIM_STATUS,
#endif
-#ifdef EXPERIMENTAL_DMARC
+#ifdef SUPPORT_DMARC
ACLC_DMARC_STATUS,
#endif
ACLC_DNSLISTS,
[ACLC_DKIM_SIGNER] = { US"dkim_signers", TRUE, FALSE, (unsigned int) ~ACL_BIT_DKIM },
[ACLC_DKIM_STATUS] = { US"dkim_status", TRUE, FALSE, (unsigned int) ~ACL_BIT_DKIM },
#endif
-#ifdef EXPERIMENTAL_DMARC
+#ifdef SUPPORT_DMARC
[ACLC_DMARC_STATUS] = { US"dmarc_status", TRUE, FALSE, (unsigned int) ~ACL_BIT_DATA },
#endif
#ifndef DISABLE_DKIM
CONTROL_DKIM_VERIFY,
#endif
-#ifdef EXPERIMENTAL_DMARC
+#ifdef SUPPORT_DMARC
CONTROL_DMARC_VERIFY,
CONTROL_DMARC_FORENSIC,
#endif
CONTROL_NO_PIPELINING,
CONTROL_QUEUE_ONLY,
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
- CONTROL_REQUIRETLS,
-#endif
CONTROL_SUBMISSION,
CONTROL_SUPPRESS_LOCAL_FIXUPS,
#ifdef SUPPORT_I18N
},
#endif
-#ifdef EXPERIMENTAL_DMARC
+#ifdef SUPPORT_DMARC
[CONTROL_DMARC_VERIFY] =
{ US"dmarc_disable_verify", FALSE,
ACL_BIT_DATA | ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START
},
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
-[CONTROL_REQUIRETLS] =
- { US"requiretls", FALSE,
- (unsigned)
- ~(ACL_BIT_MAIL | ACL_BIT_RCPT | ACL_BIT_PREDATA |
- ACL_BIT_DATA | ACL_BIT_MIME |
- ACL_BIT_NOTSMTP)
- },
-#endif
-
[CONTROL_SUBMISSION] =
{ US"submission", TRUE,
(unsigned)
*error = string_sprintf("malformed ACL line \"%s\"", saveline);
return NULL;
}
- this = store_get(sizeof(acl_block));
+ this = store_get(sizeof(acl_block), FALSE);
*lastp = this;
lastp = &(this->next);
this->next = NULL;
return NULL;
}
- cond = store_get(sizeof(acl_condition_block));
+ cond = store_get(sizeof(acl_condition_block), FALSE);
cond->next = NULL;
cond->type = c;
cond->u.negated = negated;
if (!*hptr)
{
- header_line *h = store_get(sizeof(header_line));
+ /* The header_line struct itself is not tainted, though it points to
+ tainted data. */
+ header_line *h = store_get(sizeof(header_line), FALSE);
h->text = hdr;
h->next = NULL;
h->type = newtype;
tree_node *t;
const uschar *found;
int priority, weight, port;
-dns_answer dnsa;
+dns_answer * dnsa = store_get_dns_answer();
dns_scan dnss;
dns_record *rr;
int rc, type;
t = tree_search(csa_cache, domain);
if (t != NULL) return t->data.val;
-t = store_get_perm(sizeof(tree_node) + Ustrlen(domain));
+t = store_get_perm(sizeof(tree_node) + Ustrlen(domain), is_tainted(domain));
Ustrcpy(t->name, domain);
(void)tree_insertnode(&csa_cache, t);
/* Now we are ready to do the actual DNS lookup(s). */
found = domain;
-switch (dns_special_lookup(&dnsa, domain, T_CSA, &found))
+switch (dns_special_lookup(dnsa, domain, T_CSA, &found))
{
/* If something bad happened (most commonly DNS_AGAIN), defer. */
/* Scan the reply for well-formed CSA SRV records. */
-for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS);
+for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
rr;
- rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT)) if (rr->type == T_SRV)
+ rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == T_SRV)
{
const uschar * p = rr->data;
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,
+ (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, p,
(DN_EXPAND_ARG4_TYPE)target, sizeof(target));
DEBUG(D_acl) debug_printf_indent("CSA target is %s\n", target);
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);
+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. */
lookup_dnssec_authenticated = NULL;
-switch (dns_lookup(&dnsa, target, type, NULL))
+switch (dns_lookup(dnsa, target, type, NULL))
{
/* If something bad happened (most commonly DNS_AGAIN), defer. */
/* If the query succeeded, scan the addresses and return the result. */
case DNS_SUCCEED:
- rc = acl_verify_csa_address(&dnsa, &dnss, RESET_ANSWERS, target);
+ rc = acl_verify_csa_address(dnsa, &dnss, RESET_ANSWERS, target);
if (rc != CSA_FAIL_NOADDR) return t->data.val = rc;
/* else fall through */
unsigned alt_opt_sep; /* >0 Non-/ option separator (custom parser) */
} verify_type_t;
static verify_type_t verify_type_list[] = {
- /* name value where no-opt opt-sep */
- { US"reverse_host_lookup", VERIFY_REV_HOST_LKUP, ~0, FALSE, 0 },
- { US"certificate", VERIFY_CERT, ~0, TRUE, 0 },
- { US"helo", VERIFY_HELO, ~0, TRUE, 0 },
- { US"csa", VERIFY_CSA, ~0, FALSE, 0 },
+ /* name value where no-opt opt-sep */
+ { US"reverse_host_lookup", VERIFY_REV_HOST_LKUP, (unsigned)~0, FALSE, 0 },
+ { 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, 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"sender", VERIFY_SNDR, ACL_BIT_MAIL | ACL_BIT_RCPT
|ACL_BIT_PREDATA | ACL_BIT_DATA | ACL_BIT_NOTSMTP,
case VERIFY_NOT_BLIND:
/* Check that no recipient of this message is "blind", that is, every envelope
recipient must be mentioned in either To: or Cc:. */
+ {
+ BOOL case_sensitive = TRUE;
+
+ while ((ss = string_nextinlist(&list, &sep, NULL, 0)))
+ if (strcmpic(ss, US"case_insensitive") == 0)
+ case_sensitive = FALSE;
+ else
+ {
+ *log_msgptr = string_sprintf("unknown option \"%s\" in ACL "
+ "condition \"verify %s\"", ss, arg);
+ return ERROR;
+ }
- if ((rc = verify_check_notblind()) != OK)
+ if ((rc = verify_check_notblind(case_sensitive)) != OK)
{
- *log_msgptr = string_sprintf("bcc recipient detected");
+ *log_msgptr = US"bcc recipient detected";
if (smtp_return_error_details)
*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
string_cat(NULL, US"error in arguments to \"ratelimit\" condition: ");
va_start(ap, format);
-g = string_vformat(g, TRUE, format, ap);
+g = string_vformat(g, SVFMT_EXTEND|SVFMT_REBUFFER, format, ap);
va_end(ap);
-gstring_reset_unused(g);
+gstring_release_unused(g);
*log_msgptr = string_from_gstring(g);
return ERROR;
}
/* We aren't using a pre-computed rate, so get a previously recorded rate
from the database, which will be updated and written back if required. */
-if (!(dbm = dbfn_open(US"ratelimit", O_RDWR, &dbblock, TRUE)))
+if (!(dbm = dbfn_open(US"ratelimit", O_RDWR, &dbblock, TRUE, TRUE)))
{
store_pool = old_pool;
sender_rate = NULL;
/* No Bloom filter. This basic ratelimit block is initialized below. */
HDEBUG(D_acl) debug_printf_indent("ratelimit creating new rate data block\n");
dbdb_size = sizeof(*dbd);
- dbdb = store_get(dbdb_size);
+ dbdb = store_get(dbdb_size, FALSE); /* not tainted */
}
else
{
extra = (int)limit * 2 - sizeof(dbdb->bloom);
if (extra < 0) extra = 0;
dbdb_size = sizeof(*dbdb) + extra;
- dbdb = store_get(dbdb_size);
+ dbdb = store_get(dbdb_size, FALSE); /* not tainted */
dbdb->bloom_epoch = tv.tv_sec;
dbdb->bloom_size = sizeof(dbdb->bloom) + extra;
memset(dbdb->bloom, 0, dbdb->bloom_size);
dbfn_close(dbm);
-/* Store the result in the tree for future reference. */
+/* Store the result in the tree for future reference. Take the taint status
+from the key for consistency even though it's unlikely we'll ever expand this. */
-t = store_get(sizeof(tree_node) + Ustrlen(key));
+t = store_get(sizeof(tree_node) + Ustrlen(key), is_tainted(key));
t->data.ptr = dbd;
Ustrcpy(t->name, key);
(void)tree_insertnode(anchor, t);
}
/* Make a single-item host list. */
-h = store_get(sizeof(host_item));
+h = store_get(sizeof(host_item), FALSE);
memset(h, 0, sizeof(host_item));
h->name = hostname;
h->port = portnum;
break;
#endif
- #ifndef DISABLE_DKIM
+#ifndef DISABLE_DKIM
case CONTROL_DKIM_VERIFY:
f.dkim_disable_verify = TRUE;
- #ifdef EXPERIMENTAL_DMARC
+# ifdef SUPPORT_DMARC
/* Since DKIM was blocked, skip DMARC too */
f.dmarc_disable_verify = TRUE;
f.dmarc_enable_forensic = FALSE;
- #endif
+# endif
break;
- #endif
+#endif
- #ifdef EXPERIMENTAL_DMARC
+#ifdef SUPPORT_DMARC
case CONTROL_DMARC_VERIFY:
f.dmarc_disable_verify = TRUE;
break;
case CONTROL_DMARC_FORENSIC:
f.dmarc_enable_forensic = TRUE;
break;
- #endif
+#endif
case CONTROL_DSCP:
if (*p == '/')
cancel_cutthrough_connection(TRUE, US"queueing forced");
break;
-#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS)
- case CONTROL_REQUIRETLS:
- tls_requiretls |= REQUIRETLS_MSG;
- break;
-#endif
case CONTROL_SUBMISSION:
originator_name = US"";
f.submission_mode = TRUE;
break;
#endif
- #ifdef EXPERIMENTAL_DMARC
+#ifdef SUPPORT_DMARC
case ACLC_DMARC_STATUS:
if (!f.dmarc_has_been_checked)
dmarc_process();
rc = match_isinlist(dmarc_exim_expand_query(DMARC_VERIFY_STATUS),
&arg,0,NULL,NULL,MCL_STRING,TRUE,NULL);
break;
- #endif
+#endif
case ACLC_DNSLISTS:
rc = verify_check_dnsbl(where, &arg, log_msgptr);
(sender_host_address == NULL)? US"" : sender_host_address,
CUSS &host_data);
if (rc == DEFER) *log_msgptr = search_error_message;
- if (host_data) host_data = string_copy_malloc(host_data);
+ if (host_data) host_data = string_copy_perm(host_data, TRUE);
break;
case ACLC_LOCAL_PARTS:
"Directory separator not permitted in queue name: '%s'", arg);
return ERROR;
}
- queue_name = string_copy_malloc(arg);
+ queue_name = string_copy_perm(arg, FALSE);
break;
case ACLC_RATELIMIT:
else if (*ss == '/')
{
struct stat statbuf;
+ if (is_tainted(ss))
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "attempt to open tainted ACL file name \"%s\"", ss);
+ /* Avoid leaking info to an attacker */
+ *log_msgptr = US"internal configuration error";
+ return ERROR;
+ }
if ((fd = Uopen(ss, O_RDONLY, 0)) < 0)
{
*log_msgptr = string_sprintf("failed to open ACL file \"%s\": %s", ss,
strerror(errno));
return ERROR;
}
-
if (fstat(fd, &statbuf) != 0)
{
*log_msgptr = string_sprintf("failed to fstat ACL file \"%s\": %s", ss,
return ERROR;
}
- acl_text = store_get(statbuf.st_size + 1);
+ /* If the string being used as a filename is tainted, so is the file content */
+ acl_text = store_get(statbuf.st_size + 1, is_tainted(ss));
acl_text_end = acl_text + statbuf.st_size + 1;
if (read(fd, acl_text, statbuf.st_size) != statbuf.st_size)
if (!acl && *log_msgptr) return ERROR;
if (fd >= 0)
{
- tree_node *t = store_get_perm(sizeof(tree_node) + Ustrlen(ss));
+ tree_node *t = store_get_perm(sizeof(tree_node) + Ustrlen(ss), is_tainted(ss));
Ustrcpy(t->name, ss);
t->data.ptr = acl;
(void)tree_insertnode(&acl_anchor, t);
tree_node * node, ** root = name[0] == 'c' ? &acl_var_c : &acl_var_m;
if (!(node = tree_search(*root, name)))
{
- node = store_get(sizeof(tree_node) + Ustrlen(name));
+ node = store_get(sizeof(tree_node) + Ustrlen(name), is_tainted(name));
Ustrcpy(node->name, name);
(void)tree_insertnode(root, node);
}
acl_var_write(uschar *name, uschar *value, void *ctx)
{
FILE *f = (FILE *)ctx;
+if (is_tainted(value)) putc('-', f);
fprintf(f, "-acl%c %s %d\n%s\n", name[0], name+1, Ustrlen(value), value);
}