X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/8743d3acaaa2262007aa2862ffecd6b19125e38d..568092148bf6ade68174fa1ccf34b8c37d9064e9:/src/src/acl.c diff --git a/src/src/acl.c b/src/src/acl.c index 5f0a7864b..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,7 +367,7 @@ enum { CONTROL_NO_MULTILINE, CONTROL_NO_PIPELINING, - CONTROL_QUEUE_ONLY, + CONTROL_QUEUE, CONTROL_SUBMISSION, CONTROL_SUPPRESS_LOCAL_FIXUPS, #ifdef SUPPORT_I18N @@ -417,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 @@ -502,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 | @@ -511,7 +512,6 @@ static control_def controls_list[] = { ACL_BIT_NOTSMTP | ACL_BIT_MIME) }, - [CONTROL_SUBMISSION] = { US"submission", TRUE, (unsigned) @@ -866,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; } @@ -878,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++; } @@ -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, @@ -2115,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= @@ -2131,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); @@ -3020,193 +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) - { - 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 (*p == '/') { - 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 +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 } @@ -3442,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(); @@ -3452,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); @@ -3592,6 +3596,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 +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