Copyright updates:
[exim.git] / src / src / match.c
index 106cb6a356551bf87b9babf003b5c0817df59bef..e07d0bd59fa5ace12f58e721195dee5b48f55dc2 100644 (file)
@@ -1,10 +1,9 @@
-/* $Cambridge: exim/src/src/match.c,v 1.5 2005/02/17 11:58:26 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for matching strings */
@@ -17,8 +16,8 @@
 strings, domains, and local parts. */
 
 typedef struct check_string_block {
-  uschar *origsubject;               /* caseful; keep these two first, in */
-  uschar *subject;                   /* step with the block below */
+  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;
@@ -30,7 +29,7 @@ typedef struct check_string_block {
 addresses. */
 
 typedef struct check_address_block {
-  uschar *origaddress;               /* caseful; keep these two first, in */
+  const uschar *origaddress;         /* caseful; keep these two first, in */
   uschar *address;                   /* step with the block above */
   int    expand_setup;
   BOOL   caseless;
@@ -74,7 +73,8 @@ Arguments:
                    returns ERROR)
 
 Contents of the argument block:
-  subject        the subject string to be checked
+  origsubject    the subject in its original casing
+  subject        the subject string to be checked, lowercased if caseless
   expand_setup   if < 0, don't set up any numeric expansion variables;
                  if = 0, set $0 to whole subject, and either
                    $1 to what matches * or
@@ -93,13 +93,13 @@ Returns:       OK    if matched
 */
 
 static int
-check_string(void *arg, uschar *pattern, uschar **valueptr, uschar **error)
+check_string(void *arg, const uschar *pattern, const uschar **valueptr, uschar **error)
 {
-check_string_block *cb = (check_string_block *)arg;
+const check_string_block *cb = arg;
 int search_type, partial, affixlen, starflags;
 int expand_setup = cb->expand_setup;
-uschar *affix;
-uschar *s = cb->subject;
+const uschar * affix, * opts;
+uschar *s;
 uschar *filename = NULL;
 uschar *keyquery, *result, *semicolon;
 void *handle;
@@ -108,6 +108,12 @@ error = error;  /* Keep clever compilers from complaining */
 
 if (valueptr != NULL) *valueptr = NULL;  /* For non-lookup matches */
 
+/* For regular expressions, use cb->origsubject rather than cb->subject so that
+it works if the pattern uses (?-i) to turn off case-independence, overriding
+"caseless". */
+
+s = string_copy(pattern[0] == '^' ? cb->origsubject : cb->subject);
+
 /* If required to set up $0, initialize the data but don't turn on by setting
 expand_nmax until the match is assured. */
 
@@ -126,7 +132,7 @@ if (pattern[0] == '^')
   {
   const pcre *re = regex_must_compile(pattern, cb->caseless, FALSE);
   return ((expand_setup < 0)?
-           pcre_exec(re, NULL, CS s, Ustrlen(s), 0, PCRE_EOPT, NULL, 0) >= 0
+           pcre_exec(re, NULL, CCS s, Ustrlen(s), 0, PCRE_EOPT, NULL, 0) >= 0
            :
            regex_match_and_setup(re, s, 0, expand_setup)
          )?
@@ -170,11 +176,12 @@ if (cb->at_is_special && pattern[0] == '@')
 
   if (Ustrcmp(pattern, "@[]") == 0)
     {
-    ip_address_item *ip;
     int slen = Ustrlen(s);
     if (s[0] != '[' && s[slen-1] != ']') return FAIL;
-    for (ip = host_find_interfaces(); ip != NULL; ip = ip->next)
-      if (Ustrncmp(ip->address, s+1, slen - 2) == 0) return OK;
+    for (ip_address_item * ip = host_find_interfaces(); ip; ip = ip->next)
+      if (Ustrncmp(ip->address, s+1, slen - 2) == 0
+            && ip->address[slen - 2] == 0)
+        return OK;
     return FAIL;
     }
 
@@ -185,8 +192,8 @@ if (cb->at_is_special && pattern[0] == '@')
     BOOL prim = FALSE;
     BOOL secy = FALSE;
     BOOL removed = FALSE;
-    uschar *ss = pattern + 4;
-    uschar *ignore_target_hosts = NULL;
+    const uschar *ss = pattern + 4;
+    const uschar *ignore_target_hosts = NULL;
 
     if (strncmpic(ss, US"any", 3) == 0) ss += 3;
     else if (strncmpic(ss, US"primary", 7) == 0)
@@ -214,6 +221,7 @@ if (cb->at_is_special && pattern[0] == '@')
       NULL,                /* service name not relevant */
       NULL,                /* srv_fail_domains not relevant */
       NULL,                /* mx_fail_domains not relevant */
+      NULL,                /* no dnssec request/require XXX ? */
       NULL,                /* no feedback FQDN */
       &removed);           /* feedback if local removed */
 
@@ -256,7 +264,7 @@ the part of the string preceding the semicolon. */
 
 *semicolon = 0;
 search_type = search_findtype_partial(pattern, &partial, &affix, &affixlen,
-  &starflags);
+  &starflags, &opts);
 *semicolon = ';';
 if (search_type < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s",
   search_error_message);
@@ -266,12 +274,20 @@ up user@domain for sender rejection). There's a flag to disable it. */
 
 if (!cb->use_partial) partial = -1;
 
-/* Set the parameters for the two different kinds of lookup. */
+/* Set the parameters for the three different kinds of lookup. */
 
 keyquery = semicolon + 1;
-while (isspace(*keyquery)) keyquery++;
+Uskip_whitespace(&keyquery);
+
+if (mac_islookup(search_type, lookup_absfilequery))
+  {
+  filename = keyquery;
+  while (*keyquery && !isspace(*keyquery)) keyquery++;
+  filename = string_copyn(filename, keyquery - filename);
+  Uskip_whitespace(&keyquery);
+  }
 
-if (!mac_islookup(search_type, lookup_querystyle))
+else if (!mac_islookup(search_type, lookup_querystyle))
   {
   filename = keyquery;
   keyquery = s;
@@ -281,14 +297,13 @@ if (!mac_islookup(search_type, lookup_querystyle))
 for; partial matching is all handled inside search_find(). Note that there is
 no search_close() because of the caching arrangements. */
 
-handle = search_open(filename, search_type, 0, NULL, NULL);
-if (handle == NULL) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s",
-  search_error_message);
+if (!(handle = search_open(filename, search_type, 0, NULL, NULL)))
+  log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", search_error_message);
 result = search_find(handle, filename, keyquery, partial, affix, affixlen,
-  starflags, &expand_setup);
+  starflags, &expand_setup, opts);
 
