Fix matching of long addresses. Bug 2677
[exim.git] / src / src / match.c
index 9e471105b3dd9ce0a33c5d1143190094c09abac1..bf8cb3b98f8331700c187a9430ad8ebd9034b568 100644 (file)
@@ -2,7 +2,8 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* 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 */
@@ -97,15 +98,13 @@ check_string(void *arg, const uschar *pattern, const uschar **valueptr, uschar *
 const check_string_block *cb = arg;
 int search_type, partial, affixlen, starflags;
 int expand_setup = cb->expand_setup;
-const uschar *affix;
+const uschar * affix, * opts;
 uschar *s;
 uschar *filename = NULL;
 uschar *keyquery, *result, *semicolon;
 void *handle;
 
-error = error;  /* Keep clever compilers from complaining */
-
-if (valueptr != NULL) *valueptr = NULL;  /* For non-lookup matches */
+if (valueptr) *valueptr = NULL;
 
 /* 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
@@ -119,7 +118,7 @@ expand_nmax until the match is assured. */
 expand_nmax = -1;
 if (expand_setup == 0)
   {
-  expand_nstring[0] = s;
+  expand_nstring[0] = s;       /* $0 (might be) the matched subject in full */
   expand_nlength[0] = Ustrlen(s);
   }
 else if (expand_setup > 0) expand_setup--;
@@ -129,35 +128,37 @@ required. */
 
 if (pattern[0] == '^')
   {
-  const pcre *re = regex_must_compile(pattern, cb->caseless, FALSE);
-  return ((expand_setup < 0)?
-           pcre_exec(re, NULL, CCS s, Ustrlen(s), 0, PCRE_EOPT, NULL, 0) >= 0
-           :
-           regex_match_and_setup(re, s, 0, expand_setup)
-         )?
-         OK : FAIL;
+  const pcre * re = regex_must_compile(pattern, cb->caseless, FALSE);
+  if (expand_setup < 0
+      ? pcre_exec(re, NULL, CCS s, Ustrlen(s), 0, PCRE_EOPT, NULL, 0) < 0
+      : !regex_match_and_setup(re, s, 0, expand_setup)
+     )
+    return FAIL;
+  if (valueptr) *valueptr = pattern;   /* "value" gets the RE */
+  return OK;
   }
 
 /* Tail match */
 
 if (pattern[0] == '*')
   {
-  BOOL yield;
   int slen = Ustrlen(s);
   int patlen;    /* Sun compiler doesn't like non-constant initializer */
 
   patlen = Ustrlen(++pattern);
   if (patlen > slen) return FAIL;
-  yield = cb->caseless?
-    (strncmpic(s + slen - patlen, pattern, patlen) == 0) :
-    (Ustrncmp(s + slen - patlen, pattern, patlen) == 0);
-  if (yield && expand_setup >= 0)
+  if (cb->caseless
+      ? strncmpic(s + slen - patlen, pattern, patlen) != 0
+      : Ustrncmp(s + slen - patlen, pattern, patlen) != 0)
+    return FAIL;
+  if (expand_setup >= 0)
     {
-    expand_nstring[++expand_setup] = s;
+    expand_nstring[++expand_setup] = s;                /* write a $n, the matched subject variable-part */
     expand_nlength[expand_setup] = slen - patlen;
-    expand_nmax = expand_setup;
+    expand_nmax = expand_setup;                        /* commit also $0, the matched subject */
     }
-  return yield? OK : FAIL;
+  if (valueptr) *valueptr = pattern - 1;       /* "value" gets the (original) pattern */
+  return OK;
   }
 
 /* Match a special item starting with @ if so enabled. On its own, "@" matches
@@ -175,13 +176,16 @@ 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 (s[0] != '[' && s[slen-1] != ']') return FAIL;          /*XXX should this be || ? */
+    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)
+       {
+       if (expand_setup >= 0) expand_nmax = expand_setup;      /* commit $0, the IP addr */
+       if (valueptr) *valueptr = pattern;      /* "value" gets the pattern */
         return OK;
+       }
     return FAIL;
     }
 
@@ -209,7 +213,7 @@ if (cb->at_is_special && pattern[0] == '@')
     else goto NOT_AT_SPECIAL;
 
     if (strncmpic(ss, US"/ignore=", 8) == 0) ignore_target_hosts = ss + 8;
-      else if (*ss != 0) goto NOT_AT_SPECIAL;
+    else if (*ss) goto NOT_AT_SPECIAL;
 
     h.next = NULL;
     h.name = s;
@@ -231,9 +235,12 @@ if (cb->at_is_special && pattern[0] == '@')
       return DEFER;
       }
 
