*************************************************/
/* 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
otherwise it's a literal string
afflen the length of the affix
starflags where to put the SEARCH_STAR and SEARCH_STARAT flags
+ opts where to put the options
Returns: +ve => valid lookup name; value is offset in lookup_list
-ve => invalid name; message in search_error_message.
int
search_findtype_partial(const uschar *name, int *ptypeptr, const uschar **ptypeaff,
- int *afflen, int *starflags)
+ int *afflen, int *starflags, const uschar ** opts)
{
int len, stype;
int pv = -1;
const uschar *ss = name;
+const uschar * t;
*starflags = 0;
*ptypeaff = NULL;
}
}
-/* Now we are left with a lookup name, possibly followed by * or *@. */
+/* Now we are left with a lookup name, possibly followed by * or *@,
+and then by options starting with a "," */
len = Ustrlen(ss);
-if (len >= 2 && Ustrncmp(ss + len - 2, "*@", 2) == 0)
+if ((t = Ustrchr(ss, '*')))
{
- *starflags |= SEARCH_STARAT;
- len -= 2;
+ len = t - ss;
+ *starflags |= (t[1] == '@' ? SEARCH_STARAT : SEARCH_STAR);
}
-else if (len >= 1 && ss[len-1] == '*')
+else
+ t = ss;
+
+if ((t = Ustrchr(t, ',')))
{
- *starflags |= SEARCH_STAR;
- len--;
+ 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
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);
}
*/
void *
-search_open(uschar *filename, int search_type, int modemask, uid_t *owners,
- gid_t *owngroups)
+search_open(const uschar * filename, int search_type, int modemask,
+ uid_t * owners, gid_t * owngroups)
{
void *handle;
tree_node *t;
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);
+ return NULL;
+ }
+
/* Change to the search store pool and remember our reset point */
store_pool = POOL_SEARCH;
if ((t = tree_search(search_tree, keybuffer)))
{
- c = (search_cache *)(t->data.ptr);
- if (c->handle)
+ if ((c = (search_cache *)t->data.ptr)->handle)
{
DEBUG(D_lookup) debug_printf_indent(" cached open\n");
store_pool = old_pool;
recently used one. */
if (lk->type == lookup_absfile && open_filecount >= lookup_open_max)
- {
if (!open_bot)
log_write(0, LOG_MAIN|LOG_PANIC, "too many lookups open, but can't find "
"one to close");
c->handle = NULL;
open_filecount--;
}
- }
/* If opening is successful, call the file-checking function if there is one,
and if all is still well, enter the open database into the tree. */
NULL for query-style searches
keystring the keystring for single-key+file lookups, or
the querystring for query-style lookups
+ opts type-specific options
Returns: a pointer to a dynamic string containing the answer,
or NULL if the query failed or was deferred; in the
*/
static uschar *
-internal_search_find(void *handle, uschar *filename, uschar *keystring)
+internal_search_find(void * handle, const uschar * filename, uschar * keystring,
+ const uschar * opts)
{
tree_node * t = (tree_node *)handle;
search_cache * c = (search_cache *)(t->data.ptr);
f.search_find_defer = FALSE;
DEBUG(D_lookup) debug_printf_indent("internal_search_find: file=\"%s\"\n "
- "type=%s key=\"%s\"\n", filename,
- lookup_list[search_type]->name, keystring);
+ "type=%s key=\"%s\" opts=%s%s%s\n", filename,
+ lookup_list[search_type]->name, keystring,
+ opts ? "\"" : "", opts, opts ? "\"" : "");
/* Insurance. If the keystring is empty, just fail. */
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)
)
{ /* 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" : "wrong opts");
debug_printf_indent("%s lookup required for %s%s%s\n",
filename ? US"file" : US"database",
keystring,
distinguish if necessary. */
if (lookup_list[search_type]->find(c->handle, filename, keystring, keylength,
- &data, &search_error_message, &do_cache) == 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
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
+ 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));
- e->expiry = do_cache == UINT_MAX ? 0 : time(NULL)+do_cache;
- e->data.ptr = data;
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 */
}
}
starflags SEARCH_STAR and SEARCH_STARAT flags
expand_setup pointer to offset for setting up expansion strings;
don't do any if < 0
+ opts type-specific options
Returns: a pointer to a dynamic string containing the answer,
or NULL if the query failed or was deferred; in the
*/
uschar *
-search_find(void *handle, uschar *filename, uschar *keystring, int partial,
- const uschar *affix, int affixlen, int starflags, int *expand_setup)
+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;
{
if (partial < 0) affixlen = 99; /* So that "NULL" prints */
debug_printf_indent("search_find: file=\"%s\"\n key=\"%s\" "
- "partial=%d affix=%.*s starflags=%x\n",
- (filename == NULL)? US"NULL" : filename,
- keystring, partial, affixlen, affix, starflags);
+ "partial=%d affix=%.*s starflags=%x opts=%s%s%s\n",
+ filename ? filename : US"NULL",
+ keystring, partial, affixlen, affix, starflags,
+ opts ? "\"" : "", opts, opts ? "\"" : "");
+
}
/* Arrange to put this database at the top of the LRU chain if it is a type
/* 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);
+yield = internal_search_find(handle, filename, keystring, 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);
+ yield = internal_search_find(handle, filename, keystring2, 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);
+ yield = internal_search_find(handle, filename, keystring3, 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);
+ yield = internal_search_find(handle, filename, atat, 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"*");
+ yield = internal_search_find(handle, filename, US"*", 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 && 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; }
+ }
+
return yield;
}