Lookups: per-searchtype options framework
[exim.git] / src / src / search.c
index ebdf8a165cf800747aa2389f6d5dae9c29e75f1c..51bbc6aed12eabe33a016caa4826318d3a39c911 100644 (file)
@@ -41,7 +41,7 @@ static int open_filecount = 0;
 
 /* Allow us to reset store used for lookups and lookup caching */
 
-static void *search_reset_point = NULL;
+static rmark search_reset_point = NULL;
 
 
 
@@ -115,6 +115,7 @@ Arguments:
                  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.
@@ -122,11 +123,12 @@ Returns:     +ve => valid lookup name; value is offset in lookup_list
 
 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;
@@ -167,8 +169,10 @@ if (Ustrncmp(name, "partial", 7) == 0)
     }
   }
 
-/* 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 "," */
 
+#ifdef old
 len = Ustrlen(ss);
 if (len >= 2 && Ustrncmp(ss + len - 2, "*@", 2) == 0)
   {
@@ -180,6 +184,18 @@ else if (len >= 1 && ss[len-1]  == '*')
   *starflags |= SEARCH_STAR;
   len--;
   }
+#endif
+
+len = Ustrlen(ss);
+if ((t = Ustrchr(ss, '*')))
+  {
+  len = t - ss;
+  *starflags |= (t[1] == '@' ? SEARCH_STARAT : SEARCH_STAR);
+  }
+else
+  t = ss;
+
+* USS opts = (t = Ustrchr(t, ',')) ? string_copy(t+1) : 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
@@ -269,8 +285,7 @@ open_filecount = 0;
 for (int i = 0; i < lookup_list_count; i++) if (lookup_list[i]->tidy)
   (lookup_list[i]->tidy)();
 
-if (search_reset_point) store_reset(search_reset_point);
-search_reset_point = NULL;
+if (search_reset_point) search_reset_point = store_reset(search_reset_point);
 store_pool = old_pool;
 }
 
@@ -326,8 +341,8 @@ Returns:         an identifying handle for the open database;
 */
 
 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;
@@ -336,10 +351,17 @@ lookup_info *lk = lookup_list[search_type];
 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 (search_reset_point == NULL) search_reset_point = store_get(0);
+if (!search_reset_point) search_reset_point = store_mark();
 
 DEBUG(D_lookup) debug_printf_indent("search_open: %s \"%s\"\n", lk->name,
   filename ? filename : US"NULL");
@@ -350,12 +372,11 @@ type plus '0' concatenated with the file name. There may be entries in the tree
 with closed files if a lot of files have been opened. */
 
 sprintf(CS keybuffer, "%c%.254s", search_type + '0',
-  (filename == NULL)? US"" : filename);
+  filename ? filename : US"");
 