-if (result == NULL) return search_find_defer? DEFER : FAIL;
-if (valueptr != NULL) *valueptr = result;
+if (!result) return f.search_find_defer ? DEFER : FAIL;
+if (valueptr) *valueptr = result;
 
 expand_nmax = expand_setup;
 return OK;
@@ -320,8 +335,8 @@ Returns:       OK    if matched
 */
 
 int
-match_check_string(uschar *s, uschar *pattern, int expand_setup,
-  BOOL use_partial, BOOL caseless, BOOL at_is_special, uschar **valueptr)
+match_check_string(const uschar *s, const uschar *pattern, int expand_setup,
+  BOOL use_partial, BOOL caseless, BOOL at_is_special, const uschar **valueptr)
 {
 check_string_block cb;
 cb.origsubject = s;
@@ -349,7 +364,7 @@ Arguments:
   type         MCL_STRING, MCL_DOMAIN, MCL_HOST, MCL_ADDRESS, or MCL_LOCALPART
 */
 
-static uschar *
+static const uschar *
 get_check_key(void *arg, int type)
 {
 switch(type)
@@ -415,22 +430,23 @@ Returns:       OK    if matched a non-negated item
                FAIL  if expansion force-failed
                FAIL  if matched a negated item
                FAIL  if hit end of list after a non-negated item
-               DEFER if a lookup deferred or expansion failed
+               DEFER if a something deferred or expansion failed
 */
 
 int
-match_check_list(uschar **listptr, int sep, tree_node **anchorptr,
-  unsigned int **cache_ptr, int (*func)(void *,uschar *,uschar **,uschar **),
-  void *arg, int type, uschar *name, uschar **valueptr)
+match_check_list(const uschar **listptr, int sep, tree_node **anchorptr,
+  unsigned int **cache_ptr, int (*func)(void *,const uschar *,const uschar **,uschar **),
+  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;
-uschar *list;
+BOOL include_defer = FALSE;
+BOOL ignore_defer = FALSE;
+const uschar *list;
 uschar *sss;
 uschar *ot = NULL;
-uschar buffer[1024];
 
 /* Save time by not scanning for the option name when we don't need it. */
 
@@ -443,12 +459,9 @@ HDEBUG(D_any)
 /* If the list is empty, the answer is no. Skip the debugging output for
 an unnamed list. */
 
-if (*listptr == NULL)
+if (!*listptr)
   {
-  HDEBUG(D_lists)
-    {
-    if (ot != NULL) debug_printf("%s no (option unset)\n", ot);
-    }
+  HDEBUG(D_lists) if (ot) debug_printf("%s no (option unset)\n", ot);
   return FAIL;
   }
 
@@ -464,10 +477,22 @@ if (type >= MCL_NOEXPAND)
   }
 else
   {
-  list = expand_string(*listptr);
-  if (list == NULL)
+  /* If we are searching a domain list, and $domain is not set, set it to the
+  subject that is being sought for the duration of the expansion. */
+
+  if (type == MCL_DOMAIN && !deliver_domain)
+    {
+    check_string_block *cb = (check_string_block *)arg;
+    deliver_domain = string_copy(cb->subject);
+    list = expand_cstring(*listptr);
+    deliver_domain = NULL;
+    }
+  else
+    list = expand_cstring(*listptr);
+
+  if (!list)
     {
-    if (expand_string_forcedfail)
+    if (f.expand_string_forcedfail)
       {
       HDEBUG(D_lists) debug_printf("expansion of \"%s\" forced failure: "
         "assume not in this list\n", *listptr);
@@ -481,17 +506,14 @@ else
 
 /* For an unnamed list, use the expanded version in comments */
 
-HDEBUG(D_any)
-  {
-  if (ot == NULL) ot = string_sprintf("%s in \"%s\"?", name, list);
-  }
+HDEBUG(D_any) if (!ot) ot = string_sprintf("%s in \"%s\"?", name, list);
 
 /* Now scan the list and process each item in turn, until one of them matches,
 or we hit an error. */
 
-while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
+while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
   {
-  uschar *ss = sss;
+  uschar * ss = sss;
 
   /* Address lists may contain +caseful, to restore caseful matching of the
   local part. We have to know the layout of the control block, unfortunately.
@@ -504,7 +526,8 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
       {
       check_address_block *cb = (check_address_block *)arg;
       uschar *at = Ustrrchr(cb->origaddress, '@');
-      if (at != NULL)
+
+      if (at)
         Ustrncpy(cb->address, cb->origaddress, at - cb->origaddress);
       cb->caseless = FALSE;
       continue;
@@ -518,16 +541,17 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
     if (Ustrcmp(ss, "+caseful") == 0)
       {
       check_string_block *cb = (check_string_block *)arg;
-      Ustrcpy(cb->subject, cb->origsubject);
+      Ustrcpy(US cb->subject, cb->origsubject);
       cb->caseless = FALSE;
       continue;
       }
     }
 
-  /* If the host item is "+include_unknown", remember it in case there's a
-  subsequent failed reverse lookup. */
+  /* If the host item is "+include_unknown" or "+ignore_unknown", remember it
+  in case there's a subsequent failed reverse lookup. There is similar
+  processing for "defer". */
 
-  else if (type == MCL_HOST)
+  else if (type == MCL_HOST && *ss == '+')
     {
     if (Ustrcmp(ss, "+include_unknown") == 0)
       {
@@ -541,6 +565,18 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
       include_unknown = FALSE;
       continue;
       }
+    if (Ustrcmp(ss, "+include_defer") == 0)
+      {
+      include_defer = TRUE;
+      ignore_defer = FALSE;
+      continue;
+      }
+    if (Ustrcmp(ss, "+ignore_defer") == 0)
+      {
+      ignore_defer = TRUE;
+      include_defer = FALSE;
+      continue;
+      }
     }
 
   /* Starting with ! specifies a negative item. It is theoretically possible
@@ -551,7 +587,8 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
     yield = FAIL;
     while (isspace((*(++ss))));
     }
-  else yield = OK;
+  else
+    yield = OK;
 
   /* If the item does not begin with '/', it might be a + item for a named
   list. Otherwise, it is just a single list entry that has to be matched.
@@ -559,7 +596,7 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
 
   if (*ss != '/')
     {
-    if (*ss == '+' && anchorptr != NULL)
+    if (*ss == '+' && anchorptr)
       {
       int bits = 0;
       int offset = 0;
@@ -567,15 +604,18 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
       unsigned int *use_cache_bits = original_cache_bits;
       uschar *cached = US"";
       namedlist_block *nb;
-      tree_node *t = tree_search(*anchorptr, ss+1);
-
-      if (t == NULL)
-        log_write(0, LOG_MAIN|LOG_PANIC_DIE, "unknown named%s list \"%s\"",
-          (type == MCL_DOMAIN)?    " domain" :
-          (type == MCL_HOST)?      " host" :
-          (type == MCL_ADDRESS)?   " address" :
-          (type == MCL_LOCALPART)? " local part" : "",
+      tree_node * t;
+
+      if (!(t = tree_search(*anchorptr, ss+1)))
+       {
+        log_write(0, LOG_MAIN|LOG_PANIC, "unknown named%s list \"%s\"",
+          type == MCL_DOMAIN ?    " domain" :
+          type == MCL_HOST ?      " host" :
+          type == MCL_ADDRESS ?   " address" :
+          type == MCL_LOCALPART ? " local part" : "",
           ss);
+       return DEFER;
+       }
       nb = t->data.ptr;
 
       /* If the list number is negative, it means that this list is not
@@ -587,7 +627,7 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
       because the pointer may be NULL from the start if caching is not
       required. */
 
-      if (use_cache_bits != NULL)
+      if (use_cache_bits)
         {
         offset = (nb->number)/16;
         shift = ((nb->number)%16)*2;
@@ -611,15 +651,13 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
         wasn't before. Ensure that this is passed up to the next level.
         Otherwise, remember the result of the search in the cache. */
 
-        if (use_cache_bits == NULL)
-          {
+        if (!use_cache_bits)
           *cache_ptr = NULL;
-          }
         else
           {
           use_cache_bits[offset] |= bits << shift;
 
-          if (valueptr != NULL)
+          if (valueptr)
             {
             int old_pool = store_pool;
             namedlist_cacheblock *p;
@@ -628,20 +666,18 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
             so we use the permanent store pool */
 
             store_pool = POOL_PERM;
-            p = store_get(sizeof(namedlist_cacheblock));
+            p = store_get(sizeof(namedlist_cacheblock), FALSE);
             p->key = string_copy(get_check_key(arg, type));
 
 
-            p->data = (*valueptr == NULL)? NULL : string_copy(*valueptr);
+            p->data = *valueptr ? string_copy(*valueptr) : NULL;
             store_pool = old_pool;
 
             p->next = nb->cache_data;
             nb->cache_data = p;
-            if (*valueptr != NULL)
-              {
+            if (*valueptr)
               DEBUG(D_lists) debug_printf("data from lookup saved for "
                 "cache for %s: %s\n", ss, *valueptr);
-              }
             }
           }
         }
@@ -654,19 +690,18 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
         {
         DEBUG(D_lists) debug_printf("cached %s match for %s\n",
           ((bits & (-bits)) == bits)? "yes" : "no", ss);
+
         cached = US" - cached";
-        if (valueptr != NULL)
+        if (valueptr)
           {
-          uschar *key = get_check_key(arg, type);
-          namedlist_cacheblock *p;
-          for (p = nb->cache_data; p != NULL; p = p->next)
-            {
+          const uschar *key = get_check_key(arg, type);
+
+          for (namedlist_cacheblock * p = nb->cache_data; p; p = p->next)
             if (Ustrcmp(key, p->key) == 0)
               {
               *valueptr = p->data;
               break;
               }
-            }
           DEBUG(D_lists) debug_printf("cached lookup data = %s\n", *valueptr);
           }
         }
@@ -686,35 +721,55 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
 
     else
       {
-      uschar *error;
+      uschar * error = NULL;
       switch ((func)(arg, ss, valueptr, &error))
         {
         case OK:
-        HDEBUG(D_lists) debug_printf("%s %s (matched \"%s\")\n", ot,
-          (yield == OK)? "yes" : "no", sss);
-        return yield;
+         HDEBUG(D_lists) debug_printf("%s %s (matched \"%s\")\n", ot,
+           (yield == OK)? "yes" : "no", sss);
+         return yield;
 
         case DEFER:
-        goto DEFER_RETURN;
-
-        /* The ERROR return occurs only when checking hosts, when either a
-        forward or reverse lookup has failed. The error string gives details of
+         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",
+             error);
+           break;
+           }
+         if (include_defer)
+           {
+           log_write(0, LOG_MAIN, "%s: accepted by +include_defer", error);
+           return OK;
+           }
+         if (!search_error_message) search_error_message = error;
+         goto DEFER_RETURN;
+
+        /* The ERROR return occurs when checking hosts, when either a forward
+        or reverse lookup has failed. It can also occur in a match_ip list if a
+        non-IP address item is encountered. The error string gives details of
         which it was. */
 
         case ERROR:
-        if (ignore_unknown)
-          {
-          HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_unknown",
-            error);
-          }
-        else
-          {
-          HDEBUG(D_lists) debug_printf("%s %s (%s)\n", ot,
-            include_unknown? "yes":"no", error);
-          if (!include_unknown) return FAIL;
-          log_write(0, LOG_MAIN, "%s: accepted by +include_unknown", error);
-          return OK;
-          }
+         if (ignore_unknown)
+           {
+           HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_unknown\n",
+             error);
+           }
+         else
+           {
+           HDEBUG(D_lists) debug_printf("%s %s (%s)\n", ot,
+             include_unknown? "yes":"no", error);
+           if (!include_unknown)
+             {
+             if (LOGGING(unknown_in_list))
+               log_write(0, LOG_MAIN, "list matching forced to fail: %s", error);
+             return FAIL;
+             }
+           log_write(0, LOG_MAIN, "%s: accepted by +include_unknown", error);
+           return OK;
+           }
         }
       }
     }
@@ -725,16 +780,16 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
   else
     {
     int file_yield = yield;       /* In case empty file */
-    uschar *filename = ss;
-    FILE *f = Ufopen(filename, "rb");
+    uschar * filename = ss;
+    FILE * f = Ufopen(filename, "rb");
     uschar filebuffer[1024];
 
     /* ot will be null in non-debugging cases, and anyway, we get better
     wording by reworking it. */
 
-    if (f == NULL)
+    if (!f)
       {
-      uschar *listname = readconf_find_option(listptr);
+      uschar * listname = readconf_find_option(listptr);
       if (listname[0] == 0)
         listname = string_sprintf("\"%s\"", *listptr);
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s",
@@ -782,30 +837,48 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
       switch ((func)(arg, ss, valueptr, &error))
         {
         case OK:
-        fclose(f);
-        HDEBUG(D_lists) debug_printf("%s %s (matched \"%s\" in %s)\n", ot,
-          (yield == OK)? "yes" : "no", sss, filename);
-        return file_yield;
+         (void)fclose(f);
+         HDEBUG(D_lists) debug_printf("%s %s (matched \"%s\" in %s)\n", ot,
+           yield == OK ? "yes" : "no", sss, filename);
+         return file_yield;
 
         case DEFER:
-        fclose(f);
-        goto DEFER_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",
-            error);
-          }
-        else
-         {
-          HDEBUG(D_lists) debug_printf("%s %s (%s)\n", ot,
-            include_unknown? "yes":"no", error);
-          fclose(f);
-          if (!include_unknown) return FAIL;
-          log_write(0, LOG_MAIN, "%s: accepted by +include_unknown", error);
-          return OK;
-          }
+         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",
+             error);
+           break;
+           }
+         (void)fclose(f);
+         if (include_defer)
+           {
+           log_write(0, LOG_MAIN, "%s: accepted by +include_defer", error);
+           return OK;
+           }
+         goto DEFER_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",
+             error);
+           }
+         else
+          {
+           HDEBUG(D_lists) debug_printf("%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;
+             }
+           log_write(0, LOG_MAIN, "%s: accepted by +include_unknown", error);
+           return OK;
+           }
         }
       }
 
