SPDX: Mass-update to GPL-2.0-or-later
[exim.git] / src / src / search.c
index 94a58897f787da0115b9d6ef225f27de6b1083f5..2b6e5d37f6b9d44bc8a0ea4185d80a48522439e8 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 - 2015 */
-/* Copyright (c) The Exim Maintainers 2020 */
 /* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
 
 /* A set of functions to search databases in various formats. An open
 database is represented by a void * value which is returned from a lookup-
@@ -63,11 +64,9 @@ Returns:     +ve => valid lookup name; value is offset in lookup_list
 */
 
 int
-search_findtype(const uschar *name, int len)
+search_findtype(const uschar * name, int len)
 {
-int bot = 0;
-int top = lookup_list_count;
-while (top > bot)
+for (int bot = 0, top = lookup_list_count; top > bot; )
   {
   int mid = (top + bot)/2;
   int c = Ustrncmp(name, lookup_list[mid]->name, len);
@@ -92,7 +91,7 @@ while (top > bot)
   if (c > 0) bot = mid + 1; else top = mid;
   }
 
-search_error_message = string_sprintf("unknown lookup type \"%.*s\"",len,name);
+search_error_message = string_sprintf("unknown lookup type \"%.*s\"", len, name);
 return -1;
 }
 
@@ -217,6 +216,55 @@ return stype;
 }
 
 
