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