summaryrefslogtreecommitdiffstats
path: root/src/lookups/lsearch.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lookups/lsearch.c')
-rw-r--r--src/lookups/lsearch.c487
1 files changed, 487 insertions, 0 deletions
diff --git a/src/lookups/lsearch.c b/src/lookups/lsearch.c
new file mode 100644
index 0000000..dcfdec9
--- /dev/null
+++ b/src/lookups/lsearch.c
@@ -0,0 +1,487 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) The Exim Maintainers 2020 - 2022 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+#include "../exim.h"
+#include "lf_functions.h"
+
+/* Codes for the different kinds of lsearch that are supported */
+
+enum {
+ LSEARCH_PLAIN, /* Literal keys */
+ LSEARCH_WILD, /* Wild card keys, expanded */
+ LSEARCH_NWILD, /* Wild card keys, not expanded */
+ LSEARCH_IP /* IP addresses and networks */
+};
+
+
+
+/*************************************************
+* Open entry point *
+*************************************************/
+
+/* See local README for interface description */
+
+static void *
+lsearch_open(const uschar * filename, uschar ** errmsg)
+{
+FILE * f = Ufopen(filename, "rb");
+if (!f)
+ *errmsg = string_open_failed("%s for linear search", filename);
+return f;
+}
+
+
+
+/*************************************************
+* Check entry point *
+*************************************************/
+
+static BOOL
+lsearch_check(void *handle, const uschar *filename, int modemask, uid_t *owners,
+ gid_t *owngroups, uschar **errmsg)
+{
+return lf_check_file(fileno((FILE *)handle), filename, S_IFREG, modemask,
+ owners, owngroups, "lsearch", errmsg) == 0;
+}
+
+
+
+/*************************************************
+* Internal function for the various lsearches *
+*************************************************/
+
+/* See local README for interface description, plus:
+
+Extra argument:
+
+ type one of the values LSEARCH_PLAIN, LSEARCH_WILD, LSEARCH_NWILD, or
+ LSEARCH_IP
+
+There is some messy logic in here to cope with very long data lines that do not
+fit into the fixed sized buffer. Most of the time this will never be exercised,
+but people do occasionally do weird things. */
+
+static int
+internal_lsearch_find(void * handle, const uschar * filename,
+ const uschar * keystring, int length, uschar ** result, uschar ** errmsg,
+ int type, const uschar * opts)
+{
+FILE *f = handle;
+BOOL ret_full = FALSE;
+int old_pool = store_pool;
+rmark reset_point = NULL;
+uschar buffer[4096];
+
+if (opts)
+ {
+ int sep = ',';
+ uschar * ele;
+
+ while ((ele = string_nextinlist(&opts, &sep, NULL, 0)))
+ if (Ustrcmp(ele, "ret=full") == 0)
+ { ret_full = TRUE; break; }
+ }
+
+/* Wildcard searches may use up some store, because of expansions. We don't
+want them to fill up our search store. What we do is set the pool to the main
+pool and get a point to reset to later. Wildcard searches could also issue
+lookups, but internal_search_find will take care of that, and the cache will be
+safely stored in the search pool again. */
+
+if (type == LSEARCH_WILD || type == LSEARCH_NWILD)
+ {
+ store_pool = POOL_MAIN;
+ reset_point = store_mark();
+ }
+
+rewind(f);
+for (BOOL this_is_eol, last_was_eol = TRUE;
+ Ufgets(buffer, sizeof(buffer), f) != NULL;
+ last_was_eol = this_is_eol)
+ {
+ int p = Ustrlen(buffer);
+ int linekeylength;
+ BOOL this_is_comment;
+ gstring * yield;
+ uschar *s = buffer;
+
+ /* Check whether this the final segment of a line. If it follows an
+ incomplete part-line, skip it. */
+
+ this_is_eol = p > 0 && buffer[p-1] == '\n';
+ if (!last_was_eol) continue;
+
+ /* We now have the start of a physical line. If this is a final line segment,
+ remove trailing white space. */
+
+ if (this_is_eol)
+ {
+ while (p > 0 && isspace((uschar)buffer[p-1])) p--;
+ buffer[p] = 0;
+ }
+
+ /* If the buffer is empty it might be (a) a complete empty line, or (b) the
+ start of a line that begins with so much white space that it doesn't all fit
+ in the buffer. In both cases we want to skip the entire physical line.
+
+ If the buffer begins with # it is a comment line; if it begins with white
+ space it is a logical continuation; again, we want to skip the entire
+ physical line. */
+
+ if (buffer[0] == 0 || buffer[0] == '#' || isspace(buffer[0])) continue;
+
+ /* We assume that they key will fit in the buffer. If the key starts with ",
+ read it as a quoted string. We don't use string_dequote() because that uses
+ new store for the result, and we may be doing this many times in a long file.
+ We know that the dequoted string must be shorter than the original, because
+ we are removing the quotes, and also any escape sequences always turn two or
+ more characters into one character. Therefore, we can store the new string in
+ the same buffer. */
+
+ if (*s == '\"')
+ {
+ uschar *t = s++;
+ while (*s && *s != '\"')
+ {
+ *t++ = *s == '\\' ? string_interpret_escape(CUSS &s) : *s;
+ s++;
+ }
+ linekeylength = t - buffer;
+ if (*s) s++; /* Past terminating " */
+ if (ret_full)
+ memmove(t, s, Ustrlen(s)+1); /* copy the rest of line also */
+ }
+
+ /* Otherwise it is terminated by a colon or white space */
+
+ else
+ {
+ while (*s && *s != ':' && !isspace(*s)) s++;
+ linekeylength = s - buffer;
+ }
+
+ /* The matching test depends on which kind of lsearch we are doing */
+
+ switch(type)
+ {
+ /* A plain lsearch treats each key as a literal */
+
+ case LSEARCH_PLAIN:
+ if (linekeylength != length || strncmpic(buffer, keystring, length) != 0)
+ continue;
+ break; /* Key matched */
+
+ /* A wild lsearch treats each key as a possible wildcarded string; no
+ expansion is done for nwildlsearch. */
+
+ case LSEARCH_WILD:
+ case LSEARCH_NWILD:
+ {
+ int rc;
+ int save = buffer[linekeylength];
+ const uschar *list = buffer;
+ buffer[linekeylength] = 0;
+ rc = match_isinlist(keystring,
+ &list,
+ UCHAR_MAX+1, /* Single-item list */
+ NULL, /* No anchor */
+ NULL, /* No caching */
+ MCL_STRING + (type == LSEARCH_WILD ? 0 : MCL_NOEXPAND),
+ TRUE, /* Caseless */
+ NULL);
+ buffer[linekeylength] = save;
+ if (rc == FAIL) continue;
+ if (rc == DEFER) return DEFER;
+ }
+
+ /* The key has matched. If the search involved a regular expression, it
+ might have caused numerical variables to be set. However, their values will
+ be in the wrong storage pool for external use. Copying them to the standard
+ pool is not feasible because of the caching of lookup results - a repeated
+ lookup will not match the regular expression again. Therefore, we drop
+ all numeric variables at this point. */
+
+ expand_nmax = -1;
+ break;
+
+ /* Compare an ip address against a list of network/ip addresses. We have to
+ allow for the "*" case specially. */
+
+ case LSEARCH_IP:
+ if (linekeylength == 1 && buffer[0] == '*')
+ {
+ if (length != 1 || keystring[0] != '*') continue;
+ }
+ else if (length == 1 && keystring[0] == '*') continue;
+ else
+ {
+ int maskoffset;
+ int save = buffer[linekeylength];
+ buffer[linekeylength] = 0;
+ if (string_is_ip_address(buffer, &maskoffset) == 0 ||
+ !host_is_in_net(keystring, buffer, maskoffset)) continue;
+ buffer[linekeylength] = save;
+ }
+ break; /* Key matched */
+ }
+
+ /* The key has matched. Skip spaces after the key, and allow an optional
+ colon after the spaces. This is an odd specification, but it's for
+ compatibility. */
+
+ if (!ret_full)
+ if (Uskip_whitespace(&s) == ':')
+ {
+ s++;
+ Uskip_whitespace(&s);
+ }
+
+ /* Reset dynamic store, if we need to, and revert to the search pool */
+
+ if (reset_point)
+ {
+ reset_point = store_reset(reset_point);
+ store_pool = old_pool;
+ }
+
+ /* Now we want to build the result string to contain the data. There can be
+ two kinds of continuation: (a) the physical line may not all have fitted into
+ the buffer, and (b) there may be logical continuation lines, for which we
+ must convert all leading white space into a single blank.
+
+ Initialize, and copy the first segment of data. */
+
+ this_is_comment = FALSE;
+ yield = string_get(100);
+ if (ret_full)
+ yield = string_cat(yield, buffer);
+ else if (*s)
+ yield = string_cat(yield, s);
+
+ /* Now handle continuations */
+
+ for (last_was_eol = this_is_eol;
+ Ufgets(buffer, sizeof(buffer), f) != NULL;
+ last_was_eol = this_is_eol)
+ {
+ s = buffer;
+ p = Ustrlen(buffer);
+ this_is_eol = p > 0 && buffer[p-1] == '\n';
+
+ /* Remove trailing white space from a physical line end */
+
+ if (this_is_eol)
+ {
+ while (p > 0 && isspace((uschar)buffer[p-1])) p--;
+ buffer[p] = 0;
+ }
+
+ /* If this is not a physical line continuation, skip it entirely if it's
+ empty or starts with #. Otherwise, break the loop if it doesn't start with
+ white space. Otherwise, replace leading white space with a single blank. */
+
+ if (last_was_eol)
+ {
+ this_is_comment = (this_is_comment || (buffer[0] == 0 || buffer[0] == '#'));
+ if (this_is_comment) continue;
+ if (!isspace((uschar)buffer[0])) break;
+ while (isspace((uschar)*s)) s++;
+ *(--s) = ' ';
+ }
+ if (this_is_comment) continue;
+
+ /* Join a physical or logical line continuation onto the result string. */
+
+ yield = string_cat(yield, s);
+ }
+
+ gstring_release_unused(yield);
+ *result = string_from_gstring(yield);
+ return OK;
+ }
+
+/* Reset dynamic store, if we need to */
+
+if (reset_point)
+ {
+ store_reset(reset_point);
+ store_pool = old_pool;
+ }
+
+return FAIL;
+}
+
+
+/*************************************************
+* Find entry point for lsearch *
+*************************************************/
+
+/* See local README for interface description */
+
+static int
+lsearch_find(void * handle, const uschar * filename, const uschar * keystring,
+ int length, uschar ** result, uschar ** errmsg, uint * do_cache,
+ const uschar * opts)
+{
+return internal_lsearch_find(handle, filename, keystring, length, result,
+ errmsg, LSEARCH_PLAIN, opts);
+}
+
+
+
+/*************************************************
+* Find entry point for wildlsearch *
+*************************************************/
+
+/* See local README for interface description */
+
+static int
+wildlsearch_find(void * handle, const uschar * filename, const uschar * keystring,
+ int length, uschar ** result, uschar ** errmsg, uint * do_cache,
+ const uschar * opts)
+{
+return internal_lsearch_find(handle, filename, keystring, length, result,
+ errmsg, LSEARCH_WILD, opts);
+}
+
+
+
+/*************************************************
+* Find entry point for nwildlsearch *
+*************************************************/
+
+/* See local README for interface description */
+
+static int
+nwildlsearch_find(void * handle, const uschar * filename, const uschar * keystring,
+ int length, uschar ** result, uschar ** errmsg, uint * do_cache,
+ const uschar * opts)
+{
+return internal_lsearch_find(handle, filename, keystring, length, result,
+ errmsg, LSEARCH_NWILD, opts);
+}
+
+
+
+
+/*************************************************
+* Find entry point for iplsearch *
+*************************************************/
+
+/* See local README for interface description */
+
+static int
+iplsearch_find(void * handle, uschar const * filename, const uschar * keystring,
+ int length, uschar ** result, uschar ** errmsg, uint * do_cache,
+ const uschar * opts)
+{
+if ((length == 1 && keystring[0] == '*') ||
+ string_is_ip_address(keystring, NULL) != 0)
+ return internal_lsearch_find(handle, filename, keystring, length, result,
+ errmsg, LSEARCH_IP, opts);
+
+*errmsg = string_sprintf("\"%s\" is not a valid iplsearch key (an IP "
+"address, with optional CIDR mask, is wanted): "
+"in a host list, use net-iplsearch as the search type", keystring);
+return DEFER;
+}
+
+
+
+
+/*************************************************
+* Close entry point *
+*************************************************/
+
+/* See local README for interface description */
+
+static void
+lsearch_close(void *handle)
+{
+(void)fclose((FILE *)handle);
+}
+
+
+
+/*************************************************
+* Version reporting entry point *
+*************************************************/
+
+/* See local README for interface description. */
+
+#include "../version.h"
+
+gstring *
+lsearch_version_report(gstring * g)
+{
+#ifdef DYNLOOKUP
+g = string_fmt_append(g, "Library version: lsearch: Exim version %s\n", EXIM_VERSION_STR));
+#endif
+return g;
+}
+
+
+static lookup_info iplsearch_lookup_info = {
+ .name = US"iplsearch", /* lookup name */
+ .type = lookup_absfile, /* uses absolute file name */
+ .open = lsearch_open, /* open function */
+ .check = lsearch_check, /* check function */
+ .find = iplsearch_find, /* find function */
+ .close = lsearch_close, /* close function */
+ .tidy = NULL, /* no tidy function */
+ .quote = NULL, /* no quoting function */
+ .version_report = NULL /* no version reporting (redundant) */
+};
+
+static lookup_info lsearch_lookup_info = {
+ .name = US"lsearch", /* lookup name */
+ .type = lookup_absfile, /* uses absolute file name */
+ .open = lsearch_open, /* open function */
+ .check = lsearch_check, /* check function */
+ .find = lsearch_find, /* find function */
+ .close = lsearch_close, /* close function */
+ .tidy = NULL, /* no tidy function */
+ .quote = NULL, /* no quoting function */
+ .version_report = lsearch_version_report /* version reporting */
+};
+
+static lookup_info nwildlsearch_lookup_info = {
+ .name = US"nwildlsearch", /* lookup name */
+ .type = lookup_absfile, /* uses absolute file name */
+ .open = lsearch_open, /* open function */
+ .check = lsearch_check, /* check function */
+ .find = nwildlsearch_find, /* find function */
+ .close = lsearch_close, /* close function */
+ .tidy = NULL, /* no tidy function */
+ .quote = NULL, /* no quoting function */
+ .version_report = NULL /* no version reporting (redundant) */
+};
+
+static lookup_info wildlsearch_lookup_info = {
+ .name = US"wildlsearch", /* lookup name */
+ .type = lookup_absfile, /* uses absolute file name */
+ .open = lsearch_open, /* open function */
+ .check = lsearch_check, /* check function */
+ .find = wildlsearch_find, /* find function */
+ .close = lsearch_close, /* close function */
+ .tidy = NULL, /* no tidy function */
+ .quote = NULL, /* no quoting function */
+ .version_report = NULL /* no version reporting (redundant) */
+};
+
+#ifdef DYNLOOKUP
+#define lsearch_lookup_module_info _lookup_module_info
+#endif
+
+static lookup_info *_lookup_list[] = { &iplsearch_lookup_info,
+ &lsearch_lookup_info,
+ &nwildlsearch_lookup_info,
+ &wildlsearch_lookup_info };
+lookup_module_info lsearch_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 4 };
+
+/* End of lookups/lsearch.c */