X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/9e160d8100cd15517a3444c1ad8ab81e51399582..c1cea16d93da8e47aa0d29e79d9b854cf2c50951:/src/src/acl.c diff --git a/src/src/acl.c b/src/src/acl.c index 8fbd2250f..74ec1ef33 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -70,7 +70,7 @@ enum { ACLC_ACL, ACLC_DKIM_SIGNER, ACLC_DKIM_STATUS, #endif -#ifdef EXPERIMENTAL_DMARC +#ifdef SUPPORT_DMARC ACLC_DMARC_STATUS, #endif ACLC_DNSLISTS, @@ -112,7 +112,8 @@ enum { ACLC_ACL, /* ACL conditions/modifiers: "delay", "control", "continue", "endpass", "message", "log_message", "log_reject_target", "logwrite", "queue" and "set" are modifiers that look like conditions but always return TRUE. They are used for -their side effects. */ +their side effects. Do not invent new modifier names that result in one name +being the prefix of another; the binary-search in the list will go wrong. */ typedef struct condition_def { uschar *name; @@ -192,7 +193,7 @@ static condition_def conditions[] = { [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 @@ -346,7 +347,7 @@ enum { #ifndef DISABLE_DKIM CONTROL_DKIM_VERIFY, #endif -#ifdef EXPERIMENTAL_DMARC +#ifdef SUPPORT_DMARC CONTROL_DMARC_VERIFY, CONTROL_DMARC_FORENSIC, #endif @@ -366,10 +367,7 @@ enum { CONTROL_NO_MULTILINE, CONTROL_NO_PIPELINING, - CONTROL_QUEUE_ONLY, -#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_REQUIRETLS) - CONTROL_REQUIRETLS, -#endif + CONTROL_QUEUE, CONTROL_SUBMISSION, CONTROL_SUPPRESS_LOCAL_FIXUPS, #ifdef SUPPORT_I18N @@ -420,7 +418,7 @@ static control_def controls_list[] = { }, #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 @@ -505,8 +503,8 @@ static control_def controls_list[] = { ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START }, -[CONTROL_QUEUE_ONLY] = - { US"queue_only", FALSE, +[CONTROL_QUEUE] = + { US"queue", TRUE, (unsigned) ~(ACL_BIT_MAIL | ACL_BIT_RCPT | ACL_BIT_PREDATA | ACL_BIT_DATA | @@ -514,17 +512,6 @@ static control_def controls_list[] = { ACL_BIT_NOTSMTP | ACL_BIT_MIME) }, - -#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) @@ -790,7 +777,7 @@ while ((s = (*func)()) != NULL) *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; @@ -837,7 +824,7 @@ while ((s = (*func)()) != 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; @@ -879,11 +866,10 @@ while ((s = (*func)()) != NULL) { uschar *endptr; - if (Ustrncmp(s, "acl_c", 5) != 0 && - Ustrncmp(s, "acl_m", 5) != 0) + if (Ustrncmp(s, "acl_c", 5) != 0 && Ustrncmp(s, "acl_m", 5) != 0) { *error = string_sprintf("invalid variable name after \"set\" in ACL " - "modifier \"set %s\" (must start \"acl_c\" or \"acl_m\")", s); + "modifier \"set %s\" (must start \"acl_c\" or \"acl_m\")", s); return NULL; } @@ -891,19 +877,19 @@ while ((s = (*func)()) != NULL) if (!isdigit(*endptr) && *endptr != '_') { *error = string_sprintf("invalid variable name after \"set\" in ACL " - "modifier \"set %s\" (digit or underscore must follow acl_c or acl_m)", - s); + "modifier \"set %s\" (digit or underscore must follow acl_c or acl_m)", + s); return NULL; } - while (*endptr != 0 && *endptr != '=' && !isspace(*endptr)) + while (*endptr && *endptr != '=' && !isspace(*endptr)) { if (!isalnum(*endptr) && *endptr != '_') - { - *error = string_sprintf("invalid character \"%c\" in variable name " - "in ACL modifier \"set %s\"", *endptr, s); - return NULL; - } + { + *error = string_sprintf("invalid character \"%c\" in variable name " + "in ACL modifier \"set %s\"", *endptr, s); + return NULL; + } endptr++; } @@ -1035,7 +1021,9 @@ for (p = q; *p; p = q) if (!*hptr) { - header_line *h = store_get(sizeof(header_line)); + /* The header_line struct itself is not tainted, though it points to + possibly tainted data. */ + header_line * h = store_get(sizeof(header_line), FALSE); h->text = hdr; h->next = NULL; h->type = newtype; @@ -1321,7 +1309,7 @@ acl_verify_csa(const uschar *domain) 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; @@ -1356,8 +1344,7 @@ extension to CSA, so we allow it to be turned off for proper conformance. */ if (string_is_ip_address(domain, NULL) != 0) { if (!dns_csa_use_reverse) return CSA_UNKNOWN; - dns_build_reverse(domain, target); - domain = target; + domain = dns_build_reverse(domain); } /* Find out if we've already done the CSA check for this domain. If we have, @@ -1368,14 +1355,14 @@ 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)); +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. */ @@ -1396,9 +1383,9 @@ switch (dns_special_lookup(&dnsa, domain, T_CSA, &found)) /* 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; @@ -1438,7 +1425,7 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS); 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); @@ -1463,7 +1450,7 @@ 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); +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. */ @@ -1477,7 +1464,7 @@ else 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. */ @@ -1487,7 +1474,7 @@ switch (dns_lookup(&dnsa, target, type, NULL)) /* 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 */ @@ -1519,13 +1506,13 @@ typedef struct { 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, @@ -1724,14 +1711,27 @@ switch(vp->value) 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 @@ -2113,7 +2113,9 @@ return ERROR; * Check argument for control= modifier * *************************************************/ -/* Called from acl_check_condition() below +/* Called from acl_check_condition() below. +To handle the case "queue_only" we accept an _ in the +initial / option-switch position. Arguments: arg the argument string for control= @@ -2129,10 +2131,11 @@ decode_control(const uschar *arg, const uschar **pptr, int where, uschar **log_m { int idx, len; control_def * d; +uschar c; if ( (idx = find_control(arg, controls_list, nelem(controls_list))) < 0 - || ( arg[len = Ustrlen((d = controls_list+idx)->name)] != 0 - && (!d->has_option || arg[len] != '/') + || ( (c = arg[len = Ustrlen((d = controls_list+idx)->name)]) != 0 + && (!d->has_option || c != '/' && c != '_') ) ) { *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg); @@ -2168,10 +2171,10 @@ gstring * g = 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; } @@ -2406,7 +2409,7 @@ if ((t = tree_search(*anchor, key))) /* 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; @@ -2455,7 +2458,7 @@ if (!dbdb) /* 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 { @@ -2469,7 +2472,7 @@ if (!dbdb) 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); @@ -2686,9 +2689,10 @@ else 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); @@ -2761,7 +2765,7 @@ if (*portend != '\0') } /* 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; @@ -3017,198 +3021,196 @@ for (; cb; cb = cb->next) switch(control_type) { case CONTROL_AUTH_UNADVERTISED: - f.allow_auth_unadvertised = TRUE; - break; + f.allow_auth_unadvertised = TRUE; + break; - #ifdef EXPERIMENTAL_BRIGHTMAIL +#ifdef EXPERIMENTAL_BRIGHTMAIL case CONTROL_BMI_RUN: - bmi_run = 1; - break; - #endif + bmi_run = 1; + break; +#endif - #ifndef DISABLE_DKIM +#ifndef DISABLE_DKIM case CONTROL_DKIM_VERIFY: - f.dkim_disable_verify = TRUE; - #ifdef EXPERIMENTAL_DMARC - /* Since DKIM was blocked, skip DMARC too */ - f.dmarc_disable_verify = TRUE; - f.dmarc_enable_forensic = FALSE; - #endif + f.dkim_disable_verify = TRUE; +# ifdef SUPPORT_DMARC + /* Since DKIM was blocked, skip DMARC too */ + f.dmarc_disable_verify = TRUE; + f.dmarc_enable_forensic = FALSE; +# endif break; - #endif +#endif - #ifdef EXPERIMENTAL_DMARC +#ifdef SUPPORT_DMARC case CONTROL_DMARC_VERIFY: - f.dmarc_disable_verify = TRUE; - break; + f.dmarc_disable_verify = TRUE; + break; case CONTROL_DMARC_FORENSIC: - f.dmarc_enable_forensic = TRUE; - break; - #endif + f.dmarc_enable_forensic = TRUE; + break; +#endif case CONTROL_DSCP: - if (*p == '/') - { - int fd, af, level, optname, value; - /* If we are acting on stdin, the setsockopt may fail if stdin is not - a socket; we can accept that, we'll just debug-log failures anyway. */ - fd = fileno(smtp_in); - af = ip_get_address_family(fd); - if (af < 0) + if (*p == '/') { - HDEBUG(D_acl) - debug_printf_indent("smtp input is probably not a socket [%s], not setting DSCP\n", - strerror(errno)); - break; - } - if (dscp_lookup(p+1, af, &level, &optname, &value)) - { - if (setsockopt(fd, level, optname, &value, sizeof(value)) < 0) + int fd, af, level, optname, value; + /* If we are acting on stdin, the setsockopt may fail if stdin is not + a socket; we can accept that, we'll just debug-log failures anyway. */ + fd = fileno(smtp_in); + if ((af = ip_get_address_family(fd)) < 0) { - HDEBUG(D_acl) debug_printf_indent("failed to set input DSCP[%s]: %s\n", - p+1, strerror(errno)); + HDEBUG(D_acl) + debug_printf_indent("smtp input is probably not a socket [%s], not setting DSCP\n", + strerror(errno)); + break; } + if (dscp_lookup(p+1, af, &level, &optname, &value)) + if (setsockopt(fd, level, optname, &value, sizeof(value)) < 0) + { + HDEBUG(D_acl) debug_printf_indent("failed to set input DSCP[%s]: %s\n", + p+1, strerror(errno)); + } + else + { + HDEBUG(D_acl) debug_printf_indent("set input DSCP to \"%s\"\n", p+1); + } else { - HDEBUG(D_acl) debug_printf_indent("set input DSCP to \"%s\"\n", p+1); + *log_msgptr = string_sprintf("unrecognised DSCP value in \"control=%s\"", arg); + return ERROR; } } else { - *log_msgptr = string_sprintf("unrecognised DSCP value in \"control=%s\"", arg); + *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg); return ERROR; } - } - else - { - *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg); - return ERROR; - } - break; + break; case CONTROL_ERROR: - return ERROR; + return ERROR; case CONTROL_CASEFUL_LOCAL_PART: - deliver_localpart = addr->cc_local_part; - break; + deliver_localpart = addr->cc_local_part; + break; case CONTROL_CASELOWER_LOCAL_PART: - deliver_localpart = addr->lc_local_part; - break; + deliver_localpart = addr->lc_local_part; + break; case CONTROL_ENFORCE_SYNC: - smtp_enforce_sync = TRUE; - break; + smtp_enforce_sync = TRUE; + break; case CONTROL_NO_ENFORCE_SYNC: - smtp_enforce_sync = FALSE; - break; + smtp_enforce_sync = FALSE; + break; - #ifdef WITH_CONTENT_SCAN +#ifdef WITH_CONTENT_SCAN case CONTROL_NO_MBOX_UNSPOOL: - f.no_mbox_unspool = TRUE; - break; - #endif + f.no_mbox_unspool = TRUE; + break; +#endif case CONTROL_NO_MULTILINE: - f.no_multiline_responses = TRUE; - break; + f.no_multiline_responses = TRUE; + break; case CONTROL_NO_PIPELINING: - f.pipelining_enable = FALSE; - break; + f.pipelining_enable = FALSE; + break; case CONTROL_NO_DELAY_FLUSH: - f.disable_delay_flush = TRUE; - break; + f.disable_delay_flush = TRUE; + break; case CONTROL_NO_CALLOUT_FLUSH: - f.disable_callout_flush = TRUE; - break; + f.disable_callout_flush = TRUE; + break; case CONTROL_FAKEREJECT: - cancel_cutthrough_connection(TRUE, US"fakereject"); - case CONTROL_FAKEDEFER: - fake_response = (control_type == CONTROL_FAKEDEFER) ? DEFER : FAIL; - if (*p == '/') - { - const uschar *pp = p + 1; - while (*pp) 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; + cancel_cutthrough_connection(TRUE, US"fakereject"); + case CONTROL_FAKEDEFER: + fake_response = (control_type == CONTROL_FAKEDEFER) ? DEFER : FAIL; + if (*p == '/') + { + const uschar *pp = p + 1; + while (*pp) 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: - f.deliver_freeze = TRUE; - deliver_frozen_at = time(NULL); - freeze_tell = freeze_tell_config; /* Reset to configured value */ - if (Ustrncmp(p, "/no_tell", 8) == 0) - { - p += 8; - freeze_tell = NULL; - } - if (*p != 0) - { - *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg); - return ERROR; - } - cancel_cutthrough_connection(TRUE, US"item frozen"); - break; + f.deliver_freeze = TRUE; + deliver_frozen_at = time(NULL); + freeze_tell = freeze_tell_config; /* Reset to configured value */ + if (Ustrncmp(p, "/no_tell", 8) == 0) + { + p += 8; + freeze_tell = NULL; + } + if (*p) + { + *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg); + return ERROR; + } + cancel_cutthrough_connection(TRUE, US"item frozen"); + break; - case CONTROL_QUEUE_ONLY: - f.queue_only_policy = TRUE; - cancel_cutthrough_connection(TRUE, US"queueing forced"); - break; + case CONTROL_QUEUE: + f.queue_only_policy = TRUE; + if (Ustrcmp(p, "_only") == 0) + p += 5; + else while (*p == '/') + if (Ustrncmp(p, "/only", 5) == 0) + { p += 5; f.queue_smtp = FALSE; } + else if (Ustrncmp(p, "/first_pass_route", 17) == 0) + { p += 17; f.queue_smtp = TRUE; } + else + break; + 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; - while (*p == '/') - { - if (Ustrncmp(p, "/sender_retain", 14) == 0) - { - p += 14; - f.active_local_sender_retain = TRUE; - f.active_local_from_check = FALSE; - } - else if (Ustrncmp(p, "/domain=", 8) == 0) + originator_name = US""; + f.submission_mode = TRUE; + while (*p == '/') { - const uschar *pp = p + 8; - while (*pp && *pp != '/') pp++; - submission_domain = string_copyn(p+8, pp-p-8); - p = pp; + if (Ustrncmp(p, "/sender_retain", 14) == 0) + { + p += 14; + f.active_local_sender_retain = TRUE; + f.active_local_from_check = FALSE; + } + else if (Ustrncmp(p, "/domain=", 8) == 0) + { + const uschar *pp = p + 8; + while (*pp && *pp != '/') pp++; + submission_domain = string_copyn(p+8, pp-p-8); + p = pp; + } + /* The name= option must be last, because it swallows the rest of + the string. */ + else if (Ustrncmp(p, "/name=", 6) == 0) + { + 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)); + p = pp; + } + else break; } - /* The name= option must be last, because it swallows the rest of - the string. */ - else if (Ustrncmp(p, "/name=", 6) == 0) + if (*p) { - 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)); - p = pp; + *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg); + return ERROR; } - else break; - } - if (*p != 0) - { - *log_msgptr = string_sprintf("syntax error in \"control=%s\"", arg); - return ERROR; - } - break; + break; case CONTROL_DEBUG: { @@ -3243,99 +3245,99 @@ for (; cb; cb = cb->next) debug_logging_stop(); else debug_logging_activate(debug_tag, debug_opts); + break; } - break; case CONTROL_SUPPRESS_LOCAL_FIXUPS: - f.suppress_local_fixups = TRUE; - break; + f.suppress_local_fixups = TRUE; + break; case CONTROL_CUTTHROUGH_DELIVERY: - { - uschar * ignored = NULL; + { + uschar * ignored = NULL; #ifndef DISABLE_PRDR - if (prdr_requested) + if (prdr_requested) #else - if (0) + if (0) #endif - /* Too hard to think about for now. We might in future cutthrough - 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"; + /* Too hard to think about for now. We might in future cutthrough + the case where both sides handle prdr and this-node prdr acl + is "accept" */ + ignored = US"PRDR active"; else { - if (rcpt_count == 1) + 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 == '/') { - pp += 6; - if (Ustrncmp(pp, "pass", 4) == 0) cutthrough.defer_pass = TRUE; - /* else if (Ustrncmp(pp, "spool") == 0) ; default */ + 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; } - else - while (*pp && *pp != '/') pp++; - p = pp; } + else + ignored = US"nonfirst rcpt"; } - else - ignored = US"nonfirst rcpt"; } + DEBUG(D_acl) if (ignored) + debug_printf(" cutthrough request ignored on %s item\n", ignored); } - DEBUG(D_acl) if (ignored) - debug_printf(" cutthrough request ignored on %s item\n", ignored); - } break; #ifdef SUPPORT_I18N case CONTROL_UTF8_DOWNCONVERT: - if (*p == '/') - { - if (p[1] == '1') + if (*p == '/') { - message_utf8_downconvert = 1; - addr->prop.utf8_downcvt = TRUE; - addr->prop.utf8_downcvt_maybe = FALSE; - p += 2; - break; + if (p[1] == '1') + { + message_utf8_downconvert = 1; + addr->prop.utf8_downcvt = TRUE; + addr->prop.utf8_downcvt_maybe = FALSE; + p += 2; + break; + } + if (p[1] == '0') + { + message_utf8_downconvert = 0; + addr->prop.utf8_downcvt = FALSE; + addr->prop.utf8_downcvt_maybe = FALSE; + p += 2; + break; + } + if (p[1] == '-' && p[2] == '1') + { + message_utf8_downconvert = -1; + addr->prop.utf8_downcvt = FALSE; + addr->prop.utf8_downcvt_maybe = TRUE; + p += 3; + break; + } + *log_msgptr = US"bad option value for control=utf8_downconvert"; } - if (p[1] == '0') + else { - message_utf8_downconvert = 0; - addr->prop.utf8_downcvt = FALSE; + message_utf8_downconvert = 1; + addr->prop.utf8_downcvt = TRUE; addr->prop.utf8_downcvt_maybe = FALSE; - p += 2; break; } - if (p[1] == '-' && p[2] == '1') - { - message_utf8_downconvert = -1; - addr->prop.utf8_downcvt = FALSE; - addr->prop.utf8_downcvt_maybe = TRUE; - p += 3; - break; - } - *log_msgptr = US"bad option value for control=utf8_downconvert"; - } - else - { - message_utf8_downconvert = 1; - addr->prop.utf8_downcvt = TRUE; - addr->prop.utf8_downcvt_maybe = FALSE; - break; - } - return ERROR; + return ERROR; #endif } @@ -3444,7 +3446,7 @@ for (; cb; cb = cb->next) break; #endif - #ifdef EXPERIMENTAL_DMARC +#ifdef SUPPORT_DMARC case ACLC_DMARC_STATUS: if (!f.dmarc_has_been_checked) dmarc_process(); @@ -3454,7 +3456,7 @@ for (; cb; cb = cb->next) 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); @@ -3498,7 +3500,7 @@ for (; cb; cb = cb->next) (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: @@ -3594,13 +3596,19 @@ for (; cb; cb = cb->next) #endif case ACLC_QUEUE: + if (is_tainted(arg)) + { + *log_msgptr = string_sprintf("Tainted name '%s' for queue not permitted", + arg); + return ERROR; + } if (Ustrchr(arg, '/')) { *log_msgptr = string_sprintf( "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: @@ -3637,15 +3645,12 @@ for (; cb; cb = cb->next) sender_address_cache, -1, 0, CUSS &sender_data); break; - /* Connection variables must persist forever */ + /* Connection variables must persist forever; message variables not */ case ACLC_SET: { int old_pool = store_pool; - if ( cb->u.varname[0] == 'c' -#ifndef DISABLE_DKIM - || cb->u.varname[0] == 'd' -#endif + if ( cb->u.varname[0] != 'm' #ifndef DISABLE_EVENT || event_name /* An event is being delivered */ #endif @@ -4000,13 +4005,20 @@ if (Ustrchr(ss, ' ') == NULL) 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, @@ -4014,7 +4026,8 @@ if (Ustrchr(ss, ' ') == NULL) 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) @@ -4044,7 +4057,7 @@ if (!acl) 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); @@ -4525,7 +4538,7 @@ acl_var_create(uschar * name) 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); } @@ -4559,6 +4572,7 @@ void 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); }