-    if (rc == HOST_FOUND_LOCAL && !secy) return OK;
-    if (prim) return FAIL;
-    return removed? OK : FAIL;
+    if ((rc != HOST_FOUND_LOCAL || secy) && (prim || !removed))
+      return FAIL;
+
+    if (expand_setup >= 0) expand_nmax = expand_setup; /* commit $0, the matched subject */
+    if (valueptr) *valueptr = pattern; /* "value" gets the patterm */
+    return OK;
 
     /*** The above line used to be the following line, but this is incorrect,
     because host_find_bydns() may return HOST_NOT_FOUND if it removed some MX
@@ -253,10 +260,11 @@ NOT_AT_SPECIAL:
 
 if ((semicolon = Ustrchr(pattern, ';')) == NULL)
   {
-  BOOL yield = cb->caseless?
-    (strcmpic(s, pattern) == 0) : (Ustrcmp(s, pattern) == 0);
-  if (yield && expand_setup >= 0) expand_nmax = expand_setup;
-  return yield? OK : FAIL;
+  if (cb->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 */
+  return OK;
   }
 
 /* Otherwise we have a lookup item. The lookup type, including partial, etc. is
@@ -264,7 +272,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);
@@ -276,35 +284,19 @@ if (!cb->use_partial) partial = -1;
 
 /* Set the parameters for the three different kinds of lookup. */
 
-keyquery = semicolon + 1;
-while (isspace(*keyquery)) keyquery++;
-
-if (mac_islookup(search_type, lookup_absfilequery))
-  {
-  filename = keyquery;
-  while (*keyquery != 0 && !isspace(*keyquery)) keyquery++;
-  filename = string_copyn(filename, keyquery - filename);
-  while (isspace(*keyquery)) keyquery++;
-  }
-
-else if (!mac_islookup(search_type, lookup_querystyle))
-  {
-  filename = keyquery;
-  keyquery = s;
-  }
+keyquery = search_args(search_type, s, semicolon+1, &filename, opts);
 
 /* Now do the actual lookup; throw away the data returned unless it was asked
 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;
@@ -341,7 +333,7 @@ match_check_string(const uschar *s, const uschar *pattern, int expand_setup,
 {
 check_string_block cb;
 cb.origsubject = s;
-cb.subject = caseless? string_copylc(s) : string_copy(s);
+cb.subject = caseless ? string_copylc(s) : string_copy(s);
 cb.expand_setup = expand_setup;
 cb.use_partial = use_partial;
 cb.caseless = caseless;
@@ -373,13 +365,13 @@ switch(type)
   case MCL_STRING:
   case MCL_DOMAIN:
   case MCL_LOCALPART:
-  return ((check_string_block *)arg)->subject;
+    return ((check_string_block *)arg)->subject;
 
   case MCL_HOST:
-  return ((check_host_block *)arg)->host_address;
+    return ((check_host_block *)arg)->host_address;
 
   case MCL_ADDRESS:
-  return ((check_address_block *)arg)->address;
+    return ((check_address_block *)arg)->address;
   }
 return US"";  /* In practice, should never happen */
 }
@@ -448,7 +440,6 @@ 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. */
 
@@ -461,12 +452,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;
   }
 
@@ -485,19 +473,19 @@ else
   /* 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 == NULL)
+  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);
 
-  else list = expand_cstring(*listptr);
-
-  if (list == NULL)
+  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);
@@ -511,17 +499,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.
@@ -534,7 +519,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;
@@ -548,7 +534,7 @@ 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;
       }
@@ -594,7 +580,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.
@@ -602,7 +589,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;
@@ -610,15 +597,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
@@ -630,7 +620,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;
@@ -654,15 +644,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;
@@ -671,20 +659,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);
-              }
+                "cache for %s: key '%s' value '%s'\n", ss, p->key, *valueptr);
             }
           }
         }
@@ -696,20 +682,19 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
       else
         {
         DEBUG(D_lists) debug_printf("cached %s match for %s\n",
-          ((bits & (-bits)) == bits)? "yes" : "no", ss);
+          (bits & (-bits)) == bits ? "yes" : "no", ss);
+
         cached = US" - cached";
-        if (valueptr != NULL)
+        if (valueptr)
           {
           const uschar *key = get_check_key(arg, type);
-          namedlist_cacheblock *p;
-          for (p = nb->cache_data; p != NULL; p = p->next)
-            {
+
+          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);
           }
         }
@@ -729,30 +714,30 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
 
     else
       {
-      uschar *error = NULL;
+      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:
-        if (error == NULL)
-          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;
+         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
@@ -760,24 +745,24 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
         which it was. */
 
         case ERROR:
-        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 ((log_extra_selector & LX_unknown_in_list) != 0)
-              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;
-          }
+         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;
+           }
         }
       }
     }
