Explicit log of failiing bind addr & port
[exim.git] / src / src / match.c
index 6a331419422b8a06134206e7c46d4f3d6b4af35a..07070362df3199f2956aceee614810c33b3bc831 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 */
 /* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 
 /* 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,9 +128,10 @@ required. */
 
 if (pattern[0] == '^')
   {
-  const pcre * 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
-      ? pcre_exec(re, NULL, CCS s, Ustrlen(s), 0, PCRE_EOPT, NULL, 0) < 0
+      ? !regex_match(re, s, -1, NULL)
       : !regex_match_and_setup(re, s, 0, expand_setup)
      )
     return FAIL;
@@ -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;
-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
@@ -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,9 +492,33 @@ 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
 
-HDEBUG(D_any) if (!ot) ot = string_sprintf("%s in \"%s\"?", name, list);
+HDEBUG(D_any) if (!ot)
+  {
+  int n, m;
+  gstring * g = string_fmt_append(NULL, "%s in \"%n%.*s%n\"",
+    name, &n, LIST_LIMIT_PR, list, &m);
+  if (m - n >= LIST_LIMIT_PR) g = string_catn(g, US"...", 3);
+  g = string_catn(g, US"?", 1);
+  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. */
@@ -508,6 +527,8 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
   {
   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
@@ -522,7 +543,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;
       }
     }
@@ -535,7 +556,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;
       }
     }
@@ -591,14 +612,15 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
     {
     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\"",
@@ -607,7 +629,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
           type == MCL_ADDRESS ?   " address" :
           type == MCL_LOCALPART ? " local part" : "",
           ss);
-       return DEFER;
+       goto DEFER_RETURN;
        }
       nb = t->data.ptr;
 
@@ -631,8 +653,12 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
 
       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;
@@ -659,7 +685,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));
 
 
@@ -669,7 +695,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);
             }
           }
@@ -681,8 +707,12 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
 
       else
         {
-        DEBUG(D_lists) debug_printf("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)
@@ -695,7 +725,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);
           }
         }
 
@@ -704,9 +734,9 @@ 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,
-          (yield == OK)? "yes" : "no", sss, cached);
-        return yield;
+        HDEBUG(D_lists) debug_printf_indent("%s %s (matched \"%s\"%s)\n", ot,
+          yield == OK ? "yes" : "no", sss, cached);
+       goto YIELD_RETURN;
         }
       }
 
@@ -718,16 +748,16 @@ 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;
+         goto YIELD_RETURN;
 
         case DEFER:
          if (!error)
            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;
            }
@@ -747,12 +777,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)
              {
@@ -809,19 +839,19 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
         sss = ss + 1;
         }
 
-      ss = filebuffer + Ustrlen(filebuffer);             /* trailing space */
+      ss = filebuffer + Ustrlen(filebuffer);           /* trailing space */
       while (ss > filebuffer && isspace(ss[-1])) ss--;
       *ss = 0;
 
       ss = filebuffer;
-      while (isspace(*ss)) ss++;                         /* leading space */
+      while (isspace(*ss)) ss++;                       /* leading space */
 
-      if (*ss == 0) continue;                            /* ignore empty */
+      if (!*ss) continue;                              /* ignore empty */
 
-      file_yield = yield;                                /* positive yield */
-      sss = ss;                                          /* for debugging */
+      file_yield = yield;                              /* positive yield */
+      sss = ss;                                                /* for debugging */
 
-      if (*ss == '!')                                    /* negation */
+      if (*ss == '!')                                  /* negation */
         {
         file_yield = (file_yield == OK)? FAIL : OK;
         while (isspace((*(++ss))));
@@ -831,46 +861,50 @@ 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);
-         return file_yield;
+
+         /* The "pattern" being matched came from the file; we use a stack-local.
+         Copy it to allocated memory now we know it matched. */
+
+         if (valueptr) *valueptr = string_copy(ss);
+         yield = file_yield;
+         goto YIELD_RETURN;
 
         case DEFER:
          if (!error)
            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;
            }
          (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) */
            {
-           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)
              {
              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;
            }
         }
       }
@@ -886,14 +920,33 @@ 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");
-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;
 }
 
 
@@ -915,6 +968,7 @@ Arguments:
   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
@@ -945,15 +999,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);
@@ -990,19 +1042,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 */
@@ -1012,7 +1066,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);
@@ -1023,14 +1077,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++;
 
@@ -1043,15 +1097,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
@@ -1061,7 +1114,6 @@ if (pattern[0] == '@' && pattern[1] == '@')
   {
   int watchdog = 50;
   uschar *list, *ss;
-  uschar buffer[1024];
 
   if (sdomain == subject + 1 && *subject == '*') return FAIL;
 
@@ -1072,19 +1124,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;
@@ -1092,7 +1145,7 @@ if (pattern[0] == '@' && pattern[1] == '@')
     /* Look up the local parts provided by the list; negation is permitted.
     If a local part has to begin with !, a regex can be used. */
 
-    while ((ss = string_nextinlist(CUSS &list, &sep, buffer, sizeof(buffer))))
+    while ((ss = string_nextinlist(CUSS &list, &sep, NULL, 0)))
       {
       int local_yield;
 
@@ -1104,8 +1157,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)
@@ -1135,8 +1187,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;
 
@@ -1164,7 +1215,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;
@@ -1174,14 +1225,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
@@ -1190,32 +1243,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;
 }
 
 
@@ -1269,9 +1328,11 @@ compared. Therefore, Exim now forces the entire address into lower case here,
 provided that "caseless" is set. (It is FALSE for calls for matching rewriting
 patterns.) Otherwise just the domain is lower cases. A magic item "+caseful" in
 the list can be used to restore a caseful copy of the local part from the
-original address. */
+original address.
+Limit the subject address size to avoid mem-exhaustion attacks.  The size chosen
+is historical (we used to use big_buffer here). */
 
-if ((len = Ustrlen(address)) > 255) len = 255;
+if ((len = Ustrlen(address)) > BIG_BUFFER_SIZE) len = BIG_BUFFER_SIZE;
 ab.address = string_copyn(address, len);
 
 for (uschar * p = ab.address + len - 1; p >= ab.address; p--)
@@ -1297,10 +1358,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);
 }