* 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-or-later */
/* Functions for matching strings */
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;
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;
*/
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;
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)
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;
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)
{
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;
}
/* 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. */
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
*/
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);
}
{
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 */
}
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;
-const uschar *list;
-uschar *sss;
-uschar *ot = NULL;
+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
{
list = *listptr;
type -= MCL_NOEXPAND; /* Remove the "no expand" flag */
+ textonly_re = TRUE;
}
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 (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
gstring_release_unused(g);
ot = string_from_gstring(g);
}
+HDEBUG(D_lists)
+ {
+ debug_printf_indent("%s\n", ot);
+ expand_level++;
+ }
/* Now scan the list and process each item in turn, until one of them matches,
or we hit an error. */
{
uschar * ss = sss;
+ HDEBUG(D_lists) debug_printf_indent("list element: %s\n", ss);
+
/* Address lists may contain +caseful, to restore caseful matching of the
local part. We have to know the layout of the control block, unfortunately.
The lower cased address is in a temporary buffer, so we just copy the local
if (at)
Ustrncpy(cb->address, cb->origaddress, at - cb->origaddress);
- cb->caseless = FALSE;
+ cb->flags &= ~MCS_CASELESS;
continue;
}
}
{
check_string_block *cb = (check_string_block *)arg;
Ustrcpy(US cb->subject, cb->origsubject);
- cb->caseless = FALSE;
+ cb->flags &= ~MCS_CASELESS;
continue;
}
}
{
if (*ss == '+' && anchorptr)
{
- int bits = 0;
- int offset = 0;
- int shift = 0;
- unsigned int *use_cache_bits = original_cache_bits;
- uschar *cached = US"";
- namedlist_block *nb;
+ int bits = 0, offset = 0, shift = 0;
+ unsigned int * use_cache_bits = original_cache_bits;
+ uschar * cached = US"";
+ namedlist_block * nb;
tree_node * t;
+ DEBUG(D_lists)
+ { debug_printf_indent(" start sublist %s\n", ss+1); expand_level += 2; }
+
if (!(t = tree_search(*anchorptr, ss+1)))
{
log_write(0, LOG_MAIN|LOG_PANIC, "unknown named%s list \"%s\"",
type == MCL_ADDRESS ? " address" :
type == MCL_LOCALPART ? " local part" : "",
ss);
- return DEFER;
+ goto DEFER_RETURN;
}
nb = t->data.ptr;
if (bits == 0)
{
- switch (match_check_list(&(nb->string), 0, anchorptr, &use_cache_bits,
- func, arg, type, name, valueptr))
+ int res = match_check_list(&(nb->string), 0, anchorptr, &use_cache_bits,
+ func, arg, type, name, valueptr);
+ DEBUG(D_lists)
+ { expand_level -= 2; debug_printf_indent(" end sublist %s\n", ss+1); }
+
+ switch (res)
{
case OK: bits = 1; break;
case FAIL: bits = 3; break;
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));
else
{
- DEBUG(D_lists) debug_printf_indent("cached %s match for %s\n",
- (bits & (-bits)) == bits ? "yes" : "no", ss);
+ DEBUG(D_lists)
+ {
+ expand_level -= 2;
+ debug_printf_indent("cached %s match for %s\n",
+ (bits & (-bits)) == bits ? "yes" : "no", ss);
+ }
cached = US" - cached";
if (valueptr)
{
HDEBUG(D_lists) debug_printf_indent("%s %s (matched \"%s\"%s)\n", ot,
yield == OK ? "yes" : "no", sss, cached);
- return yield;
+ goto YIELD_RETURN;
}
}
case OK:
HDEBUG(D_lists) debug_printf_indent("%s %s (matched \"%s\")\n", ot,
(yield == OK)? "yes" : "no", sss);
- return yield;
+ goto YIELD_RETURN;
case DEFER:
if (!error)
Copy it to allocated memory now we know it matched. */
if (valueptr) *valueptr = string_copy(ss);
- return file_yield;
+ yield = file_yield;
+ goto YIELD_RETURN;
case DEFER:
if (!error)
break;
}
(void)fclose(f);
- if (include_defer)
- {
- log_write(0, LOG_MAIN, "%s: accepted by +include_defer", error);
- return OK;
- }
- goto DEFER_RETURN;
+ if (!include_defer)
+ goto DEFER_RETURN;
+ log_write(0, LOG_MAIN, "%s: accepted by +include_defer", error);
+ goto OK_RETURN;
case ERROR: /* host name lookup failed - this can only */
if (ignore_unknown) /* be for an incoming host (not outgoing) */
{
if (LOGGING(unknown_in_list))
log_write(0, LOG_MAIN, "list matching forced to fail: %s", error);
- return FAIL;
+ goto FAIL_RETURN;
}
log_write(0, LOG_MAIN, "%s: accepted by +include_unknown", error);
- return OK;
+ goto OK_RETURN;
}
}
}
/* End of list reached: if the last item was negated yield OK, else FAIL. */
HDEBUG(D_lists)
- debug_printf_indent("%s %s (end of list)\n", ot, yield == OK ? "no":"yes");
-return yield == OK ? FAIL : OK;
+ HDEBUG(D_lists)
+ {
+ expand_level--;
+ debug_printf_indent("%s %s (end of list)\n", ot, yield == OK ? "no":"yes");
+ }
+ return yield == OK ? FAIL : OK;
/* Something deferred */
DEFER_RETURN:
-HDEBUG(D_lists) debug_printf("%s list match deferred for %s\n", ot, sss);
-return DEFER;
+ HDEBUG(D_lists)
+ {
+ expand_level--;
+ debug_printf_indent("%s list match deferred for %s\n", ot, sss);
+ }
+ return DEFER;
+
+FAIL_RETURN:
+ yield = FAIL;
+ goto YIELD_RETURN;
+
+OK_RETURN:
+ yield = OK;
+
+YIELD_RETURN:
+ HDEBUG(D_lists) expand_level--;
+ return yield;
}
s string to search for
listptr ptr to ptr to colon separated list of patterns, or NULL
sep a separator value for the list (see string_nextinlist())
+ or zero for auto
anchorptr ptr to tree for named items, or NULL if no named items
cache_bits ptr to cache_bits for ditto, or NULL if not caching
type MCL_DOMAIN when matching a domain list
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);
*/
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;
+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);
/* 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);
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++;
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
{
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;
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)
/* 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;
{
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;
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
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;
}
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);
}