summaryrefslogtreecommitdiffstats
path: root/src/global/map_search.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/global/map_search.c')
-rw-r--r--src/global/map_search.c397
1 files changed, 397 insertions, 0 deletions
diff --git a/src/global/map_search.c b/src/global/map_search.c
new file mode 100644
index 0000000..b10f7d5
--- /dev/null
+++ b/src/global/map_search.c
@@ -0,0 +1,397 @@
+/*++
+/* NAME
+/* map_search_search 3
+/* SUMMARY
+/* lookup table search list support
+/* SYNOPSIS
+/* #include <map_search_search.h>
+/*
+/* typedef struct {
+/* .in +4
+/* char *map_type_name; /* type:name, owned */
+/* char *search_order; /* null or owned */
+/* .in -4
+/* } MAP_SEARCH;
+/*
+/* void map_search_init(
+/* const NAME_CODE *search_actions)
+/*
+/* const MAP_SEARCH *map_search_create(
+/* const char *map_spec)
+/*
+/* const MAP_SEARCH *map_search_lookup(
+/* const char *map_spec);
+/* DESCRIPTION
+/* This module implements configurable search order support
+/* for Postfix lookup tables.
+/*
+/* map_search_init() must be called once, before other functions
+/* in this module.
+/*
+/* map_search_create() creates a MAP_SEARCH instance for
+/* map_spec, ignoring duplicate requests.
+/*
+/* map_search_lookup() looks up the MAP_SEARCH instance that
+/* was created by map_search_create().
+/*
+/* Arguments:
+/* .IP search_actions
+/* The mapping from search action string form to numeric form.
+/* The numbers must be in the range [1..126] (inclusive). The
+/* value 0 is reserved for the MAP_SEARCH.search_order terminator,
+/* and the value MAP_SEARCH_CODE_UNKNOWN is reserved for the
+/* 'not found' result. The argument is copied (the pointer
+/* value, not the table).
+/* .IP map_spec
+/* lookup table and optional search order: either maptype:mapname,
+/* or { maptype:mapname, { search = name, name }}. The search
+/* attribute is optional. The comma is equivalent to whitespace.
+/* DIAGNOSTICS
+/* map_search_create() returns a null pointer when a map_spec
+/* is a) malformed, b) specifies an unexpected attribute name,
+/* c) the search attribute contains an unknown name. Thus,
+/* map_search_create() will never return a search_order that
+/* contains the value MAP_SEARCH_CODE_UNKNOWN.
+/*
+/* Panic: interface violations. Fatal errors: out of memory.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+ /*
+ * Utility library.
+ */
+#include <htable.h>
+#include <msg.h>
+#include <mymalloc.h>
+#include <name_code.h>
+#include <stringops.h>
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <map_search.h>
+
+ /*
+ * Application-specific.
+ */
+static HTABLE *map_search_table;
+static const NAME_CODE *map_search_actions;
+
+#define STR(x) vstring_str(x)
+
+/* map_search_init - one-time initialization */
+
+void map_search_init(const NAME_CODE *search_actions)
+{
+ if (map_search_table != 0 || map_search_actions != 0)
+ msg_panic("map_search_init: multiple calls");
+ map_search_table = htable_create(100);
+ map_search_actions = search_actions;
+}
+
+/* map_search_create - store MAP_SEARCH instance */
+
+const MAP_SEARCH *map_search_create(const char *map_spec)
+{
+ char *copy_of_map_spec = 0;
+ char *bp = 0;
+ const char *const_err;
+ char *heap_err = 0;
+ VSTRING *search_order = 0;
+ const char *map_type_name;
+ char *attr_name_val = 0;
+ char *attr_name = 0;
+ char *attr_value = 0;
+ MAP_SEARCH *map_search;
+ char *atom;
+ int code;
+
+ /*
+ * Sanity check.
+ */
+ if (map_search_table == 0 || map_search_actions == 0)
+ msg_panic("map_search_create: missing initialization");
+
+ /*
+ * Allow exact duplicates. This won't catch duplicates that differ only
+ * in their use of whitespace or comma.
+ */
+ if ((map_search =
+ (MAP_SEARCH *) htable_find(map_search_table, map_spec)) != 0)
+ return (map_search);
+
+ /*
+ * Macro for readability and safety. Let the compiler worry about code
+ * duplication and redundant conditions.
+ */
+#define MAP_SEARCH_CREATE_RETURN(x) do { \
+ if (copy_of_map_spec) myfree(copy_of_map_spec); \
+ if (heap_err) myfree(heap_err); \
+ if (search_order) vstring_free(search_order); \
+ return (x); \
+ } while (0)
+
+ /*
+ * Long form specifies maptype_mapname and optional search attribute.
+ */
+ if (*map_spec == CHARS_BRACE[0]) {
+ bp = copy_of_map_spec = mystrdup(map_spec);
+ if ((heap_err = extpar(&bp, CHARS_BRACE, EXTPAR_FLAG_STRIP)) != 0) {
+ msg_warn("malformed map specification: '%s'", heap_err);
+ MAP_SEARCH_CREATE_RETURN(0);
+ } else if ((map_type_name = mystrtokq(&bp, CHARS_COMMA_SP,
+ CHARS_BRACE)) == 0) {
+ msg_warn("empty map specification: '%s'", map_spec);
+ MAP_SEARCH_CREATE_RETURN(0);
+ }
+ } else {
+ map_type_name = map_spec;
+ }
+
+ /*
+ * Sanity check the map spec before parsing attributes.
+ */
+ if (strchr(map_type_name, ':') == 0) {
+ msg_warn("malformed map specification: '%s'", map_spec);
+ msg_warn("expected maptype:mapname instead of '%s'", map_type_name);
+ MAP_SEARCH_CREATE_RETURN(0);
+ }
+
+ /*
+ * Parse the attribute list. XXX This does not detect multiple attributes
+ * with the same attribute name.
+ */
+ if (bp != 0) {
+ while ((attr_name_val = mystrtokq(&bp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) {
+ if (*attr_name_val == CHARS_BRACE[0]) {
+ if ((heap_err = extpar(&attr_name_val, CHARS_BRACE,
+ EXTPAR_FLAG_STRIP)) != 0) {
+ msg_warn("malformed map attribute: %s", heap_err);
+ MAP_SEARCH_CREATE_RETURN(0);
+ }
+ }
+ if ((const_err = split_nameval(attr_name_val, &attr_name,
+ &attr_value)) != 0) {
+ msg_warn("malformed map attribute in '%s': '%s'",
+ map_spec, const_err);
+ MAP_SEARCH_CREATE_RETURN(0);
+ }
+ if (strcasecmp(attr_name, MAP_SEARCH_ATTR_NAME_SEARCH) != 0) {
+ msg_warn("unknown map attribute in '%s': '%s'",
+ map_spec, attr_name);
+ MAP_SEARCH_CREATE_RETURN(0);
+ }
+ }
+ }
+
+ /*
+ * Parse the search list if any.
+ */
+ if (attr_name != 0) {
+ search_order = vstring_alloc(10);
+ while ((atom = mystrtok(&attr_value, CHARS_COMMA_SP)) != 0) {
+ if ((code = name_code(map_search_actions, NAME_CODE_FLAG_NONE,
+ atom)) == MAP_SEARCH_CODE_UNKNOWN) {
+ msg_warn("unknown search type '%s' in '%s'", atom, map_spec);
+ MAP_SEARCH_CREATE_RETURN(0);
+ }
+ VSTRING_ADDCH(search_order, code);
+ }
+ VSTRING_TERMINATE(search_order);
+ }
+
+ /*
+ * Bundle up the result.
+ */
+ map_search = (MAP_SEARCH *) mymalloc(sizeof(*map_search));
+ map_search->map_type_name = mystrdup(map_type_name);
+ if (search_order) {
+ map_search->search_order = vstring_export(search_order);
+ search_order = 0;
+ } else {
+ map_search->search_order = 0;
+ }
+
+ /*
+ * Save the ACL to cache.
+ */
+ (void) htable_enter(map_search_table, map_spec, map_search);
+
+ MAP_SEARCH_CREATE_RETURN(map_search);
+}
+
+/* map_search_lookup - lookup MAP_SEARCH instance */
+
+const MAP_SEARCH *map_search_lookup(const char *map_spec)
+{
+
+ /*
+ * Sanity check.
+ */
+ if (map_search_table == 0 || map_search_actions == 0)
+ msg_panic("map_search_lookup: missing initialization");
+
+ return ((MAP_SEARCH *) htable_find(map_search_table, map_spec));
+}
+
+ /*
+ * Test driver.
+ */
+#ifdef TEST
+#include <stdlib.h>
+
+ /*
+ * Test search actions.
+ */
+#define TEST_NAME_1 "one"
+#define TEST_NAME_2 "two"
+#define TEST_CODE_1 1
+#define TEST_CODE_2 2
+
+#define BAD_NAME "bad"
+
+static const NAME_CODE search_actions[] = {
+ TEST_NAME_1, TEST_CODE_1,
+ TEST_NAME_2, TEST_CODE_2,
+ 0, MAP_SEARCH_CODE_UNKNOWN,
+};
+
+/* Helpers to simplify tests. */
+
+static const char *string_or_null(const char *s)
+{
+ return (s ? s : "(null)");
+}
+
+static char *escape_order(VSTRING *buf, const char *search_order)
+{
+ return (STR(escape(buf, search_order, strlen(search_order))));
+}
+
+int main(int argc, char **argv)
+{
+ /* Test cases with inputs and expected outputs. */
+ typedef struct TEST_CASE {
+ const char *map_spec;
+ int exp_return; /* 0=fail, 1=success */
+ const char *exp_map_type_name; /* 0 or match */
+ const char *exp_search_order; /* 0 or match */
+ } TEST_CASE;
+ static TEST_CASE test_cases[] = {
+ {"type", 0, 0, 0},
+ {"type:name", 1, "type:name", 0},
+ {"{type:name}", 1, "type:name", 0},
+ {"{type:name", 0, 0, 0}, /* } */
+ {"{type}", 0, 0, 0},
+ {"{type:name foo}", 0, 0, 0},
+ {"{type:name foo=bar}", 0, 0, 0},
+ {"{type:name search_order=}", 1, "type:name", ""},
+ {"{type:name search_order=one, two}", 0, 0, 0},
+ {"{type:name {search_order=one, two}}", 1, "type:name", "\01\02"},
+ {"{type:name {search_order=one, two, bad}}", 0, 0, 0},
+ {"{inline:{a=b} {search_order=one, two}}", 1, "inline:{a=b}", "\01\02"},
+ {"{inline:{a=b, c=d} {search_order=one, two}}", 1, "inline:{a=b, c=d}", "\01\02"},
+ {0},
+ };
+ TEST_CASE *test_case;
+
+ /* Actual results. */
+ const MAP_SEARCH *map_search_from_create;
+ const MAP_SEARCH *map_search_from_create_2nd;
+ const MAP_SEARCH *map_search_from_lookup;
+
+ /* Findings. */
+ int tests_failed = 0;
+ int test_failed;
+
+ /* Scratch */
+ VSTRING *expect_escaped = vstring_alloc(100);
+ VSTRING *actual_escaped = vstring_alloc(100);
+
+ map_search_init(search_actions);
+
+ for (tests_failed = 0, test_case = test_cases; test_case->map_spec;
+ tests_failed += test_failed, test_case++) {
+ test_failed = 0;
+ msg_info("test case %d: '%s'",
+ (int) (test_case - test_cases), test_case->map_spec);
+ map_search_from_create = map_search_create(test_case->map_spec);
+ if (!test_case->exp_return != !map_search_from_create) {
+ if (map_search_from_create)
+ msg_warn("test case %d return expected %s actual {%s, %s}",
+ (int) (test_case - test_cases),
+ test_case->exp_return ? "success" : "fail",
+ map_search_from_create->map_type_name,
+ escape_order(actual_escaped,
+ map_search_from_create->search_order));
+ else
+ msg_warn("test case %d return expected %s actual %s",
+ (int) (test_case - test_cases), "success",
+ map_search_from_create ? "success" : "fail");
+ test_failed = 1;
+ continue;
+ }
+ if (test_case->exp_return == 0)
+ continue;
+ map_search_from_lookup = map_search_lookup(test_case->map_spec);
+ if (map_search_from_create != map_search_from_lookup) {
+ msg_warn("test case %d map_search_lookup expected=%p actual=%p",
+ (int) (test_case - test_cases),
+ map_search_from_create, map_search_from_lookup);
+ test_failed = 1;
+ }
+ map_search_from_create_2nd = map_search_create(test_case->map_spec);
+ if (map_search_from_create != map_search_from_create_2nd) {
+ msg_warn("test case %d repeated map_search_create "
+ "expected=%p actual=%p",
+ (int) (test_case - test_cases),
+ map_search_from_create, map_search_from_create_2nd);
+ test_failed = 1;
+ }
+ if (strcmp(string_or_null(test_case->exp_map_type_name),
+ string_or_null(map_search_from_create->map_type_name))) {
+ msg_warn("test case %d map_type_name expected=%s actual=%s",
+ (int) (test_case - test_cases),
+ string_or_null(test_case->exp_map_type_name),
+ string_or_null(map_search_from_create->map_type_name));
+ test_failed = 1;
+ }
+ if (strcmp(string_or_null(test_case->exp_search_order),
+ string_or_null(map_search_from_create->search_order))) {
+ msg_warn("test case %d search_order expected=%s actual=%s",
+ (int) (test_case - test_cases),
+ escape_order(expect_escaped,
+ string_or_null(test_case->exp_search_order)),
+ escape_order(actual_escaped,
+ string_or_null(map_search_from_create->search_order)));
+ test_failed = 1;
+ }
+ }
+ vstring_free(expect_escaped);
+ vstring_free(actual_escaped);
+
+ if (tests_failed)
+ msg_info("tests failed: %d", tests_failed);
+ exit(tests_failed != 0);
+}
+
+#endif