1 /* $NetBSD: mail_addr_map.c,v 1.3 2020/03/18 19:05:16 christos Exp $ */
2
3 /*++
4 /* NAME
5 /* mail_addr_map 3
6 /* SUMMARY
7 /* generic address mapping
8 /* SYNOPSIS
9 /* #include <mail_addr_map.h>
10 /*
11 /* ARGV *mail_addr_map_internal(path, address, propagate)
12 /* MAPS *path;
13 /* const char *address;
14 /* int propagate;
15 /*
16 /* ARGV *mail_addr_map_opt(path, address, propagate, in_form,
17 /* query_form, out_form)
18 /* MAPS *path;
19 /* const char *address;
20 /* int propagate;
21 /* int in_form;
22 /* int query_form;
23 /* int out_form;
24 /* DESCRIPTION
25 /* mail_addr_map_*() returns the translation for the named address,
26 /* or a null pointer if none is found.
27 /*
28 /* With mail_addr_map_internal(), the search address and results
29 /* are in internal (unquoted) form.
30 /*
31 /* mail_addr_map_opt() gives more control, at the cost of additional
32 /* conversions between internal and external forms.
33 /*
34 /* When the \fBpropagate\fR argument is non-zero,
35 /* address extensions that aren't explicitly matched in the lookup
36 /* table are propagated to the result addresses. The caller is
37 /* expected to pass the lookup result to argv_free().
38 /*
39 /* Lookups are performed by mail_addr_find_*(). When the result has the
40 /* form \fI@otherdomain\fR, the result is the original user in
41 /* \fIotherdomain\fR.
42 /*
43 /* Arguments:
44 /* .IP path
45 /* Dictionary search path (see maps(3)).
46 /* .IP address
47 /* The address to be looked up in external (quoted) form, or
48 /* in the form specified with the in_form argument.
49 /* .IP query_form
50 /* Database query address forms, either MA_FORM_INTERNAL (unquoted
51 /* form), MA_FORM_EXTERNAL (quoted form), MA_FORM_EXTERNAL_FIRST
52 /* (external, then internal if the forms differ), or
53 /* MA_FORM_INTERNAL_FIRST (internal, then external if the forms
54 /* differ).
55 /* .IP in_form
56 /* .IP out_form
57 /* Input and output address forms, either MA_FORM_INTERNAL (unquoted
58 /* form) or MA_FORM_EXTERNAL (quoted form).
59 /* DIAGNOSTICS
60 /* Warnings: map lookup returns a non-address result.
61 /*
62 /* The path->error value is non-zero when the lookup
63 /* failed with a non-permanent error.
64 /* SEE ALSO
65 /* mail_addr_find(3), mail address matching
66 /* mail_addr_crunch(3), mail address parsing and rewriting
67 /* LICENSE
68 /* .ad
69 /* .fi
70 /* The Secure Mailer license must be distributed with this software.
71 /* AUTHOR(S)
72 /* Wietse Venema
73 /* IBM T.J. Watson Research
74 /* P.O. Box 704
75 /* Yorktown Heights, NY 10598, USA
76 /*
77 /* Wietse Venema
78 /* Google, Inc.
79 /* 111 8th Avenue
80 /* New York, NY 10011, USA
81 /*--*/
82
83 /* System library. */
84
85 #include <sys_defs.h>
86 #include <string.h>
87
88 /* Utility library. */
89
90 #include <msg.h>
91 #include <vstring.h>
92 #include <dict.h>
93 #include <argv.h>
94 #include <mymalloc.h>
95
96 /* Global library. */
97
98 #include <quote_822_local.h>
99 #include <mail_addr_find.h>
100 #include <mail_addr_crunch.h>
101 #include <mail_addr_map.h>
102
103 /* Application-specific. */
104
105 #define STR vstring_str
106 #define LEN VSTRING_LEN
107
108 /* mail_addr_map - map a canonical address */
109
mail_addr_map_opt(MAPS * path,const char * address,int propagate,int in_form,int query_form,int out_form)110 ARGV *mail_addr_map_opt(MAPS *path, const char *address, int propagate,
111 int in_form, int query_form, int out_form)
112 {
113 VSTRING *buffer = 0;
114 const char *myname = "mail_addr_map";
115 const char *string;
116 char *ratsign;
117 char *extension = 0;
118 ARGV *argv = 0;
119 int i;
120 VSTRING *int_address = 0;
121 VSTRING *ext_address = 0;
122 const char *int_addr;
123
124 /*
125 * Optionally convert input from external form. We prefer internal-form
126 * input to avoid unnecessary input conversion in mail_addr_find_opt().
127 */
128 if (in_form == MA_FORM_EXTERNAL) {
129 int_address = vstring_alloc(100);
130 unquote_822_local(int_address, address);
131 int_addr = STR(int_address);
132 in_form = MA_FORM_INTERNAL;
133 } else {
134 int_addr = address;
135 }
136
137 /*
138 * Look up the full address; if no match is found, look up the address
139 * with the extension stripped off, and remember the unmatched extension.
140 */
141 if ((string = mail_addr_find_opt(path, int_addr, &extension,
142 in_form, query_form,
143 MA_FORM_EXTERNAL,
144 MA_FIND_DEFAULT)) != 0) {
145
146 /*
147 * Prepend the original user to @otherdomain, but do not propagate
148 * the unmatched address extension. Convert the address to external
149 * form just like the mail_addr_find_opt() output.
150 */
151 if (*string == '@') {
152 buffer = vstring_alloc(100);
153 if ((ratsign = strrchr(int_addr, '@')) != 0)
154 vstring_strncpy(buffer, int_addr, ratsign - int_addr);
155 else
156 vstring_strcpy(buffer, int_addr);
157 if (extension)
158 vstring_truncate(buffer, LEN(buffer) - strlen(extension));
159 vstring_strcat(buffer, string);
160 ext_address = vstring_alloc(2 * LEN(buffer));
161 quote_822_local(ext_address, STR(buffer));
162 string = STR(ext_address);
163 }
164
165 /*
166 * Canonicalize the result, and propagate the unmatched extension to
167 * each address found.
168 */
169 argv = mail_addr_crunch_opt(string, propagate ? extension : 0,
170 MA_FORM_EXTERNAL, out_form);
171 if (buffer)
172 vstring_free(buffer);
173 if (ext_address)
174 vstring_free(ext_address);
175 if (msg_verbose)
176 for (i = 0; i < argv->argc; i++)
177 msg_info("%s: %s -> %d: %s", myname, address, i, argv->argv[i]);
178 if (argv->argc == 0) {
179 msg_warn("%s lookup of %s returns non-address result \"%s\"",
180 path->title, address, string);
181 argv = argv_free(argv);
182 path->error = DICT_ERR_RETRY;
183 }
184 }
185
186 /*
187 * No match found.
188 */
189 else {
190 if (msg_verbose)
191 msg_info("%s: %s -> %s", myname, address,
192 path->error ? "(try again)" : "(not found)");
193 }
194
195 /*
196 * Cleanup.
197 */
198 if (extension)
199 myfree(extension);
200 if (int_address)
201 vstring_free(int_address);
202
203 return (argv);
204 }
205
206 #ifdef TEST
207
208 /*
209 * SYNOPSIS
210 * mail_addr_map pass_tests | fail_tests
211 * DESCRIPTION
212 * mail_addr_map performs the specified set of built-in
213 * unit tests. With 'pass_tests', all tests must pass, and
214 * with 'fail_tests' all tests must fail.
215 * DIAGNOSTICS
216 * When a unit test fails, the program prints details of the
217 * failed test.
218 *
219 * The program terminates with a non-zero exit status when at
220 * least one test does not pass with 'pass_tests', or when at
221 * least one test does not fail with 'fail_tests'.
222 */
223
224 /* System library. */
225
226 #include <sys_defs.h>
227 #include <ctype.h>
228 #include <stdlib.h>
229 #include <string.h>
230 #include <unistd.h>
231
232 /* Utility library. */
233
234 #include <argv.h>
235 #include <msg.h>
236 #include <mymalloc.h>
237 #include <vstring.h>
238
239 /* Global library. */
240
241 #include <canon_addr.h>
242 #include <mail_addr_map.h>
243 #include <mail_params.h>
244
245 /* Application-specific. */
246
247 #define STR vstring_str
248
249 typedef struct {
250 const char *testname;
251 const char *database;
252 int propagate;
253 const char *delimiter;
254 int in_form;
255 int query_form;
256 int out_form;
257 const char *address;
258 const char *expect_argv[2];
259 int expect_argc;
260 } MAIL_ADDR_MAP_TEST;
261
262 #define DONT_PROPAGATE_UNMATCHED_EXTENSION 0
263 #define DO_PROPAGATE_UNMATCHED_EXTENSION 1
264 #define NO_RECIPIENT_DELIMITER ""
265 #define PLUS_RECIPIENT_DELIMITER "+"
266 #define DOT_RECIPIENT_DELIMITER "."
267
268 /*
269 * All these tests must pass, so that we know that mail_addr_map_opt() works
270 * as intended. mail_addr_map() has always been used for maps that expect
271 * external-form queries, so there are no tests here for internal-form
272 * queries.
273 */
274 static MAIL_ADDR_MAP_TEST pass_tests[] = {
275 {
276 "1 external -external-> external, no extension",
277 "inline:{ aa@example.com=bb@example.com }",
278 DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
279 MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
280 "aa@example.com",
281 {"bb@example.com"}, 1,
282 },
283 {
284 "2 external -external-> external, extension, propagation",
285 "inline:{ aa@example.com=bb@example.com }",
286 DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
287 MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
288 "aa+ext@example.com",
289 {"bb+ext@example.com"}, 1,
290 },
291 {
292 "3 external -external-> external, extension, no propagation, no match",
293 "inline:{ aa@example.com=bb@example.com }",
294 DONT_PROPAGATE_UNMATCHED_EXTENSION, NO_RECIPIENT_DELIMITER,
295 MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
296 "aa+ext@example.com",
297 {0}, 0,
298 },
299 {
300 "4 external -external-> external, extension, full match",
301 "inline:{{cc+ext@example.com=dd@example.com,ee@example.com}}",
302 DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
303 MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
304 "cc+ext@example.com",
305 {"dd@example.com", "ee@example.com"}, 2,
306 },
307 {
308 "5 external -external-> external, no extension, quoted",
309 "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }",
310 DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
311 MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
312 "\"a a\"@example.com",
313 {"\"b b\"@example.com"}, 1,
314 },
315 {
316 "6 external -external-> external, extension, propagation, quoted",
317 "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }",
318 DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
319 MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
320 "\"a a+ext\"@example.com",
321 {"\"b b+ext\"@example.com"}, 1,
322 },
323 {
324 "7 internal -external-> internal, no extension, propagation, embedded space",
325 "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }",
326 DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
327 MA_FORM_INTERNAL, MA_FORM_EXTERNAL, MA_FORM_INTERNAL,
328 "a a@example.com",
329 {"b b@example.com"}, 1,
330 },
331 {
332 "8 internal -external-> internal, extension, propagation, embedded space",
333 "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }",
334 DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
335 MA_FORM_INTERNAL, MA_FORM_EXTERNAL, MA_FORM_INTERNAL,
336 "a a+ext@example.com",
337 {"b b+ext@example.com"}, 1,
338 },
339 {
340 "9 internal -external-> internal, no extension, propagation, embedded space",
341 "inline:{ {a_a@example.com=\"b b\"@example.com} }",
342 DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
343 MA_FORM_INTERNAL, MA_FORM_EXTERNAL, MA_FORM_INTERNAL,
344 "a_a@example.com",
345 {"b b@example.com"}, 1,
346 },
347 {
348 "10 internal -external-> internal, extension, propagation, embedded space",
349 "inline:{ {a_a@example.com=\"b b\"@example.com} }",
350 DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
351 MA_FORM_INTERNAL, MA_FORM_EXTERNAL, MA_FORM_INTERNAL,
352 "a_a+ext@example.com",
353 {"b b+ext@example.com"}, 1,
354 },
355 {
356 "11 internal -external-> internal, no extension, @domain",
357 "inline:{ {@example.com=@example.net} }",
358 DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
359 MA_FORM_INTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
360 "a@a@example.com",
361 {"\"a@a\"@example.net"}, 1,
362 },
363 {
364 "12 external -external-> external, extension, propagation",
365 "inline:{ aa@example.com=bb@example.com }",
366 DO_PROPAGATE_UNMATCHED_EXTENSION, DOT_RECIPIENT_DELIMITER,
367 MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
368 "aa.ext@example.com",
369 {"bb.ext@example.com"}, 1,
370 },
371 0,
372 };
373
374 /*
375 * All these tests must fail, so that we know that the tests work.
376 */
377 static MAIL_ADDR_MAP_TEST fail_tests[] = {
378 {
379 "selftest 1 external -external-> external, no extension, quoted",
380 "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }",
381 DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
382 MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
383 "\"a a\"@example.com",
384 {"\"bXb\"@example.com"}, 1,
385 },
386 {
387 "selftest 2 external -external-> external, no extension, quoted",
388 "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }",
389 DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
390 MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
391 "\"aXa\"@example.com",
392 {"\"b b\"@example.com"}, 1,
393 },
394 {
395 "selftest 3 external -external-> external, no extension, quoted",
396 "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }",
397 DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER,
398 MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL,
399 "\"a a\"@example.com",
400 {0}, 0,
401 },
402 0,
403 };
404
405 /* canon_addr_external - surrogate to avoid trivial-rewrite dependency */
406
canon_addr_external(VSTRING * result,const char * addr)407 VSTRING *canon_addr_external(VSTRING *result, const char *addr)
408 {
409 return (vstring_strcpy(result, addr));
410 }
411
compare(const char * testname,const char ** expect_argv,int expect_argc,char ** result_argv,int result_argc)412 static int compare(const char *testname,
413 const char **expect_argv, int expect_argc,
414 char **result_argv, int result_argc)
415 {
416 int n;
417 int err = 0;
418
419 if (expect_argc != 0 && result_argc != 0) {
420 for (n = 0; n < expect_argc && n < result_argc; n++) {
421 if (strcmp(expect_argv[n], result_argv[n]) != 0) {
422 msg_warn("fail test %s: expect[%d]='%s', result[%d]='%s'",
423 testname, n, expect_argv[n], n, result_argv[n]);
424 err = 1;
425 }
426 }
427 }
428 if (expect_argc != result_argc) {
429 msg_warn("fail test %s: expects %d results but there were %d",
430 testname, expect_argc, result_argc);
431 for (n = expect_argc; n < result_argc; n++)
432 msg_info("no expect to match result[%d]='%s'", n, result_argv[n]);
433 for (n = result_argc; n < expect_argc; n++)
434 msg_info("no result to match expect[%d]='%s'", n, expect_argv[n]);
435 err = 1;
436 }
437 return (err);
438 }
439
440 static char *progname;
441
usage(void)442 static NORETURN usage(void)
443 {
444 msg_fatal("usage: %s pass_test | fail_test", progname);
445 }
446
main(int argc,char ** argv)447 int main(int argc, char **argv)
448 {
449 MAIL_ADDR_MAP_TEST *test;
450 MAIL_ADDR_MAP_TEST *tests;
451 int errs = 0;
452
453 #define UPDATE(dst, src) { if (dst) myfree(dst); dst = mystrdup(src); }
454
455 /*
456 * Parse JCL.
457 */
458 progname = argv[0];
459 if (argc != 2) {
460 usage();
461 } else if (strcmp(argv[1], "pass_tests") == 0) {
462 tests = pass_tests;
463 } else if (strcmp(argv[1], "fail_tests") == 0) {
464 tests = fail_tests;
465 } else {
466 usage();
467 }
468
469 /*
470 * Initialize.
471 */
472 mail_params_init();
473
474 /*
475 * A read-eval-print loop, because specifying C strings with quotes and
476 * backslashes is painful.
477 */
478 for (test = tests; test->testname; test++) {
479 ARGV *result;
480 int fail = 0;
481
482 if (mail_addr_form_to_string(test->in_form) == 0) {
483 msg_warn("test %s: bad in_form field: %d",
484 test->testname, test->in_form);
485 fail = 1;
486 continue;
487 }
488 if (mail_addr_form_to_string(test->query_form) == 0) {
489 msg_warn("test %s: bad query_form field: %d",
490 test->testname, test->query_form);
491 fail = 1;
492 continue;
493 }
494 if (mail_addr_form_to_string(test->out_form) == 0) {
495 msg_warn("test %s: bad out_form field: %d",
496 test->testname, test->out_form);
497 fail = 1;
498 continue;
499 }
500 MAPS *maps = maps_create("test", test->database, DICT_FLAG_LOCK
501 | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST);
502
503 UPDATE(var_rcpt_delim, test->delimiter);
504 result = mail_addr_map_opt(maps, test->address, test->propagate,
505 test->in_form, test->query_form, test->out_form);
506 if (compare(test->testname, test->expect_argv, test->expect_argc,
507 result ? result->argv : 0, result ? result->argc : 0) != 0) {
508 msg_info("database = %s", test->database);
509 msg_info("propagate = %d", test->propagate);
510 msg_info("delimiter = '%s'", var_rcpt_delim);
511 msg_info("in_form = %s", mail_addr_form_to_string(test->in_form));
512 msg_info("query_form = %s", mail_addr_form_to_string(test->query_form));
513 msg_info("out_form = %s", mail_addr_form_to_string(test->out_form));
514 msg_info("address = %s", test->address);
515 fail = 1;
516 }
517 maps_free(maps);
518 if (result)
519 argv_free(result);
520
521 /*
522 * It is an error if a test does not pass or fail as intended.
523 */
524 errs += (tests == pass_tests ? fail : !fail);
525 }
526 return (errs != 0);
527 }
528
529 #endif
530