@@ -813,20 +886,20 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
     for the file, in case this is the last item in the list. */
 
     yield = file_yield;
-    fclose(f);
+    (void)fclose(f);
     }
   }    /* Loop for the next item on the top-level list */
 
 /* 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;
+  debug_printf("%s %s (end of list)\n", ot, yield == OK ? "no":"yes");
+return yield == OK ? FAIL : OK;
 
-/* Handle lookup defer */
+/* Something deferred */
 
 DEFER_RETURN:
-HDEBUG(D_lists) debug_printf("%s lookup deferred for %s\n", ot, sss);
+HDEBUG(D_lists) debug_printf("%s list match deferred for %s\n", ot, sss);
 return DEFER;
 }
 
@@ -871,8 +944,9 @@ Returns:         OK    if matched a non-negated item
 */
 
 int
-match_isinlist(uschar *s, uschar **listptr, int sep, tree_node **anchorptr,
-  unsigned int *cache_bits, int type, BOOL caseless, uschar **valueptr)
+match_isinlist(const uschar *s, const uschar **listptr, int sep,
+   tree_node **anchorptr,
+  unsigned int *cache_bits, int type, BOOL caseless, const uschar **valueptr)
 {
 unsigned int *local_cache_bits = cache_bits;
 check_string_block cb;
@@ -918,22 +992,37 @@ Returns:         OK     for a match
 */
 
 static int
-check_address(void *arg, uschar *pattern, 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;
 int rc;
 int expand_inc = 0;
 unsigned int *null = NULL;
-uschar *listptr;
+const uschar *listptr;
 uschar *subject = cb->address;
-uschar *s, *pdomain, *sdomain;
+const uschar *s;
+uschar *pdomain, *sdomain;
 
 error = error;  /* Keep clever compilers from complaining */
 
-DEBUG(D_lists) debug_printf("address match: subject=%s pattern=%s\n",
+DEBUG(D_lists) debug_printf("address match test: subject=%s pattern=%s\n",
   subject, pattern);
 
+/* Find the subject's domain */
+
+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)
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "no @ found in the subject of an "
+    "address list match: subject=\"%s\" pattern=\"%s\"", subject, pattern);
+  return FAIL;
+  }
+
 /* Handle a regular expression, which must match the entire incoming address.
 This may be the empty address. */
 
