diff options
Diffstat (limited to '')
-rw-r--r-- | src/global/mail_addr_map.c | 527 |
1 files changed, 527 insertions, 0 deletions
diff --git a/src/global/mail_addr_map.c b/src/global/mail_addr_map.c new file mode 100644 index 0000000..f226628 --- /dev/null +++ b/src/global/mail_addr_map.c @@ -0,0 +1,527 @@ +/*++ +/* NAME +/* mail_addr_map 3 +/* SUMMARY +/* generic address mapping +/* SYNOPSIS +/* #include <mail_addr_map.h> +/* +/* ARGV *mail_addr_map_internal(path, address, propagate) +/* MAPS *path; +/* const char *address; +/* int propagate; +/* +/* ARGV *mail_addr_map_opt(path, address, propagate, in_form, +/* query_form, out_form) +/* MAPS *path; +/* const char *address; +/* int propagate; +/* int in_form; +/* int query_form; +/* int out_form; +/* DESCRIPTION +/* mail_addr_map_*() returns the translation for the named address, +/* or a null pointer if none is found. +/* +/* With mail_addr_map_internal(), the search address and results +/* are in internal (unquoted) form. +/* +/* mail_addr_map_opt() gives more control, at the cost of additional +/* conversions between internal and external forms. +/* +/* When the \fBpropagate\fR argument is non-zero, +/* address extensions that aren't explicitly matched in the lookup +/* table are propagated to the result addresses. The caller is +/* expected to pass the lookup result to argv_free(). +/* +/* Lookups are performed by mail_addr_find_*(). When the result has the +/* form \fI@otherdomain\fR, the result is the original user in +/* \fIotherdomain\fR. +/* +/* Arguments: +/* .IP path +/* Dictionary search path (see maps(3)). +/* .IP address +/* The address to be looked up in external (quoted) form, or +/* in the form specified with the in_form argument. +/* .IP query_form +/* Database query address forms, either MA_FORM_INTERNAL (unquoted +/* form), MA_FORM_EXTERNAL (quoted form), MA_FORM_EXTERNAL_FIRST +/* (external, then internal if the forms differ), or +/* MA_FORM_INTERNAL_FIRST (internal, then external if the forms +/* differ). +/* .IP in_form +/* .IP out_form +/* Input and output address forms, either MA_FORM_INTERNAL (unquoted +/* form) or MA_FORM_EXTERNAL (quoted form). +/* DIAGNOSTICS +/* Warnings: map lookup returns a non-address result. +/* +/* The path->error value is non-zero when the lookup +/* failed with a non-permanent error. +/* SEE ALSO +/* mail_addr_find(3), mail address matching +/* mail_addr_crunch(3), mail address parsing and rewriting +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstring.h> +#include <dict.h> +#include <argv.h> +#include <mymalloc.h> + +/* Global library. */ + +#include <quote_822_local.h> +#include <mail_addr_find.h> +#include <mail_addr_crunch.h> +#include <mail_addr_map.h> + +/* Application-specific. */ + +#define STR vstring_str +#define LEN VSTRING_LEN + +/* mail_addr_map - map a canonical address */ + +ARGV *mail_addr_map_opt(MAPS *path, const char *address, int propagate, + int in_form, int query_form, int out_form) +{ + VSTRING *buffer = 0; + const char *myname = "mail_addr_map"; + const char *string; + char *ratsign; + char *extension = 0; + ARGV *argv = 0; + int i; + VSTRING *int_address = 0; + VSTRING *ext_address = 0; + const char *int_addr; + + /* + * Optionally convert input from external form. We prefer internal-form + * input to avoid unnecessary input conversion in mail_addr_find_opt(). + */ + if (in_form == MA_FORM_EXTERNAL) { + int_address = vstring_alloc(100); + unquote_822_local(int_address, address); + int_addr = STR(int_address); + in_form = MA_FORM_INTERNAL; + } else { + int_addr = address; + } + + /* + * Look up the full address; if no match is found, look up the address + * with the extension stripped off, and remember the unmatched extension. + */ + if ((string = mail_addr_find_opt(path, int_addr, &extension, + in_form, query_form, + MA_FORM_EXTERNAL, + MA_FIND_DEFAULT)) != 0) { + + /* + * Prepend the original user to @otherdomain, but do not propagate + * the unmatched address extension. Convert the address to external + * form just like the mail_addr_find_opt() output. + */ + if (*string == '@') { + buffer = vstring_alloc(100); + if ((ratsign = strrchr(int_addr, '@')) != 0) + vstring_strncpy(buffer, int_addr, ratsign - int_addr); + else + vstring_strcpy(buffer, int_addr); + if (extension) + vstring_truncate(buffer, LEN(buffer) - strlen(extension)); + vstring_strcat(buffer, string); + ext_address = vstring_alloc(2 * LEN(buffer)); + quote_822_local(ext_address, STR(buffer)); + string = STR(ext_address); + } + + /* + * Canonicalize the result, and propagate the unmatched extension to + * each address found. + */ + argv = mail_addr_crunch_opt(string, propagate ? extension : 0, + MA_FORM_EXTERNAL, out_form); + if (buffer) + vstring_free(buffer); + if (ext_address) + vstring_free(ext_address); + if (msg_verbose) + for (i = 0; i < argv->argc; i++) + msg_info("%s: %s -> %d: %s", myname, address, i, argv->argv[i]); + if (argv->argc == 0) { + msg_warn("%s lookup of %s returns non-address result \"%s\"", + path->title, address, string); + argv = argv_free(argv); + path->error = DICT_ERR_RETRY; + } + } + + /* + * No match found. + */ + else { + if (msg_verbose) + msg_info("%s: %s -> %s", myname, address, + path->error ? "(try again)" : "(not found)"); + } + + /* + * Cleanup. + */ + if (extension) + myfree(extension); + if (int_address) + vstring_free(int_address); + + return (argv); +} + +#ifdef TEST + +/* + * SYNOPSIS + * mail_addr_map pass_tests | fail_tests + * DESCRIPTION + * mail_addr_map performs the specified set of built-in + * unit tests. With 'pass_tests', all tests must pass, and + * with 'fail_tests' all tests must fail. + * DIAGNOSTICS + * When a unit test fails, the program prints details of the + * failed test. + * + * The program terminates with a non-zero exit status when at + * least one test does not pass with 'pass_tests', or when at + * least one test does not fail with 'fail_tests'. + */ + +/* System library. */ + +#include <sys_defs.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/* Utility library. */ + +#include <argv.h> +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> + +/* Global library. */ + +#include <canon_addr.h> +#include <mail_addr_map.h> +#include <mail_params.h> + +/* Application-specific. */ + +#define STR vstring_str + +typedef struct { + const char *testname; + const char *database; + int propagate; + const char *delimiter; + int in_form; + int query_form; + int out_form; + const char *address; + const char *expect_argv[2]; + int expect_argc; +} MAIL_ADDR_MAP_TEST; + +#define DONT_PROPAGATE_UNMATCHED_EXTENSION 0 +#define DO_PROPAGATE_UNMATCHED_EXTENSION 1 +#define NO_RECIPIENT_DELIMITER "" +#define PLUS_RECIPIENT_DELIMITER "+" +#define DOT_RECIPIENT_DELIMITER "." + + /* + * All these tests must pass, so that we know that mail_addr_map_opt() works + * as intended. mail_addr_map() has always been used for maps that expect + * external-form queries, so there are no tests here for internal-form + * queries. + */ +static MAIL_ADDR_MAP_TEST pass_tests[] = { + { + "1 external -external-> external, no extension", + "inline:{ aa@example.com=bb@example.com }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, + "aa@example.com", + {"bb@example.com"}, 1, + }, + { + "2 external -external-> external, extension, propagation", + "inline:{ aa@example.com=bb@example.com }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, + "aa+ext@example.com", + {"bb+ext@example.com"}, 1, + }, + { + "3 external -external-> external, extension, no propagation, no match", + "inline:{ aa@example.com=bb@example.com }", + DONT_PROPAGATE_UNMATCHED_EXTENSION, NO_RECIPIENT_DELIMITER, + MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, + "aa+ext@example.com", + {0}, 0, + }, + { + "4 external -external-> external, extension, full match", + "inline:{{cc+ext@example.com=dd@example.com,ee@example.com}}", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, + "cc+ext@example.com", + {"dd@example.com", "ee@example.com"}, 2, + }, + { + "5 external -external-> external, no extension, quoted", + "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, + "\"a a\"@example.com", + {"\"b b\"@example.com"}, 1, + }, + { + "6 external -external-> external, extension, propagation, quoted", + "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, + "\"a a+ext\"@example.com", + {"\"b b+ext\"@example.com"}, 1, + }, + { + "7 internal -external-> internal, no extension, propagation, embedded space", + "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MA_FORM_INTERNAL, MA_FORM_EXTERNAL, MA_FORM_INTERNAL, + "a a@example.com", + {"b b@example.com"}, 1, + }, + { + "8 internal -external-> internal, extension, propagation, embedded space", + "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MA_FORM_INTERNAL, MA_FORM_EXTERNAL, MA_FORM_INTERNAL, + "a a+ext@example.com", + {"b b+ext@example.com"}, 1, + }, + { + "9 internal -external-> internal, no extension, propagation, embedded space", + "inline:{ {a_a@example.com=\"b b\"@example.com} }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MA_FORM_INTERNAL, MA_FORM_EXTERNAL, MA_FORM_INTERNAL, + "a_a@example.com", + {"b b@example.com"}, 1, + }, + { + "10 internal -external-> internal, extension, propagation, embedded space", + "inline:{ {a_a@example.com=\"b b\"@example.com} }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MA_FORM_INTERNAL, MA_FORM_EXTERNAL, MA_FORM_INTERNAL, + "a_a+ext@example.com", + {"b b+ext@example.com"}, 1, + }, + { + "11 internal -external-> internal, no extension, @domain", + "inline:{ {@example.com=@example.net} }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MA_FORM_INTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, + "a@a@example.com", + {"\"a@a\"@example.net"}, 1, + }, + { + "12 external -external-> external, extension, propagation", + "inline:{ aa@example.com=bb@example.com }", + DO_PROPAGATE_UNMATCHED_EXTENSION, DOT_RECIPIENT_DELIMITER, + MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, + "aa.ext@example.com", + {"bb.ext@example.com"}, 1, + }, + 0, +}; + + /* + * All these tests must fail, so that we know that the tests work. + */ +static MAIL_ADDR_MAP_TEST fail_tests[] = { + { + "selftest 1 external -external-> external, no extension, quoted", + "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, + "\"a a\"@example.com", + {"\"bXb\"@example.com"}, 1, + }, + { + "selftest 2 external -external-> external, no extension, quoted", + "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, + "\"aXa\"@example.com", + {"\"b b\"@example.com"}, 1, + }, + { + "selftest 3 external -external-> external, no extension, quoted", + "inline:{ {\"a a\"@example.com=\"b b\"@example.com} }", + DO_PROPAGATE_UNMATCHED_EXTENSION, PLUS_RECIPIENT_DELIMITER, + MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, + "\"a a\"@example.com", + {0}, 0, + }, + 0, +}; + +/* canon_addr_external - surrogate to avoid trivial-rewrite dependency */ + +VSTRING *canon_addr_external(VSTRING *result, const char *addr) +{ + return (vstring_strcpy(result, addr)); +} + +static int compare(const char *testname, + const char **expect_argv, int expect_argc, + char **result_argv, int result_argc) +{ + int n; + int err = 0; + + if (expect_argc != 0 && result_argc != 0) { + for (n = 0; n < expect_argc && n < result_argc; n++) { + if (strcmp(expect_argv[n], result_argv[n]) != 0) { + msg_warn("fail test %s: expect[%d]='%s', result[%d]='%s'", + testname, n, expect_argv[n], n, result_argv[n]); + err = 1; + } + } + } + if (expect_argc != result_argc) { + msg_warn("fail test %s: expects %d results but there were %d", + testname, expect_argc, result_argc); + for (n = expect_argc; n < result_argc; n++) + msg_info("no expect to match result[%d]='%s'", n, result_argv[n]); + for (n = result_argc; n < expect_argc; n++) + msg_info("no result to match expect[%d]='%s'", n, expect_argv[n]); + err = 1; + } + return (err); +} + +static char *progname; + +static NORETURN usage(void) +{ + msg_fatal("usage: %s pass_test | fail_test", progname); +} + +int main(int argc, char **argv) +{ + MAIL_ADDR_MAP_TEST *test; + MAIL_ADDR_MAP_TEST *tests; + int errs = 0; + +#define UPDATE(dst, src) { if (dst) myfree(dst); dst = mystrdup(src); } + + /* + * Parse JCL. + */ + progname = argv[0]; + if (argc != 2) { + usage(); + } else if (strcmp(argv[1], "pass_tests") == 0) { + tests = pass_tests; + } else if (strcmp(argv[1], "fail_tests") == 0) { + tests = fail_tests; + } else { + usage(); + } + + /* + * Initialize. + */ + mail_params_init(); + + /* + * A read-eval-print loop, because specifying C strings with quotes and + * backslashes is painful. + */ + for (test = tests; test->testname; test++) { + ARGV *result; + int fail = 0; + + if (mail_addr_form_to_string(test->in_form) == 0) { + msg_warn("test %s: bad in_form field: %d", + test->testname, test->in_form); + fail = 1; + continue; + } + if (mail_addr_form_to_string(test->query_form) == 0) { + msg_warn("test %s: bad query_form field: %d", + test->testname, test->query_form); + fail = 1; + continue; + } + if (mail_addr_form_to_string(test->out_form) == 0) { + msg_warn("test %s: bad out_form field: %d", + test->testname, test->out_form); + fail = 1; + continue; + } + MAPS *maps = maps_create("test", test->database, DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); + + UPDATE(var_rcpt_delim, test->delimiter); + result = mail_addr_map_opt(maps, test->address, test->propagate, + test->in_form, test->query_form, test->out_form); + if (compare(test->testname, test->expect_argv, test->expect_argc, + result ? result->argv : 0, result ? result->argc : 0) != 0) { + msg_info("database = %s", test->database); + msg_info("propagate = %d", test->propagate); + msg_info("delimiter = '%s'", var_rcpt_delim); + msg_info("in_form = %s", mail_addr_form_to_string(test->in_form)); + msg_info("query_form = %s", mail_addr_form_to_string(test->query_form)); + msg_info("out_form = %s", mail_addr_form_to_string(test->out_form)); + msg_info("address = %s", test->address); + fail = 1; + } + maps_free(maps); + if (result) + argv_free(result); + + /* + * It is an error if a test does not pass or fail as intended. + */ + errs += (tests == pass_tests ? fail : !fail); + } + return (errs != 0); +} + +#endif |