SPDX: license tags (mostly by guesswork)
[exim.git] / src / src / match.c
index 0d2cdb57d8399a9f1a8478414ec387527f328cf1..a877aef3b65d87a08fc56480a5d94c07031d71b8 100644 (file)
@@ -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 */
 }
@@ -438,6 +433,7 @@ BOOL include_unknown = FALSE, ignore_unknown = 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. */
 
@@ -465,6 +461,7 @@ if (type >= MCL_NOEXPAND)
   {
   list = *listptr;
   type -= MCL_NOEXPAND;       /* Remove the "no expand" flag */
+  textonly_re = TRUE;
   }
 else
   {
@@ -475,11 +472,11 @@ 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)
     {
@@ -495,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
 
@@ -530,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;
       }
     }
@@ -543,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;
       }
     }
@@ -958,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);
@@ -1003,7 +1007,8 @@ 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_string_block csb;
@@ -1026,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);
@@ -1037,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++;
 
@@ -1057,8 +1062,7 @@ 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,
@@ -1085,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;
@@ -1117,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)
@@ -1148,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;
 
@@ -1177,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;
@@ -1192,7 +1195,7 @@ if (pdomain != NULL)
   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;
     }
@@ -1205,18 +1208,17 @@ 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;
@@ -1321,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);
 }