@@ -968,10 +1057,6 @@ against. */
 
 if (*subject == 0) return (*pattern == 0)? OK : FAIL;
 
-/* Find the subject's domain */
-
-sdomain = Ustrrchr(subject, '@');
-
 /* 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
 "*" (called from retry) the match always fails. */
@@ -979,7 +1064,7 @@ looked up to obtain a list of local parts. If the subject's local part is just
 if (pattern[0] == '@' && pattern[1] == '@')
   {
   int watchdog = 50;
-  uschar *list, *key, *ss;
+  uschar *list, *ss;
   uschar buffer[1024];
 
   if (sdomain == subject + 1 && *subject == '*') return FAIL;
@@ -987,12 +1072,12 @@ if (pattern[0] == '@' && pattern[1] == '@')
   /* Loop for handling chains. The last item in any list may be of the form
   ">name" in order to chain on to another list. */
 
-  for (key = sdomain + 1; key != NULL && watchdog-- > 0; )
+  for (const uschar * key = sdomain + 1; key && watchdog-- > 0; )
     {
     int sep = 0;
 
     if ((rc = match_check_string(key, pattern + 2, -1, TRUE, FALSE, FALSE,
-      &list)) != OK) return rc;
+      CUSS &list)) != OK) return rc;
 
     /* Check for chaining from the last item; set up the next key if one
     is found. */
