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
map_search_init(const NAME_CODE * search_actions)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
map_search_create(const char * map_spec)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
map_search_lookup(const char * map_spec)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
string_or_null(const char * s)282 static const char *string_or_null(const char *s)
283 {
284 return (s ? s : "(null)");
285 }
286
escape_order(VSTRING * buf,const char * search_order)287 static char *escape_order(VSTRING *buf, const char *search_order)
288 {
289 return (STR(escape(buf, search_order, strlen(search_order))));
290 }
291
main(int argc,char ** argv)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