@@ -788,20 +773,20 @@ 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",
-        string_open_failed(errno, "%s when checking %s", sss, listname));
+        string_open_failed("%s when checking %s", sss, listname));
       }
 
     /* Trailing comments are introduced by #, but in an address list or local
@@ -845,48 +830,48 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
       switch ((func)(arg, ss, valueptr, &error))
         {
         case OK:
-        (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;
+         (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:
-        if (error == NULL)
-          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 ((log_extra_selector & LX_unknown_in_list) != 0)
-              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;
-          }
+         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;
+           }
         }
       }
 
@@ -901,8 +886,8 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
 /* 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;
 
 /* Something deferred */
 
@@ -959,12 +944,17 @@ match_isinlist(const uschar *s, const uschar **listptr, int sep,
 unsigned int *local_cache_bits = cache_bits;
 check_string_block cb;
 cb.origsubject = s;
-cb.subject = caseless? string_copylc(s) : string_copy(s);
-cb.expand_setup = (sep > UCHAR_MAX)? 0 : -1;
+cb.subject = caseless ? string_copylc(s) : string_copy(s);
+cb.at_is_special = FALSE;
+switch (type & ~MCL_NOEXPAND)
+  {
+  case MCL_DOMAIN:     cb.at_is_special = TRUE;        /*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;
-cb.at_is_special = (type == MCL_DOMAIN || type == MCL_DOMAIN + MCL_NOEXPAND);
-if (valueptr != NULL) *valueptr = NULL;
+if (valueptr) *valueptr = NULL;
 return  match_check_list(listptr, sep, anchorptr, &local_cache_bits,
   check_string, &cb, type, s, valueptr);
 }
@@ -1012,8 +1002,6 @@ uschar *subject = cb->address;
 const uschar *s;
 uschar *pdomain, *sdomain;
 
-error = error;  /* Keep clever compilers from complaining */
-
 DEBUG(D_lists) debug_printf("address match test: subject=%s pattern=%s\n",
   subject, pattern);
 
@@ -1072,7 +1060,6 @@ 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;
-  const uschar *key;
   uschar *list, *ss;
   uschar buffer[1024];
 
@@ -1081,7 +1068,7 @@ 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;
 
@@ -1177,16 +1164,10 @@ if (pdomain != NULL)
     {
     int cllen = pllen - 1;
     if (sllen < cllen) return FAIL;
-    if (cb->caseless)
-      {
-      if (strncmpic(subject+sllen-cllen, pattern + 1, cllen) != 0)
+    if (cb->caseless
+        ? strncmpic(subject+sllen-cllen, pattern + 1, cllen) != 0
+        : Ustrncmp(subject+sllen-cllen, pattern + 1, cllen) != 0)
         return FAIL;
-      }
-    else
-      {
-      if (Ustrncmp(subject+sllen-cllen, pattern + 1, cllen) != 0)
-        return FAIL;
-      }
     if (cb->expand_setup > 0)
       {
       expand_nstring[cb->expand_setup] = subject;
@@ -1197,14 +1178,9 @@ if (pdomain != NULL)
   else
     {
     if (sllen != pllen) return FAIL;
-    if (cb->caseless)
-      {
-      if (strncmpic(subject, pattern, sllen) != 0) return FAIL;
-      }
-    else
-      {
-      if (Ustrncmp(subject, pattern, sllen) != 0) return FAIL;
-      }
+    if (cb->caseless
+        ? strncmpic(subject, pattern, sllen) != 0
+       : Ustrncmp(subject, pattern, sllen) != 0) return FAIL;
     }
   }
 
@@ -1213,7 +1189,7 @@ the generalized function, which supports file lookups (which may defer). The
 original code read as follows:
 
   return match_check_string(sdomain + 1,
-      (pdomain == NULL)? pattern : pdomain + 1,
+      pdomain ? pdomain + 1 : pattern,
       cb->expand_setup + expand_inc, TRUE, cb->caseless, TRUE, NULL);
 
 This supported only literal domains and *.x.y patterns. In order to allow for
@@ -1221,14 +1197,14 @@ named domain lists (so that you can right, 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->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;
 
-listptr = (pdomain == NULL)? pattern : pdomain + 1;
-if (valueptr != NULL) *valueptr = NULL;
+listptr = pdomain ? pdomain + 1 : pattern;
+if (valueptr) *valueptr = NULL;
 
 return match_check_list(
   &listptr,                  /* list of one item */
@@ -1284,19 +1260,23 @@ 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,
 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-exhastion attacks.  The size chosen
+is historical (we used to use big_buffer her). */
+
+if ((len = Ustrlen(address)) > BIG_BUFFER_SIZE) len = BIG_BUFFER_SIZE;
+ab.address = string_copyn(address, len);
 
-sprintf(CS big_buffer, "%.*s", big_buffer_size - 1, address);
-for (p = big_buffer + Ustrlen(big_buffer) - 1; p >= big_buffer; p--)
+for (uschar * p = ab.address + len - 1; p >= ab.address; p--)
   {
   if (!caseless && *p == '@') break;
   *p = tolower(*p);
@@ -1317,7 +1297,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;
 
@@ -1326,4 +1306,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 */