*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2015 */
+/* Copyright (c) The Exim Maintainers 2020 */
/* See the file NOTICE for conditions of use and distribution. */
/* A set of functions to search databases in various formats. An open
/* Now we are left with a lookup name, possibly followed by * or *@,
and then by options starting with a "," */
-#ifdef old
-len = Ustrlen(ss);
-if (len >= 2 && Ustrncmp(ss + len - 2, "*@", 2) == 0)
- {
- *starflags |= SEARCH_STARAT;
- len -= 2;
- }
-else if (len >= 1 && ss[len-1] == '*')
- {
- *starflags |= SEARCH_STAR;
- len--;
- }
-#endif
-
len = Ustrlen(ss);
if ((t = Ustrchr(ss, '*')))
{
else
t = ss;
-* USS opts = (t = Ustrchr(t, ',')) ? string_copy(t+1) : NULL;
+if ((t = Ustrchr(t, ',')))
+ {
+ int l = t - ss;
+ if (l < len) len = l;
+ *opts = string_copy(t+1);
+ }
+else
+ *opts = NULL;
/* Check for the individual search type. Only those that are actually in the
binary are valid. For query-style types, "partial" and default types are
}
+/* 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 *
static void
tidyup_subtree(tree_node *t)
{
-search_cache *c = (search_cache *)(t->data.ptr);
-if (t->left != NULL) tidyup_subtree(t->left);
-if (t->right != NULL) tidyup_subtree(t->right);
-if (c != NULL &&
- c->handle != NULL &&
- lookup_list[c->search_type]->close != NULL)
+search_cache * c = (search_cache *)(t->data.ptr);
+if (t->left) tidyup_subtree(t->left);
+if (t->right) tidyup_subtree(t->right);
+if (c && c->handle && lookup_list[c->search_type]->close)
lookup_list[c->search_type]->close(c->handle);
}
uschar keybuffer[256];
int old_pool = store_pool;
-if (filename && is_tainted(filename))
- {
- log_write(0, LOG_MAIN|LOG_PANIC,
- "Tainted filename for search: '%s'", filename);
+if (filename && is_tainted2(filename, LOG_MAIN|LOG_PANIC, "Tainted filename for search: '%s'", filename))
return NULL;
- }
/* Change to the search store pool and remember our reset point */
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,
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);
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;
DEBUG(D_lookup)
{
- if (t) debug_printf_indent("cached data found but past valid time; ");
+ if (t)
+ debug_printf_indent("cached data found but %s; ",
+ 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,
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)
{
- int len = keylength + 1;
-
- if (t) /* Previous, out-of-date cache entry. Update with the */
- { /* new result and forget the old one */
- e->expiry = do_cache == UINT_MAX ? 0 : time(NULL)+do_cache;
- e->data.ptr = data;
- }
- else
+ DEBUG(D_lookup) debug_printf_indent("%s cache entry\n",
+ t ? "replacing old" : "creating new");
+ if (!t) /* No existing entry. Create new one. */
{
- e = store_get(sizeof(expiring_data) + sizeof(tree_node) + len, is_tainted(keystring));
- e->expiry = do_cache == UINT_MAX ? 0 : time(NULL)+do_cache;
- e->data.ptr = data;
+ int len = keylength + 1;
+ e = store_get(sizeof(expiring_data) + sizeof(tree_node) + len,
+ is_tainted(keystring));
t = (tree_node *)(e+1);
memcpy(t->name, keystring, len);
t->data.ptr = e;
tree_insertnode(&c->item_cache, t);
}
+ /* Else previous, out-of-date cache entry. Update with the */
+ /* new result and forget the old one */
+ e->expiry = do_cache == UINT_MAX ? 0 : time(NULL)+do_cache;
+ e->opts = opts ? string_copy(opts) : NULL;
+ e->data.ptr = data;
}
/* If caching was disabled, empty the cache tree. We just set the cache
else
{
DEBUG(D_lookup) debug_printf_indent("lookup forced cache cleanup\n");
- c->item_cache = NULL;
+ c->item_cache = NULL; /* forget all lookups on this connection */
}
}
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)
{
}
+/* 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. */
-if ( open_top != (tree_node *)handle
+if ( open_top != (tree_node *)handle
&& lookup_list[t->name[0]-'0']->type == lookup_absfile)
{
search_cache *c = (search_cache *)(t->data.ptr);
/* 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; }
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;
}
/* The key in its entirety did not match a wild entry; try chopping off
leading components. */
- if (yield == NULL)
+ if (!yield)
{
int dotcount = 0;
uschar *keystring3 = keystring2 + affixlen;
}
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)
{
*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;
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;
expand_nlength[*expand_setup] = Ustrlen(keystring);
}
+/* If we have a result, check the options to see if the key was wanted rather
+than the result. Return a de-tainted version of the key on the grounds that
+it have been validated by the lookup. */
+
+if (yield && ret_key)
+ yield = string_copy_taint(keystring, FALSE);
+
return yield;
}