X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/e3e281ccf9d8777d0df98ddd644720573e0343d1..a85c067ba6c6940512cf57ec213277a370d87e70:/src/src/match.c diff --git a/src/src/match.c b/src/src/match.c index 6415b993d..a877aef3b 100644 --- a/src/src/match.c +++ b/src/src/match.c @@ -2,9 +2,10 @@ * 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 - 2021 */ /* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-only */ /* Functions for matching strings */ @@ -19,9 +20,7 @@ typedef struct check_string_block { const uschar *origsubject; /* caseful; keep these two first, in */ const uschar *subject; /* step with the block below */ int expand_setup; - BOOL use_partial; - BOOL caseless; - BOOL at_is_special; + mcs_flags flags; /* MCS_* defs in macros.h */ } check_string_block; @@ -32,7 +31,7 @@ typedef struct check_address_block { const uschar *origaddress; /* caseful; keep these two first, in */ uschar *address; /* step with the block above */ int expand_setup; - BOOL caseless; + mcs_flags flags; /* MCS_CASELESS, MCS_TEXTONLY_RE */ } check_address_block; @@ -93,9 +92,10 @@ Returns: OK if matched */ static int -check_string(void *arg, const uschar *pattern, const uschar **valueptr, uschar **error) +check_string(void * arg, const uschar * pattern, const uschar ** valueptr, + uschar ** error) { -const check_string_block *cb = arg; +const check_string_block * cb = arg; int search_type, partial, affixlen, starflags; int expand_setup = cb->expand_setup; const uschar * affix, * opts; @@ -128,7 +128,8 @@ required. */ if (pattern[0] == '^') { - const pcre2_code * re = regex_must_compile(pattern, cb->caseless, FALSE); + const pcre2_code * re = regex_must_compile(pattern, + cb->flags & (MCS_CACHEABLE | MCS_CASELESS), FALSE); if (expand_setup < 0 ? !regex_match(re, s, -1, NULL) : !regex_match_and_setup(re, s, 0, expand_setup) @@ -147,7 +148,7 @@ if (pattern[0] == '*') patlen = Ustrlen(++pattern); if (patlen > slen) return FAIL; - if (cb->caseless + if (cb->flags & MCS_CASELESS ? strncmpic(s + slen - patlen, pattern, patlen) != 0 : Ustrncmp(s + slen - patlen, pattern, patlen) != 0) return FAIL; @@ -166,7 +167,7 @@ the primary host name - implement this by changing the pattern. For the other cases we have to do some more work. If we don't recognize a special pattern, just fall through - the match will fail. */ -if (cb->at_is_special && pattern[0] == '@') +if (cb->flags & MCS_AT_SPECIAL && pattern[0] == '@') { if (pattern[1] == 0) { @@ -260,10 +261,10 @@ NOT_AT_SPECIAL: if ((semicolon = Ustrchr(pattern, ';')) == NULL) { - if (cb->caseless ? strcmpic(s, pattern) != 0 : Ustrcmp(s, pattern) != 0) + if (cb->flags & MCS_CASELESS ? strcmpic(s, pattern) != 0 : Ustrcmp(s, pattern) != 0) return FAIL; - if (expand_setup >= 0) expand_nmax = expand_setup; /* Original code! $0 gets the matched subject */ - if (valueptr) *valueptr = pattern; /* "value" gets the pattern */ + if (expand_setup >= 0) expand_nmax = expand_setup; /* $0 gets the matched subject */ + if (valueptr) *valueptr = pattern; /* "value" gets the pattern */ return OK; } @@ -280,7 +281,7 @@ if (search_type < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", /* Partial matching is not appropriate for certain lookups (e.g. when looking up user@domain for sender rejection). There's a flag to disable it. */ -if (!cb->use_partial) partial = -1; +if (!(cb->flags & MCS_PARTIAL)) partial = -1; /* Set the parameters for the three different kinds of lookup. */ @@ -316,9 +317,10 @@ Arguments: s the subject string to be checked pattern the pattern to check it against expand_setup expansion setup option (see check_string()) - use_partial if FALSE, override any partial- search types - caseless TRUE for caseless matching where possible - at_is_special TRUE to recognize @, @[], etc. + flags + use_partial if FALSE, override any partial- search types + caseless TRUE for caseless matching where possible + at_is_special TRUE to recognize @, @[], etc. valueptr if not NULL, and a file lookup was done, return the result here instead of discarding it; else set it to point to NULL @@ -328,16 +330,14 @@ Returns: OK if matched */ int -match_check_string(const uschar *s, const uschar *pattern, int expand_setup, - BOOL use_partial, BOOL caseless, BOOL at_is_special, const uschar **valueptr) +match_check_string(const uschar * s, const uschar * pattern, int expand_setup, + mcs_flags flags, const uschar ** valueptr) { check_string_block cb; cb.origsubject = s; -cb.subject = caseless ? string_copylc(s) : string_copy(s); +cb.subject = flags & MCS_CASELESS ? string_copylc(s) : string_copy(s); cb.expand_setup = expand_setup; -cb.use_partial = use_partial; -cb.caseless = caseless; -cb.at_is_special = at_is_special; +cb.flags = flags; return check_string(&cb, pattern, valueptr, NULL); } @@ -364,14 +364,9 @@ switch(type) { case MCL_STRING: case MCL_DOMAIN: - case MCL_LOCALPART: - return ((check_string_block *)arg)->subject; - - case MCL_HOST: - return ((check_host_block *)arg)->host_address; - - case MCL_ADDRESS: - return ((check_address_block *)arg)->address; + case MCL_LOCALPART: return ((check_string_block *)arg)->subject; + case MCL_HOST: return ((check_host_block *)arg)->host_address; + case MCL_ADDRESS: return ((check_address_block *)arg)->address; } return US""; /* In practice, should never happen */ } @@ -432,21 +427,20 @@ match_check_list(const uschar **listptr, int sep, tree_node **anchorptr, void *arg, int type, const uschar *name, const uschar **valueptr) { int yield = OK; -unsigned int *original_cache_bits = *cache_ptr; -BOOL include_unknown = FALSE; -BOOL ignore_unknown = FALSE; -BOOL include_defer = FALSE; -BOOL ignore_defer = FALSE; +unsigned int * original_cache_bits = *cache_ptr; +BOOL include_unknown = FALSE, ignore_unknown = FALSE, + include_defer = FALSE, ignore_defer = FALSE; const uschar *list; uschar *sss; uschar *ot = NULL; +BOOL textonly_re; /* Save time by not scanning for the option name when we don't need it. */ HDEBUG(D_any) { - uschar *listname = readconf_find_option(listptr); - if (listname[0] != 0) ot = string_sprintf("%s in %s?", name, listname); + uschar * listname = readconf_find_option(listptr); + if (*listname) ot = string_sprintf("%s in %s?", name, listname); } /* If the list is empty, the answer is no. Skip the debugging output for @@ -454,7 +448,7 @@ an unnamed list. */ if (!*listptr) { - HDEBUG(D_lists) if (ot) debug_printf("%s no (option unset)\n", ot); + HDEBUG(D_lists) if (ot) debug_printf_indent("%s no (option unset)\n", ot); return FAIL; } @@ -467,6 +461,7 @@ if (type >= MCL_NOEXPAND) { list = *listptr; type -= MCL_NOEXPAND; /* Remove the "no expand" flag */ + textonly_re = TRUE; } else { @@ -477,17 +472,17 @@ else { check_string_block *cb = (check_string_block *)arg; deliver_domain = string_copy(cb->subject); - list = expand_cstring(*listptr); + list = expand_string_2(*listptr, &textonly_re); deliver_domain = NULL; } else - list = expand_cstring(*listptr); + list = expand_string_2(*listptr, &textonly_re); if (!list) { if (f.expand_string_forcedfail) { - HDEBUG(D_lists) debug_printf("expansion of \"%s\" forced failure: " + HDEBUG(D_lists) debug_printf_indent("expansion of \"%s\" forced failure: " "assume not in this list\n", *listptr); return FAIL; } @@ -497,6 +492,15 @@ else } } +if (textonly_re) switch (type) + { + case MCL_STRING: + case MCL_DOMAIN: + case MCL_LOCALPART: ((check_string_block *)arg)->flags |= MCS_CACHEABLE; break; + case MCL_HOST: ((check_host_block *)arg)->flags |= MCS_CACHEABLE; break; + case MCL_ADDRESS: ((check_address_block *)arg)->flags |= MCS_CACHEABLE; break; + } + /* For an unnamed list, use the expanded version in comments */ #define LIST_LIMIT_PR 2048 @@ -532,7 +536,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0))) if (at) Ustrncpy(cb->address, cb->origaddress, at - cb->origaddress); - cb->caseless = FALSE; + cb->flags &= ~MCS_CASELESS; continue; } } @@ -545,7 +549,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0))) { check_string_block *cb = (check_string_block *)arg; Ustrcpy(US cb->subject, cb->origsubject); - cb->caseless = FALSE; + cb->flags &= ~MCS_CASELESS; continue; } } @@ -669,7 +673,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0))) so we use the permanent store pool */ store_pool = POOL_PERM; - p = store_get(sizeof(namedlist_cacheblock), FALSE); + p = store_get(sizeof(namedlist_cacheblock), GET_UNTAINTED); p->key = string_copy(get_check_key(arg, type)); @@ -679,7 +683,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0))) p->next = nb->cache_data; nb->cache_data = p; if (*valueptr) - DEBUG(D_lists) debug_printf("data from lookup saved for " + DEBUG(D_lists) debug_printf_indent("data from lookup saved for " "cache for %s: key '%s' value '%s'\n", ss, p->key, *valueptr); } } @@ -691,7 +695,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0))) else { - DEBUG(D_lists) debug_printf("cached %s match for %s\n", + DEBUG(D_lists) debug_printf_indent("cached %s match for %s\n", (bits & (-bits)) == bits ? "yes" : "no", ss); cached = US" - cached"; @@ -705,7 +709,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0))) *valueptr = p->data; break; } - DEBUG(D_lists) debug_printf("cached lookup data = %s\n", *valueptr); + DEBUG(D_lists) debug_printf_indent("cached lookup data = %s\n", *valueptr); } } @@ -714,7 +718,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0))) if ((bits & (-bits)) == bits) /* Only one of the two bits is set */ { - HDEBUG(D_lists) debug_printf("%s %s (matched \"%s\"%s)\n", ot, + HDEBUG(D_lists) debug_printf_indent("%s %s (matched \"%s\"%s)\n", ot, yield == OK ? "yes" : "no", sss, cached); return yield; } @@ -728,7 +732,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0))) switch ((func)(arg, ss, valueptr, &error)) { case OK: - HDEBUG(D_lists) debug_printf("%s %s (matched \"%s\")\n", ot, + HDEBUG(D_lists) debug_printf_indent("%s %s (matched \"%s\")\n", ot, (yield == OK)? "yes" : "no", sss); return yield; @@ -737,7 +741,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0))) error = string_sprintf("DNS lookup of \"%s\" deferred", ss); if (ignore_defer) { - HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_defer\n", + HDEBUG(D_lists) debug_printf_indent("%s: item ignored by +ignore_defer\n", error); break; } @@ -757,12 +761,12 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0))) case ERROR: if (ignore_unknown) { - HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_unknown\n", + HDEBUG(D_lists) debug_printf_indent("%s: item ignored by +ignore_unknown\n", error); } else { - HDEBUG(D_lists) debug_printf("%s %s (%s)\n", ot, + HDEBUG(D_lists) debug_printf_indent("%s %s (%s)\n", ot, include_unknown? "yes":"no", error); if (!include_unknown) { @@ -841,7 +845,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0))) { case OK: (void)fclose(f); - HDEBUG(D_lists) debug_printf("%s %s (matched \"%s\" in %s)\n", ot, + HDEBUG(D_lists) debug_printf_indent("%s %s (matched \"%s\" in %s)\n", ot, yield == OK ? "yes" : "no", sss, filename); /* The "pattern" being matched came from the file; we use a stack-local. @@ -855,7 +859,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0))) error = string_sprintf("DNS lookup of %s deferred", ss); if (ignore_defer) { - HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_defer\n", + HDEBUG(D_lists) debug_printf_indent("%s: item ignored by +ignore_defer\n", error); break; } @@ -870,12 +874,12 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0))) case ERROR: /* host name lookup failed - this can only */ if (ignore_unknown) /* be for an incoming host (not outgoing) */ { - HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_unknown\n", + HDEBUG(D_lists) debug_printf_indent("%s: item ignored by +ignore_unknown\n", error); } else { - HDEBUG(D_lists) debug_printf("%s %s (%s)\n", ot, + HDEBUG(D_lists) debug_printf_indent("%s %s (%s)\n", ot, include_unknown? "yes":"no", error); (void)fclose(f); if (!include_unknown) @@ -901,7 +905,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0))) /* End of list reached: if the last item was negated yield OK, else FAIL. */ HDEBUG(D_lists) - debug_printf("%s %s (end of list)\n", ot, yield == OK ? "no":"yes"); + debug_printf_indent("%s %s (end of list)\n", ot, yield == OK ? "no":"yes"); return yield == OK ? FAIL : OK; /* Something deferred */ @@ -960,15 +964,13 @@ unsigned int *local_cache_bits = cache_bits; check_string_block cb; cb.origsubject = s; cb.subject = caseless ? string_copylc(s) : string_copy(s); -cb.at_is_special = FALSE; +cb.flags = caseless ? MCS_PARTIAL+MCS_CASELESS : MCS_PARTIAL; switch (type & ~MCL_NOEXPAND) { - case MCL_DOMAIN: cb.at_is_special = TRUE; /*FALLTHROUGH*/ + case MCL_DOMAIN: cb.flags |= MCS_AT_SPECIAL; /*FALLTHROUGH*/ case MCL_LOCALPART: cb.expand_setup = 0; break; default: cb.expand_setup = sep > UCHAR_MAX ? 0 : -1; break; } -cb.use_partial = TRUE; -cb.caseless = caseless; if (valueptr) *valueptr = NULL; return match_check_list(listptr, sep, anchorptr, &local_cache_bits, check_string, &cb, type, s, valueptr); @@ -1005,19 +1007,21 @@ Returns: OK for a match */ static int -check_address(void *arg, const uschar *pattern, const uschar **valueptr, uschar **error) +check_address(void * arg, const uschar * pattern, const uschar ** valueptr, + uschar ** error) { -check_address_block *cb = (check_address_block *)arg; +check_address_block * cb = (check_address_block *)arg; check_string_block csb; int rc; int expand_inc = 0; -unsigned int *null = NULL; -const uschar *listptr; -uschar *subject = cb->address; -const uschar *s; -uschar *pdomain, *sdomain; - -DEBUG(D_lists) debug_printf("address match test: subject=%s pattern=%s\n", +unsigned int * null = NULL; +const uschar * listptr; +uschar * subject = cb->address; +const uschar * s; +uschar * pdomain, * sdomain; +uschar * value = NULL; + +DEBUG(D_lists) debug_printf_indent("address match test: subject=%s pattern=%s\n", subject, pattern); /* Find the subject's domain */ @@ -1027,7 +1031,7 @@ sdomain = Ustrrchr(subject, '@'); /* The only case where a subject may not have a domain is if the subject is empty. Otherwise, a subject with no domain is a serious configuration error. */ -if (sdomain == NULL && *subject != 0) +if (!sdomain && *subject) { log_write(0, LOG_MAIN|LOG_PANIC, "no @ found in the subject of an " "address list match: subject=\"%s\" pattern=\"%s\"", subject, pattern); @@ -1038,14 +1042,14 @@ if (sdomain == NULL && *subject != 0) This may be the empty address. */ if (*pattern == '^') - return match_check_string(subject, pattern, cb->expand_setup, TRUE, - cb->caseless, FALSE, NULL); + return match_check_string(subject, pattern, cb->expand_setup, + cb->flags | MCS_PARTIAL, NULL); /* Handle a pattern that is just a lookup. Skip over possible lookup names (letters, digits, hyphens). Skip over a possible * or *@ at the end. Then we must have a semicolon for it to be a lookup. */ -for (s = pattern; isalnum(*s) || *s == '-'; s++); +for (s = pattern; isalnum(*s) || *s == '-'; s++) ; if (*s == '*') s++; if (*s == '@') s++; @@ -1058,15 +1062,14 @@ if (*s == ';') if (Ustrncmp(pattern, "partial-", 8) == 0) log_write(0, LOG_MAIN|LOG_PANIC, "partial matching is not applicable to " "whole-address lookups: ignored \"partial-\" in \"%s\"", pattern); - return match_check_string(subject, pattern, -1, FALSE, cb->caseless, FALSE, - valueptr); + return match_check_string(subject, pattern, -1, cb->flags, valueptr); } /* For the remaining cases, an empty subject matches only an empty pattern, because other patterns expect to have a local part and a domain to match against. */ -if (*subject == 0) return (*pattern == 0)? OK : FAIL; +if (!*subject) return *pattern ? FAIL : OK; /* If the pattern starts with "@@" we have a split lookup, where the domain is looked up to obtain a list of local parts. If the subject's local part is just @@ -1086,19 +1089,20 @@ if (pattern[0] == '@' && pattern[1] == '@') { int sep = 0; - if ((rc = match_check_string(key, pattern + 2, -1, TRUE, FALSE, FALSE, - CUSS &list)) != OK) return rc; + if ((rc = match_check_string(key, pattern + 2, -1, MCS_PARTIAL, CUSS &list)) + != OK) + return rc; /* Check for chaining from the last item; set up the next key if one is found. */ ss = Ustrrchr(list, ':'); - if (ss == NULL) ss = list; else ss++; - while (isspace(*ss)) ss++; + if (!ss) ss = list; else ss++; + Uskip_whitespace(&ss); if (*ss == '>') { *ss++ = 0; - while (isspace(*ss)) ss++; + Uskip_whitespace(&ss); key = string_copy(ss); } else key = NULL; @@ -1118,8 +1122,7 @@ if (pattern[0] == '@' && pattern[1] == '@') else local_yield = OK; *sdomain = 0; - rc = match_check_string(subject, ss, -1, TRUE, cb->caseless, FALSE, - valueptr); + rc = match_check_string(subject, ss, -1, cb->flags + MCS_PARTIAL, valueptr); *sdomain = '@'; switch(rc) @@ -1149,8 +1152,7 @@ if (pattern[0] == '@' && pattern[1] == '@') /* We get here if the pattern is not a lookup or a regular expression. If it contains an @ there is both a local part and a domain. */ -pdomain = Ustrrchr(pattern, '@'); -if (pdomain != NULL) +if ((pdomain = Ustrrchr(pattern, '@'))) { int pllen, sllen; @@ -1178,7 +1180,7 @@ if (pdomain != NULL) { int cllen = pllen - 1; if (sllen < cllen) return FAIL; - if (cb->caseless + if (cb->flags & MCS_CASELESS ? strncmpic(subject+sllen-cllen, pattern + 1, cllen) != 0 : Ustrncmp(subject+sllen-cllen, pattern + 1, cllen) != 0) return FAIL; @@ -1188,14 +1190,16 @@ if (pdomain != NULL) expand_nlength[cb->expand_setup] = sllen - cllen; expand_inc = 1; } + value = string_copyn(pattern + 1, cllen); } else { if (sllen != pllen) return FAIL; - if (cb->caseless + if (cb->flags & MCS_CASELESS ? strncmpic(subject, pattern, sllen) != 0 : Ustrncmp(subject, pattern, sllen) != 0) return FAIL; } + value = string_copyn(pattern, sllen); } /* If the local part matched, or was not being checked, check the domain using @@ -1204,32 +1208,38 @@ original code read as follows: return match_check_string(sdomain + 1, pdomain ? pdomain + 1 : pattern, - cb->expand_setup + expand_inc, TRUE, cb->caseless, TRUE, NULL); + cb->expand_setup + expand_inc, cb->flags, NULL); This supported only literal domains and *.x.y patterns. In order to allow for -named domain lists (so that you can right, for example, "senders=+xxxx"), it +named domain lists (so that you can write, for example, "senders=+xxxx"), it was changed to use the list scanning function. */ csb.origsubject = sdomain + 1; -csb.subject = cb->caseless ? string_copylc(sdomain+1) : string_copy(sdomain+1); +csb.subject = cb->flags & MCS_CASELESS + ? string_copylc(sdomain+1) : string_copy(sdomain+1); csb.expand_setup = cb->expand_setup + expand_inc; -csb.use_partial = TRUE; -csb.caseless = cb->caseless; -csb.at_is_special = TRUE; +csb.flags = MCS_PARTIAL | MCS_AT_SPECIAL | cb->flags & MCS_CASELESS; listptr = pdomain ? pdomain + 1 : pattern; if (valueptr) *valueptr = NULL; -return match_check_list( - &listptr, /* list of one item */ - UCHAR_MAX+1, /* impossible separator; single item */ - &domainlist_anchor, /* it's a domain list */ - &null, /* ptr to NULL means no caching */ - check_string, /* the function to do one test */ - &csb, /* its data */ - MCL_DOMAIN + MCL_NOEXPAND, /* domain list; don't expand */ - csb.subject, /* string for messages */ - valueptr); /* where to pass back lookup data */ + { + const uschar * dvalue = NULL; + rc = match_check_list( + &listptr, /* list of one item */ + UCHAR_MAX+1, /* impossible separator; single item */ + &domainlist_anchor, /* it's a domain list */ + &null, /* ptr to NULL means no caching */ + check_string, /* the function to do one test */ + &csb, /* its data */ + MCL_DOMAIN + MCL_NOEXPAND, /* domain list; don't expand */ + csb.subject, /* string for messages */ + &dvalue); /* where to pass back lookup data */ + if (valueptr && (value || dvalue)) + *valueptr = string_sprintf("%s@%s", + value ? value : US"", dvalue ? dvalue : US""); + } +return rc; } @@ -1313,10 +1323,10 @@ if (expand_setup == 0) ab.origaddress = address; /* ab.address is above */ ab.expand_setup = expand_setup; -ab.caseless = caseless; +ab.flags = caseless ? MCS_CASELESS : 0; return match_check_list(listptr, sep, &addresslist_anchor, &local_cache_bits, - check_address, &ab, MCL_ADDRESS + (expand? 0:MCL_NOEXPAND), address, + check_address, &ab, MCL_ADDRESS + (expand ? 0 : MCL_NOEXPAND), address, valueptr); }