Home | History | Annotate | Line # | Download | only in global
      1 /*	$NetBSD: map_search.c,v 1.4 2023/12/23 20:30:43 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	map_search_search 3
      6 /* SUMMARY
      7 /*	lookup table search list support
      8 /* SYNOPSIS
      9 /*	#include <map_search_search.h>
     10 /*
     11 /*	typedef struct {
     12 /* .in +4
     13 /*	    char   *map_type_name;	/* type:name, owned */
     14 /*	    char   *search_order;	/* null or owned */
     15 /* .in -4
     16 /*	} MAP_SEARCH;
     17 /*
     18 /*	void	map_search_init(
     19 /*	const NAME_CODE *search_actions)
     20 /*
     21 /*	const MAP_SEARCH *map_search_create(
     22 /*	const char *map_spec)
     23 /*
     24 /*	const MAP_SEARCH *map_search_lookup(
     25 /*	const char *map_spec);
     26 /* DESCRIPTION
     27 /*	This module implements configurable search order support
     28 /*	for Postfix lookup tables.
     29 /*
     30 /*	map_search_init() must be called once, before other functions
     31 /*	in this module.
     32 /*
     33 /*	map_search_create() creates a MAP_SEARCH instance for
     34 /*	map_spec, ignoring duplicate requests.
     35 /*
     36 /*	map_search_lookup() looks up the MAP_SEARCH instance that
     37 /*	was created by map_search_create().
     38 /*
     39 /*	Arguments:
     40 /* .IP search_actions
     41 /*	The mapping from search action string form to numeric form.
     42 /*	The numbers must be in the range [1..126] (inclusive). The
     43 /*	value 0 is reserved for the MAP_SEARCH.search_order terminator,
     44 /*	and the value MAP_SEARCH_CODE_UNKNOWN is reserved for the
     45 /*	'not found' result. The argument is copied (the pointer
     46 /*	value, not the table).
     47 /* .IP map_spec
     48 /*	lookup table and optional search order: either maptype:mapname,
     49 /*	or { maptype:mapname, { search = name, name }}. The search
     50 /*	attribute is optional. The comma is equivalent to whitespace.
     51 /* DIAGNOSTICS
     52 /*	map_search_create() returns a null pointer when a map_spec
     53 /*	is a) malformed, b) specifies an unexpected attribute name,
     54 /*	c) the search attribute contains an unknown name. Thus,
     55 /*	map_search_create() will never return a search_order that
     56 /*	contains the value MAP_SEARCH_CODE_UNKNOWN.
     57 /*
     58 /*	Panic: interface violations. Fatal errors: out of memory.
     59 /* LICENSE
     60 /* .ad
     61 /* .fi
     62 /*	The Secure Mailer license must be distributed with this software.
     63 /* AUTHOR(S)
     64 /*	Wietse Venema
     65 /*	Google, Inc.
     66 /*	111 8th Avenue
     67 /*	New York, NY 10011, USA
     68 /*--*/
     69 
     70  /*
     71   * System library.
     72   */
     73 #include <sys_defs.h>
     74 #include <string.h>
     75 
     76 #ifdef STRCASECMP_IN_STRINGS_H
     77 #include <strings.h>
     78 #endif
     79 
     80  /*
     81   * Utility library.
     82   */
     83 #include <htable.h>
     84 #include <msg.h>
     85 #include <mymalloc.h>
     86 #include <name_code.h>
     87 #include <stringops.h>
     88 #include <vstring.h>
     89 
     90  /*
     91   * Global library.
     92   */
     93 #include <map_search.h>
     94 
     95  /*
     96   * Application-specific.
     97   */
     98 static HTABLE *map_search_table;
     99 static const NAME_CODE *map_search_actions;
    100 
    101 #define STR(x) vstring_str(x)
    102 
    103 /* map_search_init - one-time initialization */
    104 
    105 void    map_search_init(const NAME_CODE *search_actions)
    106 {
    107     if (map_search_table != 0 || map_search_actions != 0)
    108 	msg_panic("map_search_init: multiple calls");
    109     map_search_table = htable_create(100);
    110     map_search_actions = search_actions;
    111 }
    112 
    113 /* map_search_create - store MAP_SEARCH instance */
    114 
    115 const MAP_SEARCH *map_search_create(const char *map_spec)
    116 {
    117     char   *copy_of_map_spec = 0;
    118     char   *bp = 0;
    119     const char *const_err;
    120     char   *heap_err = 0;
    121     VSTRING *search_order = 0;
    122     const char *map_type_name;
    123     char   *attr_name_val = 0;
    124     char   *attr_name = 0;
    125     char   *attr_value = 0;
    126     MAP_SEARCH *map_search;
    127     char   *atom;
    128     int     code;
    129 
    130     /*
    131      * Sanity check.
    132      */
    133     if (map_search_table == 0 || map_search_actions == 0)
    134 	msg_panic("map_search_create: missing initialization");
    135 
    136     /*
    137      * Allow exact duplicates. This won't catch duplicates that differ only
    138      * in their use of whitespace or comma.
    139      */
    140     if ((map_search =
    141 	 (MAP_SEARCH *) htable_find(map_search_table, map_spec)) != 0)
    142 	return (map_search);
    143 
    144     /*
    145      * Macro for readability and safety. Let the compiler worry about code
    146      * duplication and redundant conditions.
    147      */
    148 #define MAP_SEARCH_CREATE_RETURN(x) do { \
    149 	if (copy_of_map_spec) myfree(copy_of_map_spec); \
    150 	if (heap_err) myfree(heap_err); \
    151 	if (search_order) vstring_free(search_order); \
    152 	return (x); \
    153     } while (0)
    154 
    155     /*
    156      * Long form specifies maptype_mapname and optional search attribute.
    157      */
    158     if (*map_spec == CHARS_BRACE[0]) {
    159 	bp = copy_of_map_spec = mystrdup(map_spec);
    160 	if ((heap_err = extpar(&bp, CHARS_BRACE, EXTPAR_FLAG_STRIP)) != 0) {
    161 	    msg_warn("malformed map specification: '%s'", heap_err);
    162 	    MAP_SEARCH_CREATE_RETURN(0);
    163 	} else if ((map_type_name = mystrtokq(&bp, CHARS_COMMA_SP,
    164 					      CHARS_BRACE)) == 0) {
    165 	    msg_warn("empty map specification: '%s'", map_spec);
    166 	    MAP_SEARCH_CREATE_RETURN(0);
    167 	}
    168     } else {
    169 	map_type_name = map_spec;
    170     }
    171 
    172     /*
    173      * Sanity check the map spec before parsing attributes.
    174      */
    175     if (strchr(map_type_name, ':') == 0) {
    176 	msg_warn("malformed map specification: '%s'", map_spec);
    177 	msg_warn("expected maptype:mapname instead of '%s'", map_type_name);
    178 	MAP_SEARCH_CREATE_RETURN(0);
    179     }
    180 
    181     /*
    182      * Parse the attribute list. XXX This does not detect multiple attributes
    183      * with the same attribute name.
    184      */
    185     if (bp != 0) {
    186 	while ((attr_name_val = mystrtokq(&bp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) {
    187 	    if (*attr_name_val == CHARS_BRACE[0]) {
    188 		if ((heap_err = extpar(&attr_name_val, CHARS_BRACE,
    189 				       EXTPAR_FLAG_STRIP)) != 0) {
    190 		    msg_warn("malformed map attribute: %s", heap_err);
    191 		    MAP_SEARCH_CREATE_RETURN(0);
    192 		}
    193 	    }
    194 	    if ((const_err = split_nameval(attr_name_val, &attr_name,
    195 					   &attr_value)) != 0) {
    196 		msg_warn("malformed map attribute in '%s': '%s'",
    197 			 map_spec, const_err);
    198 		MAP_SEARCH_CREATE_RETURN(0);
    199 	    }
    200 	    if (strcasecmp(attr_name, MAP_SEARCH_ATTR_NAME_SEARCH) != 0) {
    201 		msg_warn("unknown map attribute in '%s': '%s'",
    202 			 map_spec, attr_name);
    203 		MAP_SEARCH_CREATE_RETURN(0);
    204 	    }
    205 	}
    206     }
    207 
    208     /*
    209      * Parse the search list if any.
    210      */
    211     if (attr_name != 0) {
    212 	search_order = vstring_alloc(10);
    213 	while ((atom = mystrtok(&attr_value, CHARS_COMMA_SP)) != 0) {
    214 	    if ((code = name_code(map_search_actions, NAME_CODE_FLAG_NONE,
    215 				  atom)) == MAP_SEARCH_CODE_UNKNOWN) {
    216 		msg_warn("unknown search type '%s' in '%s'", atom, map_spec);
    217 		MAP_SEARCH_CREATE_RETURN(0);
    218 	    }
    219 	    VSTRING_ADDCH(search_order, code);
    220 	}
    221 	VSTRING_TERMINATE(search_order);
    222     }
    223 
    224     /*
    225      * Bundle up the result.
    226      */
    227     map_search = (MAP_SEARCH *) mymalloc(sizeof(*map_search));
    228     map_search->map_type_name = mystrdup(map_type_name);
    229     if (search_order) {
    230 	map_search->search_order = vstring_export(search_order);
    231 	search_order = 0;
    232     } else {
    233 	map_search->search_order = 0;
    234     }
    235 
    236     /*
    237      * Save the ACL to cache.
    238      */
    239     (void) htable_enter(map_search_table, map_spec, map_search);
    240 
    241     MAP_SEARCH_CREATE_RETURN(map_search);
    242 }
    243 
    244 /* map_search_lookup - lookup MAP_SEARCH instance */
    245 
    246 const MAP_SEARCH *map_search_lookup(const char *map_spec)
    247 {
    248 
    249     /*
    250      * Sanity check.
    251      */
    252     if (map_search_table == 0 || map_search_actions == 0)
    253 	msg_panic("map_search_lookup: missing initialization");
    254 
    255     return ((MAP_SEARCH *) htable_find(map_search_table, map_spec));
    256 }
    257 
    258  /*
    259   * Test driver.
    260   */
    261 #ifdef TEST
    262 #include <stdlib.h>
    263 
    264  /*
    265   * Test search actions.
    266   */
    267 #define TEST_NAME_1	"one"
    268 #define TEST_NAME_2	"two"
    269 #define TEST_CODE_1	1
    270 #define TEST_CODE_2	2
    271 
    272 #define BAD_NAME	"bad"
    273 
    274 static const NAME_CODE search_actions[] = {
    275     TEST_NAME_1, TEST_CODE_1,
    276     TEST_NAME_2, TEST_CODE_2,
    277     0, MAP_SEARCH_CODE_UNKNOWN,
    278 };
    279 
    280 /* Helpers to simplify tests. */
    281 
    282 static const char *string_or_null(const char *s)
    283 {
    284     return (s ? s : "(null)");
    285 }
    286 
    287 static char *escape_order(VSTRING *buf, const char *search_order)
    288 {
    289     return (STR(escape(buf, search_order, strlen(search_order))));
    290 }
    291 
    292 int     main(int argc, char **argv)
    293 {
    294     /* Test cases with inputs and expected outputs. */
    295     typedef struct TEST_CASE {
    296 	const char *map_spec;
    297 	int     exp_return;		/* 0=fail, 1=success */
    298 	const char *exp_map_type_name;	/* 0 or match */
    299 	const char *exp_search_order;	/* 0 or match */
    300     } TEST_CASE;
    301     static TEST_CASE test_cases[] = {
    302 	{"type", 0, 0, 0},
    303 	{"type:name", 1, "type:name", 0},
    304 	{"{type:name}", 1, "type:name", 0},
    305 	{"{type:name", 0, 0, 0},	/* } */
    306 	{"{type}", 0, 0, 0},
    307 	{"{type:name foo}", 0, 0, 0},
    308 	{"{type:name foo=bar}", 0, 0, 0},
    309 	{"{type:name search_order=}", 1, "type:name", ""},
    310 	{"{type:name search_order=one, two}", 0, 0, 0},
    311 	{"{type:name {search_order=one, two}}", 1, "type:name", "\01\02"},
    312 	{"{type:name {search_order=one, two, bad}}", 0, 0, 0},
    313 	{"{inline:{a=b} {search_order=one, two}}", 1, "inline:{a=b}", "\01\02"},
    314 	{"{inline:{a=b, c=d} {search_order=one, two}}", 1, "inline:{a=b, c=d}", "\01\02"},
    315 	{0},
    316     };
    317     TEST_CASE *test_case;
    318 
    319     /* Actual results. */
    320     const MAP_SEARCH *map_search_from_create;
    321     const MAP_SEARCH *map_search_from_create_2nd;
    322     const MAP_SEARCH *map_search_from_lookup;
    323 
    324     /* Findings. */
    325     int     tests_failed = 0;
    326     int     test_failed;
    327 
    328     /* Scratch */
    329     VSTRING *expect_escaped = vstring_alloc(100);
    330     VSTRING *actual_escaped = vstring_alloc(100);
    331 
    332     map_search_init(search_actions);
    333 
    334     for (tests_failed = 0, test_case = test_cases; test_case->map_spec;
    335 	 tests_failed += test_failed, test_case++) {
    336 	test_failed = 0;
    337 	msg_info("test case %d: '%s'",
    338 		 (int) (test_case - test_cases), test_case->map_spec);
    339 	map_search_from_create = map_search_create(test_case->map_spec);
    340 	if (!test_case->exp_return != !map_search_from_create) {
    341 	    if (map_search_from_create)
    342 		msg_warn("test case %d return expected %s actual {%s, %s}",
    343 			 (int) (test_case - test_cases),
    344 			 test_case->exp_return ? "success" : "fail",
    345 			 map_search_from_create->map_type_name,
    346 			 escape_order(actual_escaped,
    347 				      map_search_from_create->search_order));
    348 	    else
    349 		msg_warn("test case %d return expected %s actual %s",
    350 			 (int) (test_case - test_cases), "success",
    351 			 map_search_from_create ? "success" : "fail");
    352 	    test_failed = 1;
    353 	    continue;
    354 	}
    355 	if (test_case->exp_return == 0)
    356 	    continue;
    357 	map_search_from_lookup = map_search_lookup(test_case->map_spec);
    358 	if (map_search_from_create != map_search_from_lookup) {
    359 	    msg_warn("test case %d map_search_lookup expected=%p actual=%p",
    360 		     (int) (test_case - test_cases),
    361 		     map_search_from_create, map_search_from_lookup);
    362 	    test_failed = 1;
    363 	}
    364 	map_search_from_create_2nd = map_search_create(test_case->map_spec);
    365 	if (map_search_from_create != map_search_from_create_2nd) {
    366 	    msg_warn("test case %d repeated map_search_create "
    367 		     "expected=%p actual=%p",
    368 		     (int) (test_case - test_cases),
    369 		     map_search_from_create, map_search_from_create_2nd);
    370 	    test_failed = 1;
    371 	}
    372 	if (strcmp(string_or_null(test_case->exp_map_type_name),
    373 		   string_or_null(map_search_from_create->map_type_name))) {
    374 	    msg_warn("test case %d map_type_name expected=%s actual=%s",
    375 		     (int) (test_case - test_cases),
    376 		     string_or_null(test_case->exp_map_type_name),
    377 		     string_or_null(map_search_from_create->map_type_name));
    378 	    test_failed = 1;
    379 	}
    380 	if (strcmp(string_or_null(test_case->exp_search_order),
    381 		   string_or_null(map_search_from_create->search_order))) {
    382 	    msg_warn("test case %d search_order expected=%s actual=%s",
    383 		     (int) (test_case - test_cases),
    384 		     escape_order(expect_escaped,
    385 			       string_or_null(test_case->exp_search_order)),
    386 		     escape_order(actual_escaped,
    387 		     string_or_null(map_search_from_create->search_order)));
    388 	    test_failed = 1;
    389 	}
    390     }
    391     vstring_free(expect_escaped);
    392     vstring_free(actual_escaped);
    393 
    394     if (tests_failed)
    395 	msg_info("tests failed: %d", tests_failed);
    396     exit(tests_failed != 0);
    397 }
    398 
    399 #endif
    400