Copyright updates:
[exim.git] / src / src / match.c
index f9b539d10e87ca931a5d033ef1d0ecfe8341800d..2e4bff078eacfe6195b26f0c63c02938c1932e95 100644 (file)
@@ -2,8 +2,8 @@
 *     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. */
 
 /* Functions for matching strings */
@@ -104,9 +104,7 @@ 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
@@ -130,13 +128,12 @@ required. */
 
 if (pattern[0] == '^')
   {
-  const pcre * re = regex_must_compile(pattern, cb->caseless, FALSE);
+  const pcre2_code * 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(re, s, -1, NULL)
       : !regex_match_and_setup(re, s, 0, expand_setup)
      )
     return FAIL;
-  /* assume the above wrote $0, $n... TODO: CHECK THAT !! */
   if (valueptr) *valueptr = pattern;   /* "value" gets the RE */
   return OK;
   }
@@ -158,7 +155,7 @@ if (pattern[0] == '*')
     {
     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 */
     }
   if (valueptr) *valueptr = pattern - 1;       /* "value" gets the (original) pattern */
   return OK;
@@ -185,7 +182,7 @@ if (cb->at_is_special && pattern[0] == '@')
       if (Ustrncmp(ip->address, s+1, slen - 2) == 0
             && ip->address[slen - 2] == 0)
        {
-/* I see no reason not to return $0, the matchd IP.  if (expand_setup >= 0) expand_nmax = expand_setup; */
+       if (expand_setup >= 0) expand_nmax = expand_setup;      /* commit $0, the IP addr */
        if (valueptr) *valueptr = pattern;      /* "value" gets the pattern */
         return OK;
        }
@@ -238,11 +235,11 @@ if (cb->at_is_special && pattern[0] == '@')
       return DEFER;
       }
 
-    if (rc != HOST_FOUND_LOCAL || secy)
-      if (prim || !removed) return FAIL;
+    if ((rc != HOST_FOUND_LOCAL || secy) && (prim || !removed))
+      return FAIL;
 
-/* again, $0 getting the subject, the matched IP.  if (expand_setup >= 0) expand_nmax = expand_setup; */
-    if (valueptr) *valueptr = pattern; /* "vaulue" gets the patterm */
+    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,
@@ -268,10 +265,6 @@ if ((semicolon = Ustrchr(pattern, ';')) == NULL)
   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;
-
-/*
-XXX  looks like $0 may be usable
-*/
   }
 
 /* Otherwise we have a lookup item. The lookup type, including partial, etc. is
@@ -291,22 +284,7 @@ if (!cb->use_partial) partial = -1;
 
 /* Set the parameters for the three different kinds of lookup. */
 
-keyquery = semicolon + 1;
-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);
-  }
-
-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
@@ -355,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;
@@ -454,11 +432,9 @@ 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;
+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;
@@ -467,8 +443,8 @@ uschar *ot = NULL;
 
 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
@@ -476,7 +452,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;
   }
 
@@ -509,7 +485,7 @@ else
     {
     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;
       }
@@ -520,8 +496,18 @@ else
   }
 
 /* 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);
+  }
 
 /* Now scan the list and process each item in turn, until one of them matches,
 or we hit an error. */
@@ -681,7 +667,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));
 
 
@@ -691,7 +677,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);
             }
           }
@@ -703,7 +689,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
 
       else
         {
-        DEBUG(D_lists) debug_printf("cached %s match for %s\n",
+        DEBUG(D_lists) debug_printf_indent("cached %s match for %s\n",
           (bits & (-bits)) == bits ? "yes" : "no", ss);
 
         cached = US" - cached";
@@ -717,7 +703,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);
           }
         }
 
@@ -726,8 +712,8 @@ 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);
+        HDEBUG(D_lists) debug_printf_indent("%s %s (matched \"%s\"%s)\n", ot,
+          yield == OK ? "yes" : "no", sss, cached);
         return yield;
         }
       }
@@ -740,7 +726,7 @@ 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;
 
@@ -749,7 +735,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
            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;
            }
@@ -769,12 +755,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)
              {
@@ -808,7 +794,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
       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
@@ -831,19 +817,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))));
@@ -853,8 +839,13 @@ 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);
+
+         /* 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);
          return file_yield;
 
         case DEFER:
@@ -862,7 +853,7 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
            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;
            }
@@ -877,12 +868,12 @@ while ((sss = string_nextinlist(&list, &sep, NULL, 0)))
         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)
@@ -908,7 +899,7 @@ 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");
+  debug_printf_indent("%s %s (end of list)\n", ot, yield == OK ? "no":"yes");
 return yield == OK ? FAIL : OK;
 
 /* Something deferred */
@@ -966,12 +957,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);
 }
@@ -1009,19 +1005,18 @@ Returns:         OK     for a match
 static int
 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;
-
-error = error;  /* Keep clever compilers from complaining */
-
-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 */
@@ -1070,7 +1065,7 @@ if (*s == ';')
 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
@@ -1080,7 +1075,6 @@ if (pattern[0] == '@' && pattern[1] == '@')
   {
   int watchdog = 50;
   uschar *list, *ss;
-  uschar buffer[1024];
 
   if (sdomain == subject + 1 && *subject == '*') return FAIL;
 
@@ -1111,7 +1105,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;
 
@@ -1193,6 +1187,7 @@ if (pdomain != NULL)
       expand_nlength[cb->expand_setup] = sllen - cllen;
       expand_inc = 1;
       }
+    value = string_copyn(pattern + 1, cllen);
     }
   else
     {
@@ -1201,6 +1196,7 @@ if (pdomain != NULL)
         ? 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
@@ -1225,16 +1221,23 @@ csb.at_is_special = TRUE;
 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;
 }
 
 
@@ -1288,9 +1291,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--)