xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/mail_addr_map.c (revision 33881f779a77dce6440bdc44610d94de75bebefe)
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