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 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 = 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 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 {"{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