-if ((t = tree_search(search_tree, keybuffer)) != NULL)
+if ((t = tree_search(search_tree, keybuffer)))
   {
-  c = (search_cache *)(t->data.ptr);
-  if (c->handle != NULL)
+  if ((c = (search_cache *)t->data.ptr)->handle)
     {
     DEBUG(D_lookup) debug_printf_indent("  cached open\n");
     store_pool = old_pool;
@@ -371,8 +392,7 @@ we are holding open in the cache. If the limit is reached, close the least
 recently used one. */
 
 if (lk->type == lookup_absfile && open_filecount >= lookup_open_max)
-  {
-  if (open_bot == NULL)
+  if (!open_bot)
     log_write(0, LOG_MAIN|LOG_PANIC, "too many lookups open, but can't find "
       "one to close");
   else
@@ -380,8 +400,7 @@ if (lk->type == lookup_absfile && open_filecount >= lookup_open_max)
     search_cache *c = (search_cache *)(open_bot->data.ptr);
     DEBUG(D_lookup) debug_printf_indent("Too many lookup files open\n  closing %s\n",
       open_bot->name);
-    open_bot = c->up;
-    if (open_bot != NULL)
+    if ((open_bot = c->up))
       ((search_cache *)(open_bot->data.ptr))->down = NULL;
     else
       open_top = NULL;
@@ -389,21 +408,19 @@ if (lk->type == lookup_absfile && open_filecount >= lookup_open_max)
     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. */
 
-handle = (lk->open)(filename, &search_error_message);
-if (handle == NULL)
+if (!(handle = (lk->open)(filename, &search_error_message)))
   {
   store_pool = old_pool;
   return NULL;
   }
 
-if (lk->check != NULL &&
-   !lk->check(handle, filename, modemask, owners, owngroups,
-     &search_error_message))
+if (  lk->check
+   && !lk->check(handle, filename, modemask, owners, owngroups,
+        &search_error_message))
   {
   lk->close(handle);
   store_pool = old_pool;
@@ -418,10 +435,10 @@ if (lk->type == lookup_absfile) open_filecount++;
 insert a new entry. On re-use, leave any cached lookup data and the lookup
 count alone. */
 
-if (t == NULL)
+if (!t)
   {
-  t = store_get(sizeof(tree_node) + Ustrlen(keybuffer));
-  t->data.ptr = c = store_get(sizeof(search_cache));
+  t = store_get(sizeof(tree_node) + Ustrlen(keybuffer), FALSE);
+  t->data.ptr = c = store_get(sizeof(search_cache), FALSE);
   c->item_cache = NULL;
   Ustrcpy(t->name, keybuffer);
   tree_insertnode(&search_tree, t);
@@ -453,6 +470,7 @@ Arguments:
                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
@@ -461,7 +479,8 @@ Returns:       a pointer to a dynamic string containing the answer,
 */
 
 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);
@@ -477,8 +496,9 @@ search_error_message = US"";
 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. */
 
@@ -495,7 +515,7 @@ if (  (t = tree_search(c->item_cache, keystring))
    && (!(e = t->data.ptr)->expiry || e->expiry > time(NULL))
    )
   { /* Data was in the cache already; set the pointer from the tree node */
-  data = e->ptr;
+  data = e->data.ptr;
   DEBUG(D_lookup) debug_printf_indent("cached data used for lookup of %s%s%s\n",
     keystring,
     filename ? US"\n  in " : US"", filename ? filename : US"");
@@ -519,7 +539,7 @@ else
   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
@@ -535,13 +555,13 @@ else
     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->ptr = data;
+      e->data.ptr = data;
       }
     else
       {
-      e = store_get(sizeof(expiring_data) + sizeof(tree_node) + len);
+      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->ptr = data;
+      e->data.ptr = data;
       t = (tree_node *)(e+1);
       memcpy(t->name, keystring, len);
       t->data.ptr = e;
@@ -597,6 +617,7 @@ Arguments:
   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
@@ -604,8 +625,9 @@ Returns:         a pointer to a dynamic string containing the answer,
 */
 
 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;
@@ -615,16 +637,18 @@ DEBUG(D_lookup)
   {
   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
 that opens real files. */
 
-if (open_top != (tree_node *)handle &&
-    lookup_list[t->name[0]-'0']->type == lookup_absfile)
+if (  open_top != (tree_node *)handle 
+   && lookup_list[t->name[0]-'0']->type == lookup_absfile)
   {
   search_cache *c = (search_cache *)(t->data.ptr);
   tree_node *up = c->up;
@@ -634,20 +658,21 @@ if (open_top != (tree_node *)handle &&
   Otherwise there will be a non-NULL up pointer, since we checked above that
   this block isn't already at the top of the list. */
 
-  if (up != NULL)
+  if (up)
     {
     ((search_cache *)(up->data.ptr))->down = down;
-    if (down != NULL)
+    if (down)
       ((search_cache *)(down->data.ptr))->up = up;
-    else open_bot = up;
+    else
+      open_bot = up;
     }
 
   /* Now put it at the head of the list. */
 
   c->up = NULL;
   c->down = open_top;
-  if (open_top == NULL) open_bot = t; else
-    ((search_cache *)(open_top->data.ptr))->up = t;
+  if (!open_top) open_bot = t;
+  else ((search_cache *)(open_top->data.ptr))->up = t;
   open_top = t;
   }
 
@@ -666,9 +691,10 @@ 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);
+yield = internal_search_find(handle, filename, keystring, opts);
 if (f.search_find_defer) return NULL;
-if (yield != NULL) { if (partial >= 0) set_null_wild = TRUE; }
+
+if (yield) { if (partial >= 0) set_null_wild = TRUE; }
 
 /* Not matched a complete entry; handle partial lookups, but only if the full
 search didn't defer. Don't use string_sprintf() to construct the initial key,
@@ -685,11 +711,12 @@ else if (partial >= 0)
 
   if (affixlen == 0) keystring2 = keystring; else
     {
-    keystring2 = store_get(len + affixlen + 1);
+    keystring2 = store_get(len + affixlen + 1,
+                       is_tainted(keystring) || is_tainted(affix));
     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;
     }
 
@@ -727,14 +754,14 @@ else if (partial >= 0)
         }
 
       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 != NULL)
+      if (yield)
         {
         /* First variable is the wild part; second is the fixed part. Take care
         to get it right when keystring3 is just "*". */
 
-        if (expand_setup != NULL && *expand_setup >= 0)
+        if (expand_setup && *expand_setup >= 0)
           {
           int fixedlength = Ustrlen(keystring3) - affixlen;
           int wildlength = Ustrlen(keystring) - fixedlength - 1;
@@ -758,7 +785,7 @@ else if (partial >= 0)
 replacing everything to the left of @ by *. After a match, the wild part
 is set to the string to the left of the @. */
 
-if (yield == NULL && (starflags & SEARCH_STARAT) != 0)
+if (!yield  &&  starflags & SEARCH_STARAT)
   {
   uschar *atat = Ustrrchr(keystring, '@');
   if (atat != NULL && atat > keystring)
@@ -768,11 +795,11 @@ if (yield == NULL && (starflags & SEARCH_STARAT) != 0)
     *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 != NULL && expand_setup != NULL && *expand_setup >= 0)
+    if (yield && expand_setup && *expand_setup >= 0)
       {
       *expand_setup += 1;
       expand_nstring[*expand_setup] = keystring;
@@ -788,11 +815,11 @@ if (yield == NULL && (starflags & SEARCH_STARAT) != 0)
 try that. If we do match, the first variable (the wild part) is the whole key,
 and the second is empty. */
 
-if (yield == NULL && (starflags & (SEARCH_STAR|SEARCH_STARAT)) != 0)
+if (!yield  &&  starflags & (SEARCH_STAR|SEARCH_STARAT))
   {
   DEBUG(D_lookup) debug_printf_indent("trying to match *\n");
-  yield = internal_search_find(handle, filename, US"*");
-  if (yield != NULL && expand_setup != NULL && *expand_setup >= 0)
+  yield = internal_search_find(handle, filename, US"*", opts);
+  if (yield && expand_setup && *expand_setup >= 0)
     {
     *expand_setup += 1;
     expand_nstring[*expand_setup] = keystring;
@@ -810,7 +837,7 @@ chopping off any of the domain components, set up the expansion variables
 fixed part of the domain. The set_null_wild flag is set only when yield is not
 NULL. */
 
-if (set_null_wild && expand_setup != NULL && *expand_setup >= 0)
+if (set_null_wild && expand_setup && *expand_setup >= 0)
   {
   *expand_setup += 1;
   expand_nstring[*expand_setup] = keystring;