map_search.c revision 1.1
1/*	$NetBSD: map_search.c,v 1.1 2020/03/18 18:59:34 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  */
98static HTABLE *map_search_table;
99static const NAME_CODE *map_search_actions;
100
101#define STR(x) vstring_str(x)
102
103/* map_search_init - one-time initialization */
104
105void    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
115const 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
246const 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
274static 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
282static const char *string_or_null(const char *s)
283{
284    return (s ? s : "(null)");
285}
286
287static char *escape_order(VSTRING *buf, const char *search_order)
288{
289    return (STR(escape(buf, search_order, strlen(search_order))));
290}
291
292int     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