X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/8743d3acaaa2262007aa2862ffecd6b19125e38d..2b60ac102164f379dff0f26a42f9bb14c9ce94ad:/src/src/acl.c diff --git a/src/src/acl.c b/src/src/acl.c index 5f0a7864b..24716f0d1 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -3,6 +3,7 @@ *************************************************/ /* 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) */ @@ -70,7 +71,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 +113,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 +194,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 +348,7 @@ enum { #ifndef DISABLE_DKIM CONTROL_DKIM_VERIFY, #endif -#ifdef EXPERIMENTAL_DMARC +#ifdef SUPPORT_DMARC CONTROL_DMARC_VERIFY, CONTROL_DMARC_FORENSIC, #endif @@ -366,7 +368,7 @@ enum { CONTROL_NO_MULTILINE, CONTROL_NO_PIPELINING, - CONTROL_QUEUE_ONLY, + CONTROL_QUEUE, CONTROL_SUBMISSION, CONTROL_SUPPRESS_LOCAL_FIXUPS, #ifdef SUPPORT_I18N @@ -417,7 +419,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 @@ -502,8 +504,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 | @@ -511,7 +513,6 @@ static control_def controls_list[] = { ACL_BIT_NOTSMTP | ACL_BIT_MIME) }, - [CONTROL_SUBMISSION] = { US"submission", TRUE, (unsigned) @@ -732,7 +733,7 @@ uschar * s; *error = NULL; -while ((s = (*func)()) != NULL) +while ((s = (*func)())) { int v, c; BOOL negated = FALSE; @@ -742,8 +743,7 @@ while ((s = (*func)()) != NULL) /* Conditions (but not verbs) are allowed to be negated by an initial exclamation mark. */ - while (isspace(*s)) s++; - if (*s == '!') + if (Uskip_whitespace(&s) == '!') { negated = TRUE; s++; @@ -859,18 +859,17 @@ while ((s = (*func)()) != NULL) } cond->u.varname = string_copyn(s, 18); s = endptr; - while (isspace(*s)) s++; + Uskip_whitespace(&s); } else #endif { 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; } @@ -878,25 +877,25 @@ 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++; } cond->u.varname = string_copyn(s + 4, endptr - s - 4); s = endptr; - while (isspace(*s)) s++; + Uskip_whitespace(&s); } /* For "set", we are now positioned for the data. For the others, only @@ -910,7 +909,7 @@ while ((s = (*func)()) != NULL) conditions[c].is_modifier ? US"modifier" : US"condition"); return NULL; } - while (isspace(*s)) s++; + Uskip_whitespace(&s); cond->arg = string_copy(s); } } @@ -1023,8 +1022,8 @@ for (p = q; *p; p = q) if (!*hptr) { /* The header_line struct itself is not tainted, though it points to - tainted data. */ - header_line *h = store_get(sizeof(header_line), FALSE); + possibly tainted data. */ + header_line * h = store_get(sizeof(header_line), FALSE); h->text = hdr; h->next = NULL; h->type = newtype; @@ -1345,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, @@ -1517,7 +1515,7 @@ static verify_type_t verify_type_list[] = { { 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, + | 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 }, @@ -1603,7 +1601,7 @@ an error if options are given for items that don't expect them. uschar *slash = Ustrchr(arg, '/'); const uschar *list = arg; -uschar *ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size); +uschar *ss = string_nextinlist(&list, &sep, NULL, 0); verify_type_t * vp; if (!ss) goto BAD_VERIFY; @@ -1748,7 +1746,7 @@ switch(vp->value) 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 { @@ -1794,19 +1792,16 @@ while ((ss = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))) 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; @@ -1930,8 +1925,8 @@ else if (verify_sender_address) } 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 @@ -1998,14 +1993,12 @@ else if (verify_sender_address) *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 */ @@ -2049,8 +2042,7 @@ else *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; @@ -2061,8 +2053,10 @@ else /* 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"); @@ -2072,7 +2066,7 @@ if (rc == DEFER && (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"; @@ -2091,7 +2085,7 @@ if (rc != OK && verify_sender_address != NULL) /* 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; @@ -2115,7 +2109,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= @@ -2131,10 +2127,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); @@ -2260,7 +2257,7 @@ count = 1.0; /* 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; @@ -3020,193 +3017,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; 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: { @@ -3241,99 +3241,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 } @@ -3442,7 +3442,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(); @@ -3452,7 +3452,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); @@ -3474,13 +3474,13 @@ for (; cb; cb = cb->next) { uschar *endcipher = NULL; uschar *cipher = Ustrchr(tls_in.cipher, ':'); - if (cipher == NULL) cipher = tls_in.cipher; else + if (!cipher) cipher = tls_in.cipher; else { endcipher = Ustrchr(++cipher, ':'); - if (endcipher != NULL) *endcipher = 0; + if (endcipher) *endcipher = 0; } rc = match_isinlist(cipher, &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL); - if (endcipher != NULL) *endcipher = ':'; + if (endcipher) *endcipher = ':'; } break; @@ -3493,8 +3493,7 @@ for (; cb; cb = cb->next) case ACLC_HOSTS: rc = verify_check_this_host(&arg, sender_host_cache, NULL, - (sender_host_address == NULL)? US"" : sender_host_address, - CUSS &host_data); + sender_host_address ? sender_host_address : US"", CUSS &host_data); if (rc == DEFER) *log_msgptr = search_error_message; if (host_data) host_data = string_copy_perm(host_data, TRUE); break; @@ -3592,6 +3591,12 @@ 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( @@ -3635,15 +3640,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 @@ -3836,16 +3838,16 @@ uschar *yield; for(;;) { - while (isspace(*acl_text)) acl_text++; /* Leading spaces/empty lines */ - if (*acl_text == 0) return NULL; /* No more data */ - yield = acl_text; /* Potential data line */ + Uskip_whitespace(&acl_text); /* Leading spaces/empty lines */ + if (!*acl_text) return NULL; /* No more data */ + yield = acl_text; /* Potential data line */ while (*acl_text && *acl_text != '\n') acl_text++; /* If we hit the end before a newline, we have the whole logical line. If it's a comment, there's no more data to be given. Otherwise, yield it. */ - if (*acl_text == 0) return (*yield == '#')? NULL : yield; + if (!*acl_text) return *yield == '#' ? NULL : yield; /* After reaching a newline, end this loop if the physical line does not start with '#'. If it does, it's a comment, and the loop continues. */