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