/*++ /* NAME /* map_search_search 3 /* SUMMARY /* lookup table search list support /* SYNOPSIS /* #include /* /* 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 #include #ifdef STRCASECMP_IN_STRINGS_H #include #endif /* * Utility library. */ #include #include #include #include #include #include /* * Global library. */ #include /* * 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 /* * 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