+/* Set the parameters for the three different kinds of lookup.
+Arguments:
+ search_type   the search-type code
+ search                the search-type string
+ query         argument for the search; filename or query
+ fnamep                pointer to return filename
+ opts          options
+
+Return:        keyquery        the search-type (for single-key) or query (for query-type)
+ */
+uschar *
+search_args(int search_type, uschar * search, uschar * query, uschar ** fnamep,
+  const uschar * opts)
+{
+Uskip_whitespace(&query);
+if (mac_islookup(search_type, lookup_absfilequery))
+  {                                    /* query-style but with file (sqlite) */
+  int sep = ',';
+
+  /* Check options first for new-style file spec */
+  if (opts) for (uschar * s; s = string_nextinlist(&opts, &sep, NULL, 0); )
+    if (Ustrncmp(s, "file=", 5) == 0)
+      {
+      *fnamep = s+5;
+      return query;
+      }
+
+  /* If no filename from options, use old-tyle space-sep prefix on query */
+  if (*query == '/')
+    {
+    uschar * s = query;
+    while (*query && !isspace(*query)) query++;
+    *fnamep = string_copyn(s, query - s);
+    Uskip_whitespace(&query);
+    }
+  else
+    *fnamep = NULL;
+  return query;                /* remainder after file skipped */
+  }
+if (!mac_islookup(search_type, lookup_querystyle))
+  {                                    /* single-key */
+  *fnamep = query;
+  return search;       /* modifiers important so use "keyquery" for them */
+  }
+*fnamep = NULL;                                /* else query-style */
+return query;
+}
+
+
 
 /*************************************************
 *               Release cached resources         *
@@ -429,8 +477,8 @@ count alone. */
 
 if (!t)
   {
-  t = store_get(sizeof(tree_node) + Ustrlen(keybuffer), FALSE);
-  t->data.ptr = c = store_get(sizeof(search_cache), FALSE);
+  t = store_get(sizeof(tree_node) + Ustrlen(keybuffer), GET_UNTAINTED);
+  t->data.ptr = c = store_get(sizeof(search_cache), GET_UNTAINTED);
   c->item_cache = NULL;
   Ustrcpy(t->name, keybuffer);
   tree_insertnode(&search_tree, t);
@@ -462,6 +510,7 @@ Arguments:
                NULL for query-style searches
   keystring    the keystring for single-key+file lookups, or
                the querystring for query-style lookups
+  cache_rd     FALSE to avoid lookup in cache layer
   opts        type-specific options
 
 Returns:       a pointer to a dynamic string containing the answer,
@@ -472,7 +521,7 @@ Returns:       a pointer to a dynamic string containing the answer,
 
 static uschar *
 internal_search_find(void * handle, const uschar * filename, uschar * keystring,
-  const uschar * opts)
+  BOOL cache_rd, const uschar * opts)
 {
 tree_node * t = (tree_node *)handle;
 search_cache * c = (search_cache *)(t->data.ptr);
@@ -501,11 +550,13 @@ if (keystring[0] == 0) return NULL;
 store_pool = POOL_SEARCH;
 
 /* Look up the data for the key, unless it is already in the cache for this
-file. No need to check c->item_cache for NULL, tree_search will do so. */
+file. No need to check c->item_cache for NULL, tree_search will do so. Check
+whether we want to use the cache entry last so that we can always replace it. */
 
 if (  (t = tree_search(c->item_cache, keystring))
    && (!(e = t->data.ptr)->expiry || e->expiry > time(NULL))
    && (!opts && !e->opts  ||  opts && e->opts && Ustrcmp(opts, e->opts) == 0)
+   && cache_rd
    )
   { /* Data was in the cache already; set the pointer from the tree node */
   data = e->data.ptr;
@@ -522,11 +573,53 @@ else
     {
     if (t)
       debug_printf_indent("cached data found but %s; ",
-       e->expiry && e->expiry <= time(NULL) ? "out-of-date" : "wrong opts");
+       e->expiry && e->expiry <= time(NULL) ? "out-of-date"
+       : cache_rd ? "wrong opts" : "no_rd option set");
     debug_printf_indent("%s lookup required for %s%s%s\n",
       filename ? US"file" : US"database",
       keystring,
       filename ? US"\n  in " : US"", filename ? filename : US"");
+    if (!filename && is_tainted(keystring))
+      {
+      debug_printf_indent("                             ");
+      debug_print_taint(keystring);
+      }
+    }
+
+  /* Check that the query, for query-style lookups,
+  is either untainted or properly quoted for the lookup type.
+
+  XXX Should we this move into lf_sqlperform() ?  The server-taint check is there.
+  */
+
+  if (  !filename && lookup_list[search_type]->quote
+     && is_tainted(keystring) && !is_quoted_like(keystring, search_type))
+    {
+    uschar * s = acl_current_verb();
+    if (!s) s = authenticator_current_name();  /* must be before transport */
+    if (!s) s = transport_current_name();      /* must be before router */
+    if (!s) s = router_current_name(); /* GCC ?: would be good, but not in clang */
+    if (!s) s = US"";
+#ifdef enforce_quote_protection_notyet
+    search_error_message = string_sprintf(
+      "tainted search query is not properly quoted%s: %s%s",
+      s, keystring);
+    f.search_find_defer = TRUE;
+#else
+     {
+      int q = quoter_for_address(keystring);
+      /* If we're called from a transport, no privs to open the paniclog;
+      the logging punts to using stderr - and that seems to stop the debug
+      stream. */
+      log_write(0,
+       transport_name ? LOG_MAIN : LOG_MAIN|LOG_PANIC,
+       "tainted search query is not properly quoted%s: %s", s, keystring);
+
+      DEBUG(D_lookup) debug_printf_indent("search_type %d (%s) quoting %d (%s)\n",
+       search_type, lookup_list[search_type]->name,
+       q, is_real_quoter(q) ? lookup_list[q]->name : US"none");
+     }
+#endif
     }
 
   /* Call the code for the different kinds of search. DEFER is handled
@@ -534,21 +627,26 @@ else
   distinguish if necessary. */
 
   if (lookup_list[search_type]->find(c->handle, filename, keystring, keylength,
-      &data, &search_error_message, &do_cache, opts) == DEFER)
+         &data, &search_error_message, &do_cache, opts) == DEFER)
     f.search_find_defer = TRUE;
 
   /* A record that has been found is now in data, which is either NULL
   or points to a bit of dynamic store. Cache the result of the lookup if
   caching is permitted. Lookups can disable caching, when they did something
   that changes their data. The mysql and pgsql lookups do this when an
-  UPDATE/INSERT query was executed. */
+  UPDATE/INSERT query was executed.  Lookups can also set a TTL for the
+  cache entry; the dnsdb lookup does.
+  Finally, the caller can request no caching by setting an option. */
 
   else if (do_cache)
     {
+    DEBUG(D_lookup) debug_printf_indent("%s cache entry\n",
+      t ? "replacing old" : "creating new");
     if (!t)    /* No existing entry.  Create new one. */
       {
       int len = keylength + 1;
-      e = store_get(sizeof(expiring_data) + sizeof(tree_node) + len, is_tainted(keystring));
+      /* The cache node value should never be expanded so use tainted mem */
+      e = store_get(sizeof(expiring_data) + sizeof(tree_node) + len, GET_TAINTED);
       t = (tree_node *)(e+1);
       memcpy(t->name, keystring, len);
       t->data.ptr = e;
@@ -561,8 +659,8 @@ else
     e->data.ptr = data;
     }
 
-  /* If caching was disabled, empty the cache tree. We just set the cache
-  pointer to NULL here, because we cannot release the store at this stage. */
+/* If caching was disabled, empty the cache tree. We just set the cache
+pointer to NULL here, because we cannot release the store at this stage. */
 
   else
     {
@@ -621,9 +719,9 @@ search_find(void * handle, const uschar * filename, uschar * keystring,
   int partial, const uschar * affix, int affixlen, int starflags,
   int * expand_setup, const uschar * opts)
 {
-tree_node *t = (tree_node *)handle;
-BOOL set_null_wild = FALSE;
-uschar *yield;
+tree_node * t = (tree_node *)handle;
+BOOL set_null_wild = FALSE, cache_rd = TRUE, ret_key = FALSE;
+uschar * yield;
 
 DEBUG(D_lookup)
   {
@@ -636,6 +734,23 @@ DEBUG(D_lookup)
 
   }
 
+/* Parse global lookup options. Also, create a new options list with
+the global options dropped so that the cache-modifiers are not
+used in the cache key. */
+
+if (opts)
+  {
+  int sep = ',';
+  gstring * g = NULL;
+
+  for (uschar * ele; ele = string_nextinlist(&opts, &sep, NULL, 0); )
+    if (Ustrcmp(ele, "ret=key") == 0) ret_key = TRUE;
+    else if (Ustrcmp(ele, "cache=no_rd") == 0) cache_rd = FALSE;
+    else g = string_append_listele(g, ',', ele);
+
+  opts = string_from_gstring(g);
+  }
+
 /* Arrange to put this database at the top of the LRU chain if it is a type
 that opens real files. */
 
@@ -683,7 +798,7 @@ DEBUG(D_lookup)
 /* First of all, try to match the key string verbatim. If matched a complete
 entry but could have been partial, flag to set up variables. */
 
-yield = internal_search_find(handle, filename, keystring, opts);
+yield = internal_search_find(handle, filename, keystring, cache_rd, opts);
 if (f.search_find_defer) return NULL;
 
 if (yield) { if (partial >= 0) set_null_wild = TRUE; }
@@ -704,11 +819,11 @@ else if (partial >= 0)
   if (affixlen == 0) keystring2 = keystring; else
     {
     keystring2 = store_get(len + affixlen + 1,
-                       is_tainted(keystring) || is_tainted(affix));
+         is_tainted(keystring) || is_tainted(affix) ? GET_TAINTED : GET_UNTAINTED);
     Ustrncpy(keystring2, affix, affixlen);
     Ustrcpy(keystring2 + affixlen, keystring);
     DEBUG(D_lookup) debug_printf_indent("trying partial match %s\n", keystring2);
-    yield = internal_search_find(handle, filename, keystring2, opts);
+    yield = internal_search_find(handle, filename, keystring2, cache_rd, opts);
     if (f.search_find_defer) return NULL;
     }
 
@@ -746,7 +861,8 @@ else if (partial >= 0)
         }
 
       DEBUG(D_lookup) debug_printf_indent("trying partial match %s\n", keystring3);
-      yield = internal_search_find(handle, filename, keystring3, opts);
+      yield = internal_search_find(handle, filename, keystring3,
+               cache_rd, opts);
       if (f.search_find_defer) return NULL;
       if (yield)
         {
@@ -787,7 +903,7 @@ if (!yield  &&  starflags & SEARCH_STARAT)
     *atat = '*';
 
     DEBUG(D_lookup) debug_printf_indent("trying default match %s\n", atat);
-    yield = internal_search_find(handle, filename, atat, opts);
+    yield = internal_search_find(handle, filename, atat, cache_rd, opts);
     *atat = savechar;
     if (f.search_find_defer) return NULL;
 
@@ -810,7 +926,7 @@ and the second is empty. */
 if (!yield  &&  starflags & (SEARCH_STAR|SEARCH_STARAT))
   {
   DEBUG(D_lookup) debug_printf_indent("trying to match *\n");
-  yield = internal_search_find(handle, filename, US"*", opts);
+  yield = internal_search_find(handle, filename, US"*", cache_rd, opts);
   if (yield && expand_setup && *expand_setup >= 0)
     {
     *expand_setup += 1;
@@ -843,14 +959,8 @@ if (set_null_wild && expand_setup && *expand_setup >= 0)
 than the result.  Return a de-tainted version of the key on the grounds that
 it have been validated by the lookup. */
 
-if (yield && opts)
-  {
-  int sep = ',';
-  uschar * ele;
-  while ((ele = string_nextinlist(&opts, &sep, NULL, 0)))
-    if (Ustrcmp(ele, "ret=key") == 0)
-      { yield = string_copy_taint(keystring, FALSE); break; }
-  }
+if (yield && ret_key)
+  yield = string_copy_taint(keystring, GET_UNTAINTED);
 
 return yield;
 }