xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/map_search.c (revision 154bfe8e089c1a0a4e9ed8414f08d3da90949162)
1 /*	$NetBSD: map_search.c,v 1.2 2020/03/18 19:05:16 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 = mystrtok(&bp, CHARS_COMMA_SP)) == 0) {
164 	    msg_warn("empty map specification: '%s'", map_spec);
165 	    MAP_SEARCH_CREATE_RETURN(0);
166 	}
167     } else {
168 	map_type_name = map_spec;
169     }
170 
171     /*
172      * Sanity check the map spec before parsing attributes.
173      */
174     if (strchr(map_type_name, ':') == 0) {
175 	msg_warn("malformed map specification: '%s'", map_spec);
176 	msg_warn("expected maptype:mapname instead of '%s'", map_type_name);
177 	MAP_SEARCH_CREATE_RETURN(0);
178     }
179 
180     /*
181      * Parse the attribute list. XXX This does not detect multiple attributes
182      * with the same attribute name.
183      */
184     if (bp != 0) {
185 	while ((attr_name_val = mystrtokq(&bp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) {
186 	    if (*attr_name_val == CHARS_BRACE[0]) {
187 		if ((heap_err = extpar(&attr_name_val, CHARS_BRACE,
188 				       EXTPAR_FLAG_STRIP)) != 0) {
189 		    msg_warn("malformed map attribute: %s", heap_err);
190 		    MAP_SEARCH_CREATE_RETURN(0);
191 		}
192 	    }
193 	    msg_info("split_nameval(\"%s\"", attr_name_val);
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 	{0},
315     };
316     TEST_CASE *test_case;
317 
318     /* Actual results. */
319     const MAP_SEARCH *map_search_from_create;
320     const MAP_SEARCH *map_search_from_create_2nd;
321     const MAP_SEARCH *map_search_from_lookup;
322 
323     /* Findings. */
324     int     tests_failed = 0;
325     int     test_failed;
326 
327     /* Scratch */
328     VSTRING *expect_escaped = vstring_alloc(100);
329     VSTRING *actual_escaped = vstring_alloc(100);
330 
331     map_search_init(search_actions);
332 
333     for (tests_failed = 0, test_case = test_cases; test_case->map_spec;
334 	 tests_failed += test_failed, test_case++) {
335 	test_failed = 0;
336 	msg_info("test case %d: '%s'",
337 		 (int) (test_case - test_cases), test_case->map_spec);
338 	map_search_from_create = map_search_create(test_case->map_spec);
339 	if (!test_case->exp_return != !map_search_from_create) {
340 	    if (map_search_from_create)
341 		msg_warn("test case %d return expected %s actual {%s, %s}",
342 			 (int) (test_case - test_cases),
343 			 test_case->exp_return ? "success" : "fail",
344 			 map_search_from_create->map_type_name,
345 			 escape_order(actual_escaped,
346 				      map_search_from_create->search_order));
347 	    else
348 		msg_warn("test case %d return expected %s actual %s",
349 			 (int) (test_case - test_cases), "success",
350 			 map_search_from_create ? "success" : "fail");
351 	    test_failed = 1;
352 	    continue;
353 	}
354 	if (test_case->exp_return == 0)
355 	    continue;
356 	map_search_from_lookup = map_search_lookup(test_case->map_spec);
357 	if (map_search_from_create != map_search_from_lookup) {
358 	    msg_warn("test case %d map_search_lookup expected=%p actual=%p",
359 		     (int) (test_case - test_cases),
360 		     map_search_from_create, map_search_from_lookup);
361 	    test_failed = 1;
362 	}
363 	map_search_from_create_2nd = map_search_create(test_case->map_spec);
364 	if (map_search_from_create != map_search_from_create_2nd) {
365 	    msg_warn("test case %d repeated map_search_create "
366 		     "expected=%p actual=%p",
367 		     (int) (test_case - test_cases),
368 		     map_search_from_create, map_search_from_create_2nd);
369 	    test_failed = 1;
370 	}
371 	if (strcmp(string_or_null(test_case->exp_map_type_name),
372 		   string_or_null(map_search_from_create->map_type_name))) {
373 	    msg_warn("test case %d map_type_name expected=%s actual=%s",
374 		     (int) (test_case - test_cases),
375 		     string_or_null(test_case->exp_map_type_name),
376 		     string_or_null(map_search_from_create->map_type_name));
377 	    test_failed = 1;
378 	}
379 	if (strcmp(string_or_null(test_case->exp_search_order),
380 		   string_or_null(map_search_from_create->search_order))) {
381 	    msg_warn("test case %d search_order expected=%s actual=%s",
382 		     (int) (test_case - test_cases),
383 		     escape_order(expect_escaped,
384 			       string_or_null(test_case->exp_search_order)),
385 		     escape_order(actual_escaped,
386 		     string_or_null(map_search_from_create->search_order)));
387 	    test_failed = 1;
388 	}
389     }
390     vstring_free(expect_escaped);
391     vstring_free(actual_escaped);
392 
393     if (tests_failed)
394 	msg_info("tests failed: %d", tests_failed);
395     exit(tests_failed != 0);
396 }
397 
398 #endif
399