@@ -1011,8 +1096,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(&list, &sep, buffer, sizeof(buffer)))
-           != NULL)
+    while ((ss = string_nextinlist(CUSS &list, &sep, buffer, sizeof(buffer))))
       {
       int local_yield;
 
@@ -1187,13 +1271,13 @@ Returns:          OK    for a positive match, or end list after a negation;
 */
 
 int
-match_address_list(uschar *address, BOOL caseless, BOOL expand,
-  uschar **listptr, unsigned int *cache_bits, int expand_setup, int sep,
-  uschar **valueptr)
+match_address_list(const uschar *address, BOOL caseless, BOOL expand,
+  const uschar **listptr, unsigned int *cache_bits, int expand_setup, int sep,
+  const uschar **valueptr)
 {
-uschar *p;
 check_address_block ab;
 unsigned int *local_cache_bits = cache_bits;
+int len;
 
 /* RFC 2505 recommends that for spam checking, local parts should be caselessly
 compared. Therefore, Exim now forces the entire address into lower case here,
@@ -1202,8 +1286,10 @@ 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. */
 
-sprintf(CS big_buffer, "%.*s", big_buffer_size - 1, address);
-for (p = big_buffer + Ustrlen(big_buffer) - 1; p >= big_buffer; p--)
+if ((len = Ustrlen(address)) > 255) len = 255;
+ab.address = string_copyn(address, len);
+
+for (uschar * p = ab.address + len - 1; p >= ab.address; p--)
   {
   if (!caseless && *p == '@') break;
   *p = tolower(*p);
@@ -1224,7 +1310,7 @@ if (expand_setup == 0)
 /* Set up the data to be passed ultimately to check_address. */
 
 ab.origaddress = address;
-ab.address = big_buffer;
+/* ab.address is above */
 ab.expand_setup = expand_setup;
 ab.caseless = caseless;
 
@@ -1233,4 +1319,25 @@ return match_check_list(listptr, sep, &addresslist_anchor, &local_cache_bits,
     valueptr);
 }
 
+/* Simpler version of match_address_list; always caseless, expanding,
+no cache bits, no value-return.
+
+Arguments:
+  address         address to test
+  listptr         list to check against
+  sep             separator character for the list;
+                  may be 0 to get separator from the list;
+                  may be UCHAR_MAX+1 for one-item list
+
+Returns:          OK    for a positive match, or end list after a negation;
+                  FAIL  for a negative match, or end list after non-negation;
+                  DEFER if a lookup deferred
+*/
+
+int
+match_address_list_basic(const uschar *address, const uschar **listptr, int sep)
+{
+return match_address_list(address, TRUE, TRUE, listptr, NULL, -1, sep, NULL);
+}
+
 /* End of match.c */