X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/20395676aba7fa5eb9a2c5e0b9f582ec2b3e71e4..1d28cc061677bd07d9bed48dd84bd5c590247043:/src/src/acl.c diff --git a/src/src/acl.c b/src/src/acl.c index 1bf118764..143890668 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -2,14 +2,16 @@ * Exim - an Internet mail transport agent * *************************************************/ +/* Copyright (c) The Exim Maintainers 2020 - 2022 */ /* Copyright (c) University of Cambridge 1995 - 2018 */ -/* Copyright (c) The Exim Maintainers 2020 */ /* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* Code for handling Access Control Lists (ACLs) */ #include "exim.h" +#ifndef MACRO_PREDEF /* Default callout timeout */ @@ -53,6 +55,8 @@ static int msgcond[] = { [ACL_WARN] = BIT(OK) }; +#endif + /* ACL condition and modifier codes - keep in step with the table that follows. down. */ @@ -103,6 +107,7 @@ enum { ACLC_ACL, ACLC_REGEX, #endif ACLC_REMOVE_HEADER, + ACLC_SEEN, ACLC_SENDER_DOMAINS, ACLC_SENDERS, ACLC_SET, @@ -288,6 +293,7 @@ static condition_def conditions[] = { ACL_BIT_MIME | ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START), }, + [ACLC_SEEN] = { US"seen", TRUE, FALSE, 0 }, [ACLC_SENDER_DOMAINS] = { US"sender_domains", FALSE, FALSE, ACL_BIT_AUTH | ACL_BIT_CONNECT | ACL_BIT_HELO | @@ -338,6 +344,24 @@ static condition_def conditions[] = { }; +#ifdef MACRO_PREDEF +# include "macro_predef.h" +void +features_acl(void) +{ +for (condition_def * c = conditions; c < conditions + nelem(conditions); c++) + { + uschar buf[64], * p, * s; + int n = sprintf(CS buf, "_ACL_%s_", c->is_modifier ? "MOD" : "COND"); + for (p = buf + n, s = c->name; *s; s++) *p++ = toupper(*s); + *p = '\0'; + builtin_macro_create(buf); + } +} +#endif + + +#ifndef MACRO_PREDEF /* Return values from decode_control(); used as index so keep in step with the controls_list table that follows! */ @@ -619,6 +643,8 @@ static uschar *ratelimit_option_string[] = { static int acl_check_wargs(int, address_item *, const uschar *, uschar **, uschar **); +static acl_block * acl_current = NULL; + /************************************************* * Find control in list * @@ -709,6 +735,78 @@ return -1; } +static BOOL +acl_varname_to_cond(const uschar ** sp, acl_condition_block * cond, uschar ** error) +{ +const uschar * s = *sp, * endptr; + +#ifndef DISABLE_DKIM +if ( Ustrncmp(s, "dkim_verify_status", 18) == 0 + || Ustrncmp(s, "dkim_verify_reason", 18) == 0) + { + endptr = s+18; + if (isalnum(*endptr)) + { + *error = string_sprintf("invalid variable name after \"set\" in ACL " + "modifier \"set %s\" " + "(only \"dkim_verify_status\" or \"dkim_verify_reason\" permitted)", + s); + return FALSE; + } + cond->u.varname = string_copyn(s, 18); + } +else +#endif + { + 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); + return FALSE; + } + + endptr = s + 5; + 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); + return FALSE; + } + + for ( ; *endptr && *endptr != '=' && !isspace(*endptr); endptr++) + if (!isalnum(*endptr) && *endptr != '_') + { + *error = string_sprintf("invalid character \"%c\" in variable name " + "in ACL modifier \"set %s\"", *endptr, s); + return FALSE; + } + + cond->u.varname = string_copyn(s + 4, endptr - s - 4); + } +s = endptr; +Uskip_whitespace(&s); +*sp = s; +return TRUE; +} + + +static BOOL +acl_data_to_cond(const uschar * s, acl_condition_block * cond, + const uschar * name, uschar ** error) +{ +if (*s++ != '=') + { + *error = string_sprintf("\"=\" missing after ACL \"%s\" %s", name, + conditions[cond->type].is_modifier ? US"modifier" : US"condition"); + return FALSE;; + } +Uskip_whitespace(&s); +cond->arg = string_copy(s); +return TRUE; +} + + /************************************************* * Read and parse one ACL * *************************************************/ @@ -735,7 +833,7 @@ acl_block **lastp = &yield; acl_block *this = NULL; acl_condition_block *cond; acl_condition_block **condp = NULL; -uschar * s; +const uschar * s; *error = NULL; @@ -743,7 +841,7 @@ while ((s = (*func)())) { int v, c; BOOL negated = FALSE; - uschar *saveline = s; + const uschar * saveline = s; uschar name[EXIM_DRIVERNAME_MAX]; /* Conditions (but not verbs) are allowed to be negated by an initial @@ -783,16 +881,15 @@ while ((s = (*func)())) *error = string_sprintf("malformed ACL line \"%s\"", saveline); return NULL; } - this = store_get(sizeof(acl_block), FALSE); - *lastp = this; - lastp = &(this->next); + *lastp = this = store_get(sizeof(acl_block), GET_UNTAINTED); + lastp = &this->next; this->next = NULL; this->condition = NULL; this->verb = v; this->srcline = config_lineno; /* for debug output */ this->srcfile = config_filename; /**/ - condp = &(this->condition); - if (*s == 0) continue; /* No condition on this line */ + condp = &this->condition; + if (!*s) continue; /* No condition on this line */ if (*s == '!') { negated = TRUE; @@ -830,13 +927,13 @@ while ((s = (*func)())) return NULL; } - cond = store_get(sizeof(acl_condition_block), FALSE); + cond = store_get(sizeof(acl_condition_block), GET_UNTAINTED); cond->next = NULL; cond->type = c; cond->u.negated = negated; *condp = cond; - condp = &(cond->next); + condp = &cond->next; /* The "set" modifier is different in that its argument is "name=value" rather than just a value, and we can check the validity of the name, which @@ -849,75 +946,13 @@ while ((s = (*func)())) compatibility. */ if (c == ACLC_SET) -#ifndef DISABLE_DKIM - if ( Ustrncmp(s, "dkim_verify_status", 18) == 0 - || Ustrncmp(s, "dkim_verify_reason", 18) == 0) - { - uschar * endptr = s+18; - - if (isalnum(*endptr)) - { - *error = string_sprintf("invalid variable name after \"set\" in ACL " - "modifier \"set %s\" " - "(only \"dkim_verify_status\" or \"dkim_verify_reason\" permitted)", - s); - return NULL; - } - cond->u.varname = string_copyn(s, 18); - s = endptr; - Uskip_whitespace(&s); - } - else -#endif - { - uschar *endptr; - - 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); - return NULL; - } - - endptr = s + 5; - 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); - return NULL; - } - - 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; - } - endptr++; - } - - cond->u.varname = string_copyn(s + 4, endptr - s - 4); - s = endptr; - Uskip_whitespace(&s); - } + if (!acl_varname_to_cond(&s, cond, error)) return NULL; /* For "set", we are now positioned for the data. For the others, only "endpass" has no data */ if (c != ACLC_ENDPASS) - { - if (*s++ != '=') - { - *error = string_sprintf("\"=\" missing after ACL \"%s\" %s", name, - conditions[c].is_modifier ? US"modifier" : US"condition"); - return NULL; - } - Uskip_whitespace(&s); - cond->arg = string_copy(s); - } + if (!acl_data_to_cond(s, cond, name, error)) return NULL; } return yield; @@ -1029,7 +1064,7 @@ for (p = q; *p; p = q) { /* The header_line struct itself is not tainted, though it points to possibly tainted data. */ - header_line * h = store_get(sizeof(header_line), FALSE); + header_line * h = store_get(sizeof(header_line), GET_UNTAINTED); h->text = hdr; h->next = NULL; h->type = newtype; @@ -1188,7 +1223,7 @@ int rc; /* Previous success */ -if (sender_host_name != NULL) return OK; +if (sender_host_name) return OK; /* Previous failure */ @@ -1318,7 +1353,7 @@ dns_scan dnss; dns_record *rr; int rc, type, yield; #define TARGET_SIZE 256 -uschar * target = store_get(TARGET_SIZE, TRUE); +uschar * target = store_get(TARGET_SIZE, GET_TAINTED); /* Work out the domain we are using for the CSA lookup. The default is the client's HELO domain. If the client has not said HELO, use its IP address @@ -1360,7 +1395,7 @@ we return from this function. */ if ((t = tree_search(csa_cache, domain))) return t->data.val; -t = store_get_perm(sizeof(tree_node) + Ustrlen(domain), is_tainted(domain)); +t = store_get_perm(sizeof(tree_node) + Ustrlen(domain), domain); Ustrcpy(t->name, domain); (void)tree_insertnode(&csa_cache, t); @@ -2562,7 +2597,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, FALSE); /* not tainted */ + dbdb = store_get(dbdb_size, GET_UNTAINTED); } else { @@ -2576,7 +2611,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, FALSE); /* not tainted */ + dbdb = store_get(dbdb_size, GET_UNTAINTED); dbdb->bloom_epoch = tv.tv_sec; dbdb->bloom_size = sizeof(dbdb->bloom) + extra; memset(dbdb->bloom, 0, dbdb->bloom_size); @@ -2796,7 +2831,7 @@ dbfn_close(dbm); /* 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), is_tainted(key)); +t = store_get(sizeof(tree_node) + Ustrlen(key), key); t->data.ptr = dbd; Ustrcpy(t->name, key); (void)tree_insertnode(anchor, t); @@ -2815,6 +2850,143 @@ return rc; +/************************************************* +* Handle a check for previously-seen * +*************************************************/ + +/* +ACL clauses like: seen = -5m / key=$foo / readonly + +Return is true for condition-true - but the semantics +depend heavily on the actual use-case. + +Negative times test for seen-before, positive for seen-more-recently-than +(the given interval before current time). + +All are subject to history not having been cleaned from the DB. + +Default for seen-before is to create if not present, and to +update if older than 10d (with the seen-test time). +Default for seen-since is to always create or update. + +Options: + key=value. Default key is $sender_host_address + readonly + write + refresh=: update an existing DB entry older than given + amount. Default refresh lacking this option is 10d. + The update sets the record timestamp to the seen-test time. + +XXX do we need separate nocreate, noupdate controls? + +Arguments: + arg the option string for seen= + where ACL_WHERE_xxxx indicating which ACL this is + log_msgptr for error messages + +Returns: OK - Condition is true + FAIL - Condition is false + DEFER - Problem opening history database + ERROR - Syntax error in options +*/ + +static int +acl_seen(const uschar * arg, int where, uschar ** log_msgptr) +{ +enum { SEEN_DEFAULT, SEEN_READONLY, SEEN_WRITE }; + +const uschar * list = arg; +int slash = '/', interval, mode = SEEN_DEFAULT, yield = FAIL; +BOOL before; +int refresh = 10 * 24 * 60 * 60; /* 10 days */ +const uschar * ele, * key = sender_host_address; +open_db dbblock, * dbm; +dbdata_seen * dbd; +time_t now; + +/* Parse the first element, the time-relation. */ + +if (!(ele = string_nextinlist(&list, &slash, NULL, 0))) + goto badparse; +if ((before = *ele == '-')) + ele++; +if ((interval = readconf_readtime(ele, 0, FALSE)) < 0) + goto badparse; + +/* Remaining elements are options */ + +while ((ele = string_nextinlist(&list, &slash, NULL, 0))) + if (Ustrncmp(ele, "key=", 4) == 0) + key = ele + 4; + else if (Ustrcmp(ele, "readonly") == 0) + mode = SEEN_READONLY; + else if (Ustrcmp(ele, "write") == 0) + mode = SEEN_WRITE; + else if (Ustrncmp(ele, "refresh=", 8) == 0) + { + if ((refresh = readconf_readtime(ele + 8, 0, FALSE)) < 0) + goto badparse; + } + else + goto badopt; + +if (!(dbm = dbfn_open(US"seen", O_RDWR, &dbblock, TRUE, TRUE))) + { + HDEBUG(D_acl) debug_printf_indent("database for 'seen' not available\n"); + *log_msgptr = US"database for 'seen' not available"; + return DEFER; + } + +dbd = dbfn_read_with_length(dbm, key, NULL); +now = time(NULL); +if (dbd) /* an existing record */ + { + time_t diff = now - dbd->time_stamp; /* time since the record was written */ + + if (before ? diff >= interval : diff < interval) + yield = OK; + + if (mode == SEEN_READONLY) + { HDEBUG(D_acl) debug_printf_indent("seen db not written (readonly)\n"); } + else if (mode == SEEN_WRITE || !before) + { + dbd->time_stamp = now; + dbfn_write(dbm, key, dbd, sizeof(*dbd)); + HDEBUG(D_acl) debug_printf_indent("seen db written (update)\n"); + } + else if (diff >= refresh) + { + dbd->time_stamp = now - interval; + dbfn_write(dbm, key, dbd, sizeof(*dbd)); + HDEBUG(D_acl) debug_printf_indent("seen db written (refresh)\n"); + } + } +else + { /* No record found, yield always FAIL */ + if (mode != SEEN_READONLY) + { + dbdata_seen d = {.time_stamp = now}; + dbfn_write(dbm, key, &d, sizeof(*dbd)); + HDEBUG(D_acl) debug_printf_indent("seen db written (create)\n"); + } + else + HDEBUG(D_acl) debug_printf_indent("seen db not written (readonly)\n"); + } + +dbfn_close(dbm); +return yield; + + +badparse: + *log_msgptr = string_sprintf("failed to parse '%s'", arg); + return ERROR; +badopt: + *log_msgptr = string_sprintf("unrecognised option '%s' in '%s'", ele, arg); + return ERROR; +} + + + /************************************************* * The udpsend ACL modifier * *************************************************/ @@ -2869,7 +3041,7 @@ if (*portend != '\0') } /* Make a single-item host list. */ -h = store_get(sizeof(host_item), FALSE); +h = store_get(sizeof(host_item), GET_UNTAINTED); memset(h, 0, sizeof(host_item)); h->name = hostname; h->port = portnum; @@ -2954,17 +3126,15 @@ acl_check_condition(int verb, acl_condition_block *cb, int where, address_item *addr, int level, BOOL *epp, uschar **user_msgptr, uschar **log_msgptr, int *basic_errno) { -uschar *user_message = NULL; -uschar *log_message = NULL; +uschar * user_message = NULL; +uschar * log_message = NULL; int rc = OK; -#ifdef WITH_CONTENT_SCAN -int sep = -'/'; -#endif for (; cb; cb = cb->next) { - const uschar *arg; + const uschar * arg; int control_type; + BOOL textonly = FALSE; /* The message and log_message items set up messages to be used in case of rejection. They are expanded later. */ @@ -2998,7 +3168,8 @@ for (; cb; cb = cb->next) if (!conditions[cb->type].expand_at_top) arg = cb->arg; - else if (!(arg = expand_string(cb->arg))) + + else if (!(arg = expand_string_2(cb->arg, &textonly))) { if (f.expand_string_forcedfail) continue; *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s", @@ -3055,8 +3226,8 @@ for (; cb; cb = cb->next) switch(cb->type) { case ACLC_ADD_HEADER: - setup_header(arg); - break; + setup_header(arg); + break; /* A nested ACL that returns "discard" makes sense only for an "accept" or "discard" verb. */ @@ -3070,12 +3241,12 @@ for (; cb; cb = cb->next) verbs[verb]); return ERROR; } - break; + break; case ACLC_AUTHENTICATED: rc = sender_host_authenticated ? match_isinlist(sender_host_authenticated, &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL) : FAIL; - break; + break; #ifdef EXPERIMENTAL_BRIGHTMAIL case ACLC_BMI_OPTIN: @@ -3092,21 +3263,21 @@ for (; cb; cb = cb->next) /* The true/false parsing here should be kept in sync with that used in expand.c when dealing with ECOND_BOOL so that we don't have too many different definitions of what can be a boolean. */ - if (*arg == '-' - ? Ustrspn(arg+1, "0123456789") == Ustrlen(arg+1) /* Negative number */ - : Ustrspn(arg, "0123456789") == Ustrlen(arg)) /* Digits, or empty */ - rc = (Uatoi(arg) == 0)? FAIL : OK; - else - rc = (strcmpic(arg, US"no") == 0 || - strcmpic(arg, US"false") == 0)? FAIL : - (strcmpic(arg, US"yes") == 0 || - strcmpic(arg, US"true") == 0)? OK : DEFER; - if (rc == DEFER) - *log_msgptr = string_sprintf("invalid \"condition\" value \"%s\"", arg); - break; + if (*arg == '-' + ? Ustrspn(arg+1, "0123456789") == Ustrlen(arg+1) /* Negative number */ + : Ustrspn(arg, "0123456789") == Ustrlen(arg)) /* Digits, or empty */ + rc = (Uatoi(arg) == 0)? FAIL : OK; + else + rc = (strcmpic(arg, US"no") == 0 || + strcmpic(arg, US"false") == 0)? FAIL : + (strcmpic(arg, US"yes") == 0 || + strcmpic(arg, US"true") == 0)? OK : DEFER; + if (rc == DEFER) + *log_msgptr = string_sprintf("invalid \"condition\" value \"%s\"", arg); + break; case ACLC_CONTINUE: /* Always succeeds */ - break; + break; case ACLC_CONTROL: { @@ -3235,13 +3406,15 @@ for (; cb; cb = cb->next) case CONTROL_FAKEREJECT: cancel_cutthrough_connection(TRUE, US"fakereject"); - case CONTROL_FAKEDEFER: + 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)); + /* The entire control= line was expanded at top so no need to expand + the part after the / */ + fake_response_text = string_copyn(p+1, pp-p-1); p = pp; } else /* Explicitly reset to default string */ @@ -3317,9 +3490,8 @@ for (; cb; cb = cb->next) case CONTROL_DEBUG: { - uschar * debug_tag = NULL; - uschar * debug_opts = NULL; - BOOL kill = FALSE; + uschar * debug_tag = NULL, * debug_opts = NULL; + BOOL kill = FALSE, stop = FALSE; while (*p == '/') { @@ -3336,18 +3508,39 @@ for (; cb; cb = cb->next) } else if (Ustrncmp(pp, "kill", 4) == 0) { - for (pp += 4; *pp && *pp != '/';) pp++; + pp += 4; kill = TRUE; } - else - while (*pp && *pp != '/') pp++; + else if (Ustrncmp(pp, "stop", 4) == 0) + { + pp += 4; + stop = TRUE; + } + else if (Ustrncmp(pp, "pretrigger=", 11) == 0) + debug_pretrigger_setup(pp+11); + else if (Ustrncmp(pp, "trigger=", 8) == 0) + { + if (Ustrncmp(pp += 8, "now", 3) == 0) + { + pp += 3; + debug_trigger_fire(); + } + else if (Ustrncmp(pp, "paniclog", 8) == 0) + { + pp += 8; + dtrigger_selector |= BIT(DTi_panictrigger); + } + } + while (*pp && *pp != '/') pp++; p = pp; } - if (kill) - debug_logging_stop(); - else - debug_logging_activate(debug_tag, debug_opts); + if (kill) + debug_logging_stop(TRUE); + else if (stop) + debug_logging_stop(FALSE); + else if (debug_tag || debug_opts) + debug_logging_activate(debug_tag, debug_opts); break; } @@ -3451,27 +3644,28 @@ for (; cb; cb = cb->next) break; } - #ifdef EXPERIMENTAL_DCC +#ifdef EXPERIMENTAL_DCC case ACLC_DCC: { /* Separate the regular expression and any optional parameters. */ const uschar * list = arg; - uschar *ss = string_nextinlist(&list, &sep, NULL, 0); + int sep = -'/'; + 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, NULL, 0))) if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER) rc = FAIL; /* FAIL so that the message is passed to the next ACL */ + break; } - break; - #endif +#endif - #ifdef WITH_CONTENT_SCAN +#ifdef WITH_CONTENT_SCAN case ACLC_DECODE: - rc = mime_decode(&arg); - break; - #endif + rc = mime_decode(&arg); + break; +#endif case ACLC_DELAY: { @@ -3535,44 +3729,44 @@ for (; cb; cb = cb->next) #endif } } + break; } - break; - #ifndef DISABLE_DKIM +#ifndef DISABLE_DKIM case ACLC_DKIM_SIGNER: - if (dkim_cur_signer) - rc = match_isinlist(dkim_cur_signer, + if (dkim_cur_signer) + rc = match_isinlist(dkim_cur_signer, &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL); - else - rc = FAIL; - break; + else + rc = FAIL; + break; case ACLC_DKIM_STATUS: - rc = match_isinlist(dkim_verify_status, - &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL); - break; - #endif + rc = match_isinlist(dkim_verify_status, + &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL); + break; +#endif #ifdef SUPPORT_DMARC case ACLC_DMARC_STATUS: - if (!f.dmarc_has_been_checked) - dmarc_process(); - f.dmarc_has_been_checked = TRUE; - /* 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); - break; + if (!f.dmarc_has_been_checked) + dmarc_process(); + f.dmarc_has_been_checked = TRUE; + /* 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); + break; #endif case ACLC_DNSLISTS: - rc = verify_check_dnsbl(where, &arg, log_msgptr); - break; + rc = verify_check_dnsbl(where, &arg, log_msgptr); + break; case ACLC_DOMAINS: - rc = match_isinlist(addr->domain, &arg, 0, &domainlist_anchor, - addr->domain_cache, MCL_DOMAIN, TRUE, CUSS &deliver_domain_data); - break; + rc = match_isinlist(addr->domain, &arg, 0, &domainlist_anchor, + addr->domain_cache, MCL_DOMAIN, TRUE, CUSS &deliver_domain_data); + break; /* The value in tls_cipher is the full cipher name, for example, TLSv1:DES-CBC3-SHA:168, whereas the values to test for are just the @@ -3581,19 +3775,20 @@ for (; cb; cb = cb->next) writing is poorly documented. */ case ACLC_ENCRYPTED: - if (tls_in.cipher == NULL) rc = FAIL; else - { - uschar *endcipher = NULL; - uschar *cipher = Ustrchr(tls_in.cipher, ':'); - if (!cipher) cipher = tls_in.cipher; else - { - endcipher = Ustrchr(++cipher, ':'); - if (endcipher) *endcipher = 0; - } - rc = match_isinlist(cipher, &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL); - if (endcipher) *endcipher = ':'; - } - break; + if (!tls_in.cipher) rc = FAIL; + else + { + uschar *endcipher = NULL; + uschar *cipher = Ustrchr(tls_in.cipher, ':'); + if (!cipher) cipher = tls_in.cipher; else + { + endcipher = Ustrchr(++cipher, ':'); + if (endcipher) *endcipher = 0; + } + rc = match_isinlist(cipher, &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL); + if (endcipher) *endcipher = ':'; + } + break; /* Use verify_check_this_host() instead of verify_check_host() so that we can pass over &host_data to catch any looked up data. Once it has been @@ -3603,25 +3798,24 @@ for (; cb; cb = cb->next) message in the same SMTP connection. */ case ACLC_HOSTS: - rc = verify_check_this_host(&arg, sender_host_cache, NULL, - 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; + rc = verify_check_this_host(&arg, sender_host_cache, NULL, + 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; case ACLC_LOCAL_PARTS: - rc = match_isinlist(addr->cc_local_part, &arg, 0, - &localpartlist_anchor, addr->localpart_cache, MCL_LOCALPART, TRUE, - CUSS &deliver_localpart_data); - break; + rc = match_isinlist(addr->cc_local_part, &arg, 0, + &localpartlist_anchor, addr->localpart_cache, MCL_LOCALPART, TRUE, + CUSS &deliver_localpart_data); + break; case ACLC_LOG_REJECT_TARGET: { - int logbits = 0; - int sep = 0; - const uschar *s = arg; - uschar * ss; - while ((ss = string_nextinlist(&s, &sep, NULL, 0))) + int logbits = 0, sep = 0; + const uschar * s = arg; + + for (uschar * ss; ss = string_nextinlist(&s, &sep, NULL, 0); ) { if (Ustrcmp(ss, "main") == 0) logbits |= LOG_MAIN; else if (Ustrcmp(ss, "panic") == 0) logbits |= LOG_PANIC; @@ -3634,8 +3828,8 @@ for (; cb; cb = cb->next) } } log_reject_target = logbits; + break; } - break; case ACLC_LOGWRITE: { @@ -3666,20 +3860,19 @@ for (; cb; cb = cb->next) if (logbits == 0) logbits = LOG_MAIN; log_write(0, logbits, "%s", string_printing(s)); + break; } - break; - #ifdef WITH_CONTENT_SCAN +#ifdef WITH_CONTENT_SCAN case ACLC_MALWARE: /* Run the malware backend. */ { /* Separate the regular expression and any optional parameters. */ const uschar * list = arg; - uschar * ss = string_nextinlist(&list, &sep, NULL, 0); - uschar * opt; BOOL defer_ok = FALSE; - int timeout = 0; + int timeout = 0, sep = -'/'; + uschar * ss = string_nextinlist(&list, &sep, NULL, 0); - while ((opt = string_nextinlist(&list, &sep, NULL, 0))) + for (uschar * opt; opt = string_nextinlist(&list, &sep, NULL, 0); ) if (strcmpic(opt, US"defer_ok") == 0) defer_ok = TRUE; else if ( strncmpic(opt, US"tmo=", 4) == 0 @@ -3690,53 +3883,55 @@ for (; cb; cb = cb->next) return ERROR; } - rc = malware(ss, timeout); + rc = malware(ss, textonly, timeout); if (rc == DEFER && defer_ok) rc = FAIL; /* FAIL so that the message is passed to the next ACL */ + break; } - break; case ACLC_MIME_REGEX: - rc = mime_regex(&arg); - break; - #endif + rc = mime_regex(&arg, textonly); + break; +#endif case ACLC_QUEUE: - { - uschar *m; - if ((m = is_tainted2(arg, 0, "Tainted name '%s' for queue not permitted", arg))) - { - *log_msgptr = m; - return ERROR; - } + 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; - } + { + *log_msgptr = string_sprintf( + "Directory separator not permitted in queue name: '%s'", arg); + return ERROR; + } queue_name = string_copy_perm(arg, FALSE); break; - } case ACLC_RATELIMIT: - rc = acl_ratelimit(arg, where, log_msgptr); - break; + rc = acl_ratelimit(arg, where, log_msgptr); + break; case ACLC_RECIPIENTS: - rc = match_address_list(CUS addr->address, TRUE, TRUE, &arg, NULL, -1, 0, - CUSS &recipient_data); - break; + rc = match_address_list(CUS addr->address, TRUE, TRUE, &arg, NULL, -1, 0, + CUSS &recipient_data); + break; #ifdef WITH_CONTENT_SCAN case ACLC_REGEX: - rc = regex(&arg); - break; + rc = regex(&arg, textonly); + break; #endif case ACLC_REMOVE_HEADER: - setup_remove_header(arg); - break; + setup_remove_header(arg); + break; + + case ACLC_SEEN: + rc = acl_seen(arg, where, log_msgptr); + break; case ACLC_SENDER_DOMAINS: { @@ -3745,13 +3940,13 @@ for (; cb; cb = cb->next) sdomain = sdomain ? sdomain + 1 : US""; rc = match_isinlist(sdomain, &arg, 0, &domainlist_anchor, sender_domain_cache, MCL_DOMAIN, TRUE, NULL); + break; } - break; case ACLC_SENDERS: - rc = match_address_list(CUS sender_address, TRUE, TRUE, &arg, - sender_address_cache, -1, 0, CUSS &sender_data); - break; + rc = match_address_list(CUS sender_address, TRUE, TRUE, &arg, + sender_address_cache, -1, 0, CUSS &sender_data); + break; /* Connection variables must persist forever; message variables not */ @@ -3773,37 +3968,39 @@ for (; cb; cb = cb->next) #endif acl_var_create(cb->u.varname)->data.ptr = string_copy(arg); store_pool = old_pool; + break; } - break; #ifdef WITH_CONTENT_SCAN case ACLC_SPAM: { /* Separate the regular expression and any optional parameters. */ const uschar * list = arg; - uschar *ss = string_nextinlist(&list, &sep, NULL, 0); + int sep = -'/'; + 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, NULL, 0))) if (strcmpic(ss, US"defer_ok") == 0 && rc == DEFER) rc = FAIL; /* FAIL so that the message is passed to the next ACL */ + break; } - break; #endif #ifdef SUPPORT_SPF case ACLC_SPF: rc = spf_process(&arg, sender_address, SPF_PROCESS_NORMAL); - break; + break; + case ACLC_SPF_GUESS: rc = spf_process(&arg, sender_address, SPF_PROCESS_GUESS); - break; + break; #endif case ACLC_UDPSEND: - rc = acl_udpsend(arg, log_msgptr); - break; + rc = acl_udpsend(arg, log_msgptr); + break; /* If the verb is WARN, discard any user message from verification, because such messages are SMTP responses, not header additions. The latter come @@ -3812,16 +4009,16 @@ for (; cb; cb = cb->next) (until something changes it). */ case ACLC_VERIFY: - rc = acl_verify(where, addr, arg, user_msgptr, log_msgptr, basic_errno); - if (*user_msgptr) - acl_verify_message = *user_msgptr; - if (verb == ACL_WARN) *user_msgptr = NULL; - break; + rc = acl_verify(where, addr, arg, user_msgptr, log_msgptr, basic_errno); + if (*user_msgptr) + acl_verify_message = *user_msgptr; + if (verb == ACL_WARN) *user_msgptr = NULL; + break; default: - log_write(0, LOG_MAIN|LOG_PANIC_DIE, "internal ACL error: unknown " - "condition %d", cb->type); - break; + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "internal ACL error: unknown " + "condition %d", cb->type); + break; } /* If a condition was negated, invert OK/FAIL. */ @@ -4016,6 +4213,18 @@ for(;;) +/************************************************/ +/* For error messages, a string describing the config location +associated with current processing. NULL if not in an ACL. */ + +uschar * +acl_current_verb(void) +{ +if (acl_current) return string_sprintf(" (ACL %s, %s %d)", + verbs[acl_current->verb], acl_current->srcfile, acl_current->srcline); +return NULL; +} + /************************************************* * Check access using an ACL * *************************************************/ @@ -4090,10 +4299,10 @@ while (isspace(*ss)) ss++; acl_text = ss; -if ( !f.running_in_test_harness - && is_tainted2(acl_text, LOG_MAIN|LOG_PANIC, - "Tainted ACL text \"%s\"", acl_text)) +if (is_tainted(acl_text) && !f.running_in_test_harness) { + log_write(0, LOG_MAIN|LOG_PANIC, + "attempt to use tainted ACL text \"%s\"", acl_text); /* Avoid leaking info to an attacker */ *log_msgptr = US"internal configuration error"; return ERROR; @@ -4122,12 +4331,6 @@ if (Ustrchr(ss, ' ') == NULL) else if (*ss == '/') { struct stat statbuf; - if (is_tainted2(ss, LOG_MAIN|LOG_PANIC, "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, @@ -4142,7 +4345,7 @@ if (Ustrchr(ss, ' ') == NULL) } /* 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 = store_get(statbuf.st_size + 1, ss); acl_text_end = acl_text + statbuf.st_size + 1; if (read(fd, acl_text, statbuf.st_size) != statbuf.st_size) @@ -4172,7 +4375,7 @@ if (!acl) if (!acl && *log_msgptr) return ERROR; if (fd >= 0) { - tree_node *t = store_get_perm(sizeof(tree_node) + Ustrlen(ss), is_tainted(ss)); + tree_node * t = store_get_perm(sizeof(tree_node) + Ustrlen(ss), ss); Ustrcpy(t->name, ss); t->data.ptr = acl; (void)tree_insertnode(&acl_anchor, t); @@ -4181,7 +4384,7 @@ if (!acl) /* Now we have an ACL to use. It's possible it may be NULL. */ -while (acl) +while ((acl_current = acl)) { int cond; int basic_errno = 0; @@ -4328,8 +4531,8 @@ while (acl) else if (cond == DEFER && LOGGING(acl_warn_skipped)) log_write(0, LOG_MAIN, "%s Warning: ACL \"warn\" statement skipped: " "condition test deferred%s%s", host_and_ident(TRUE), - (*log_msgptr == NULL)? US"" : US": ", - (*log_msgptr == NULL)? US"" : *log_msgptr); + *log_msgptr ? US": " : US"", + *log_msgptr ? *log_msgptr : US""); *log_msgptr = *user_msgptr = NULL; /* In case implicit DENY follows */ break; @@ -4654,7 +4857,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), is_tainted(name)); + node = store_get(sizeof(tree_node) + Ustrlen(name), name); Ustrcpy(node->name, name); (void)tree_insertnode(root, node); } @@ -4685,13 +4888,43 @@ Returns: nothing */ void -acl_var_write(uschar *name, uschar *value, void *ctx) +acl_var_write(uschar * name, uschar * value, void * ctx) +{ +FILE * f = (FILE *)ctx; +putc('-', f); +if (is_tainted(value)) + { + int q = quoter_for_address(value); + putc('-', f); + if (is_real_quoter(q)) fprintf(f, "(%s)", lookup_list[q]->name); + } +fprintf(f, "acl%c %s %d\n%s\n", name[0], name+1, Ustrlen(value), value); +} + + + + +uschar * +acl_standalone_setvar(const uschar * s) { -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); +acl_condition_block * cond = store_get(sizeof(acl_condition_block), GET_UNTAINTED); +uschar * errstr = NULL, * log_msg = NULL; +BOOL endpass_seen; +int e; + +cond->next = NULL; +cond->type = ACLC_SET; +if (!acl_varname_to_cond(&s, cond, &errstr)) return errstr; +if (!acl_data_to_cond(s, cond, US"'-be'", &errstr)) return errstr; + +if (acl_check_condition(ACL_WARN, cond, ACL_WHERE_UNKNOWN, + NULL, 0, &endpass_seen, &errstr, &log_msg, &e) != OK) + return string_sprintf("oops: %s", errstr); +return string_sprintf("variable %s set", cond->u.varname); } + +#endif /* !MACRO_PREDEF */ /* vi: aw ai sw=2 */ /* End of acl.c */