diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 12:06:34 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 12:06:34 +0000 |
commit | 5e61585d76ae77fd5e9e96ebabb57afa4d74880d (patch) | |
tree | 2b467823aaeebc7ef8bc9e3cabe8074eaef1666d /src/global/mail_addr_find.c | |
parent | Initial commit. (diff) | |
download | postfix-5e61585d76ae77fd5e9e96ebabb57afa4d74880d.tar.xz postfix-5e61585d76ae77fd5e9e96ebabb57afa4d74880d.zip |
Adding upstream version 3.5.24.upstream/3.5.24upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/global/mail_addr_find.c')
-rw-r--r-- | src/global/mail_addr_find.c | 670 |
1 files changed, 670 insertions, 0 deletions
diff --git a/src/global/mail_addr_find.c b/src/global/mail_addr_find.c new file mode 100644 index 0000000..d8b94fb --- /dev/null +++ b/src/global/mail_addr_find.c @@ -0,0 +1,670 @@ +/*++ +/* NAME +/* mail_addr_find 3 +/* SUMMARY +/* generic address-based lookup +/* SYNOPSIS +/* #include <mail_addr_find.h> +/* +/* const char *mail_addr_find_int_to_ext(maps, address, extension) +/* MAPS *maps; +/* const char *address; +/* char **extension; +/* +/* const char *mail_addr_find_opt(maps, address, extension, in_form, +/* query_form, out_form, strategy) +/* MAPS *maps; +/* const char *address; +/* char **extension; +/* int in_form; +/* int in_form; +/* int out_form; +/* int strategy; +/* LEGACY SUPPORT +/* const char *mail_addr_find(maps, address, extension) +/* MAPS *maps; +/* const char *address; +/* char **extension; +/* +/* const char *mail_addr_find_to_internal(maps, address, extension) +/* MAPS *maps; +/* const char *address; +/* char **extension; +/* +/* const char *mail_addr_find_strategy(maps, address, extension) +/* MAPS *maps; +/* const char *address; +/* char **extension; +/* int strategy; +/* DESCRIPTION +/* mail_addr_find*() searches the specified maps for an entry with as +/* key the specified address, and derivations from that address. +/* It is up to the caller to specify its case sensitivity +/* preferences when it opens the maps. +/* The result is overwritten upon each call. +/* +/* In the lookup table, the key is expected to be in external +/* form (as produced with the postmap command) and the value is +/* expected to be in external (quoted) form if it is an email +/* address. Override these assumptions with the query_form +/* and out_form arguments. +/* +/* With mail_addr_find_int_to_ext(), the specified address is in +/* internal (unquoted) form, the query is made in external (quoted) +/* form, and the result is in the form found in the table (it is +/* not necessarily an email address). This version minimizes +/* internal/external (unquoted/quoted) conversions of the input, +/* query, extension, or result. +/* +/* mail_addr_find_opt() gives more control, at the cost of +/* additional conversions between internal and external forms. +/* In particular, output conversion to internal form assumes +/* that the lookup result is an email address. +/* +/* mail_addr_find() is used by legacy code that historically searched +/* with internal-form queries. The input is in internal form. It +/* searches with external-form queries first, and falls back to +/* internal-form queries if no result was found and the external +/* and internal forms differ. The result is external form (i.e. no +/* conversion). +/* +/* mail_addr_find_to_internal() is like mail_addr_find() but assumes +/* that the lookup result is one external-form email address, +/* and converts it to internal form. +/* +/* mail_addr_find_strategy() is like mail_addr_find() but overrides +/* the default search strategy for full and partial addresses. +/* +/* Arguments: +/* .IP maps +/* Dictionary search path (see maps(3)). +/* .IP address +/* The address to be looked up. +/* .IP extension +/* A null pointer, or the address of a pointer that is set to +/* the address of a dynamic memory copy of the address extension +/* that had to be chopped off in order to match the lookup tables. +/* The copy includes the recipient address delimiter. +/* The copy is in internal (unquoted) form. +/* The caller is expected to pass the copy to myfree(). +/* .IP query_form +/* The address form to use for database queries: one of +/* MA_FORM_INTERNAL (unquoted form), MA_FORM_EXTERNAL (quoted form), +/* MA_FORM_EXTERNAL_FIRST (external form, then internal form if the +/* external and internal forms differ), or MA_FORM_INTERNAL_FIRST +/* (internal form, then external form if the internal and external +/* forms differ). +/* .IP in_form .IP out_form +/* Input and output address forms, one of MA_FORM_INTERNAL (unquoted +/* form), or MA_FORM_EXTERNAL (quoted form). +/* .IP strategy +/* The lookup strategy for full and partial addresses, specified +/* as the binary OR of one or more of the following. These lookups +/* are implemented in the order as listed below. +/* .RS +/* .IP MA_FIND_DEFAULT +/* A convenience alias for (MA_FIND_FULL | +/* MA_FIND_NOEXT | MA_FIND_LOCALPART_IF_LOCAL | +/* MA_FIND_AT_DOMAIN). +/* .IP MA_FIND_FULL +/* Look up the full email address. +/* .IP MA_FIND_NOEXT +/* If no match was found, and the address has a localpart extension, +/* look up the address after removing the extension. +/* .IP MA_FIND_LOCALPART_IF_LOCAL +/* If no match was found, and the domain matches myorigin, +/* mydestination, or any inet_interfaces or proxy_interfaces IP +/* address, look up the localpart. If no match was found, and the +/* address has a localpart extension, repeat the same query after +/* removing the extension unless MA_FIND_NOEXT is specified. +/* .IP MA_FIND_LOCALPART_AT_IF_LOCAL +/* As above, but using the localpart@ instead. +/* .IP MA_FIND_AT_DOMAIN +/* If no match was found, look up the @domain without localpart. +/* .IP MA_FIND_DOMAIN +/* If no match was found, look up the domain without localpart. +/* .IP MA_FIND_PDMS +/* When used with MA_FIND_DOMAIN, the domain also matches subdomains. +/* .IP MA_FIND_PDDMDS +/* When used with MA_FIND_DOMAIN, dot-domain also matches +/* dot-subdomains. +/* .IP MA_FIND_LOCALPART_AT +/* If no match was found, look up the localpart@, regardless of +/* the domain content. +/* .RE +/* DIAGNOSTICS +/* The maps->error value is non-zero when the lookup failed due to +/* a non-permanent error. +/* SEE ALSO +/* maps(3), multi-dictionary search resolve_local(3), recognize +/* local system +/* 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 <name_mask.h> +#include <dict.h> +#include <stringops.h> +#include <mymalloc.h> +#include <vstring.h> + +/* Global library. */ + +#include <mail_params.h> +#include <strip_addr.h> +#include <mail_addr_find.h> +#include <resolve_local.h> +#include <quote_822_local.h> + +/* Application-specific. */ + +#define STR vstring_str + +#ifdef TEST + +static const NAME_MASK strategy_table[] = { + "full", MA_FIND_FULL, + "noext", MA_FIND_NOEXT, + "localpart_if_local", MA_FIND_LOCALPART_IF_LOCAL, + "localpart_at_if_local", MA_FIND_LOCALPART_AT_IF_LOCAL, + "at_domain", MA_FIND_AT_DOMAIN, + "domain", MA_FIND_DOMAIN, + "pdms", MA_FIND_PDMS, + "pddms", MA_FIND_PDDMDS, + "localpart_at", MA_FIND_LOCALPART_AT, + "default", MA_FIND_DEFAULT, + 0, -1, +}; + +/* strategy_from_string - symbolic strategy flags to internal form */ + +static int strategy_from_string(const char *strategy_string) +{ + return (name_mask_delim_opt("strategy_from_string", strategy_table, + strategy_string, "|", + NAME_MASK_WARN | NAME_MASK_ANY_CASE)); +} + +/* strategy_to_string - internal form to symbolic strategy flags */ + +static const char *strategy_to_string(VSTRING *res_buf, int strategy_mask) +{ + static VSTRING *my_buf; + + if (res_buf == 0 && (res_buf = my_buf) == 0) + res_buf = my_buf = vstring_alloc(20); + return (str_name_mask_opt(res_buf, "strategy_to_string", + strategy_table, strategy_mask, + NAME_MASK_WARN | NAME_MASK_PIPE)); +} + +#endif + + /* + * Specify what keys are partial or full, to avoid matching partial + * addresses with regular expressions. + */ +#define FULL 0 +#define PARTIAL DICT_FLAG_FIXED + +/* find_addr - helper to search maps with the right query form */ + +static const char *find_addr(MAPS *path, const char *address, int flags, + int with_domain, int query_form, VSTRING *ext_addr_buf) +{ + const char *result; + +#define SANS_DOMAIN 0 +#define WITH_DOMAIN 1 + + switch (query_form) { + + /* + * Query with external-form (quoted) address. The code looks a bit + * unusual to emphasize the symmetry with the other cases. + */ + case MA_FORM_EXTERNAL: + case MA_FORM_EXTERNAL_FIRST: + quote_822_local_flags(ext_addr_buf, address, + with_domain ? QUOTE_FLAG_DEFAULT : + QUOTE_FLAG_DEFAULT | QUOTE_FLAG_BARE_LOCALPART); + result = maps_find(path, STR(ext_addr_buf), flags); + if (result != 0 || path->error != 0 + || query_form != MA_FORM_EXTERNAL_FIRST + || strcmp(address, STR(ext_addr_buf)) == 0) + break; + result = maps_find(path, address, flags); + break; + + /* + * Query with internal-form (unquoted) address. The code looks a bit + * unusual to emphasize the symmetry with the other cases. + */ + case MA_FORM_INTERNAL: + case MA_FORM_INTERNAL_FIRST: + result = maps_find(path, address, flags); + if (result != 0 || path->error != 0 + || query_form != MA_FORM_INTERNAL_FIRST) + break; + quote_822_local_flags(ext_addr_buf, address, + with_domain ? QUOTE_FLAG_DEFAULT : + QUOTE_FLAG_DEFAULT | QUOTE_FLAG_BARE_LOCALPART); + if (strcmp(address, STR(ext_addr_buf)) == 0) + break; + result = maps_find(path, STR(ext_addr_buf), flags); + break; + + /* + * Can't happen. + */ + default: + msg_panic("mail_addr_find: bad query_form: %d", query_form); + } + return (result); +} + +/* find_local - search on localpart info */ + +static const char *find_local(MAPS *path, char *ratsign, int rats_offs, + char *int_full_key, char *int_bare_key, + int query_form, char **extp, char **saved_ext, + VSTRING *ext_addr_buf) +{ + const char *myname = "mail_addr_find"; + const char *result; + int with_domain; + int saved_ch; + + /* + * This code was ripped from the middle of a function so that it can be + * reused multiple times, that's why the interface makes little sense. + */ + with_domain = rats_offs ? WITH_DOMAIN : SANS_DOMAIN; + + saved_ch = *(unsigned char *) (ratsign + rats_offs); + *(ratsign + rats_offs) = 0; + result = find_addr(path, int_full_key, PARTIAL, with_domain, + query_form, ext_addr_buf); + *(ratsign + rats_offs) = saved_ch; + if (result == 0 && path->error == 0 && int_bare_key != 0) { + if ((ratsign = strrchr(int_bare_key, '@')) == 0) + msg_panic("%s: bare key botch", myname); + saved_ch = *(unsigned char *) (ratsign + rats_offs); + *(ratsign + rats_offs) = 0; + if ((result = find_addr(path, int_bare_key, PARTIAL, with_domain, + query_form, ext_addr_buf)) != 0 + && extp != 0) { + *extp = *saved_ext; + *saved_ext = 0; + } + *(ratsign + rats_offs) = saved_ch; + } + return result; +} + +/* mail_addr_find_opt - map a canonical address */ + +const char *mail_addr_find_opt(MAPS *path, const char *address, char **extp, + int in_form, int query_form, + int out_form, int strategy) +{ + const char *myname = "mail_addr_find"; + VSTRING *ext_addr_buf = 0; + VSTRING *int_addr_buf = 0; + const char *int_addr; + static VSTRING *int_result = 0; + const char *result; + char *ratsign = 0; + char *int_full_key; + char *int_bare_key; + char *saved_ext; + int rc = 0; + + /* + * Optionally convert the address from external form. + */ + if (in_form == MA_FORM_EXTERNAL) { + int_addr_buf = vstring_alloc(100); + unquote_822_local(int_addr_buf, address); + int_addr = STR(int_addr_buf); + } else { + int_addr = address; + } + if (query_form == MA_FORM_EXTERNAL_FIRST + || query_form == MA_FORM_EXTERNAL) + ext_addr_buf = vstring_alloc(100); + + /* + * Initialize. + */ + int_full_key = mystrdup(int_addr); + if (*var_rcpt_delim == 0 || (strategy & MA_FIND_NOEXT) == 0) { + int_bare_key = saved_ext = 0; + } else { + /* XXX This could be done after user+foo@domain fails. */ + int_bare_key = + strip_addr_internal(int_full_key, &saved_ext, var_rcpt_delim); + } + + /* + * Try user+foo@domain and user@domain. + */ + if ((strategy & MA_FIND_FULL) != 0) { + result = find_addr(path, int_full_key, FULL, WITH_DOMAIN, + query_form, ext_addr_buf); + } else { + result = 0; + path->error = 0; + } + + if (result == 0 && path->error == 0 && int_bare_key != 0 + && (result = find_addr(path, int_bare_key, PARTIAL, WITH_DOMAIN, + query_form, ext_addr_buf)) != 0 + && extp != 0) { + *extp = saved_ext; + saved_ext = 0; + } + + /* + * Try user+foo if the domain matches user+foo@$myorigin, + * user+foo@$mydestination or user+foo@[${proxy,inet}_interfaces]. Then + * try with +foo stripped off. + */ + if (result == 0 && path->error == 0 + && (ratsign = strrchr(int_full_key, '@')) != 0 + && (strategy & (MA_FIND_LOCALPART_IF_LOCAL + | MA_FIND_LOCALPART_AT_IF_LOCAL)) != 0) { + if (strcasecmp_utf8(ratsign + 1, var_myorigin) == 0 + || (rc = resolve_local(ratsign + 1)) > 0) { + if ((strategy & MA_FIND_LOCALPART_IF_LOCAL) != 0) + result = find_local(path, ratsign, 0, int_full_key, + int_bare_key, query_form, extp, &saved_ext, + ext_addr_buf); + if (result == 0 && path->error == 0 + && (strategy & MA_FIND_LOCALPART_AT_IF_LOCAL) != 0) + result = find_local(path, ratsign, 1, int_full_key, + int_bare_key, query_form, extp, &saved_ext, + ext_addr_buf); + } else if (rc < 0) + path->error = rc; + } + + /* + * Try @domain. + */ + if (result == 0 && path->error == 0 && ratsign != 0 + && (strategy & MA_FIND_AT_DOMAIN) != 0) + result = maps_find(path, ratsign, PARTIAL); + + /* + * Try domain (optionally, subdomains). + */ + if (result == 0 && path->error == 0 && ratsign != 0 + && (strategy & MA_FIND_DOMAIN) != 0) { + const char *name; + const char *next; + + if ((strategy & MA_FIND_PDMS) && (strategy & MA_FIND_PDDMDS)) + msg_warn("mail_addr_find_opt: do not specify both " + "MA_FIND_PDMS and MA_FIND_PDDMDS"); + for (name = ratsign + 1; *name != 0; name = next) { + if ((result = maps_find(path, name, PARTIAL)) != 0 + || path->error != 0 + || (strategy & (MA_FIND_PDMS | MA_FIND_PDDMDS)) == 0 + || (next = strchr(name + 1, '.')) == 0) + break; + if ((strategy & MA_FIND_PDDMDS) == 0) + next++; + } + } + + /* + * Try localpart@ even if the domain is not local. + */ + if ((strategy & MA_FIND_LOCALPART_AT) != 0 \ + &&result == 0 && path->error == 0) + result = find_local(path, ratsign, 1, int_full_key, + int_bare_key, query_form, extp, &saved_ext, + ext_addr_buf); + + /* + * Optionally convert the result to internal form. The lookup result is + * supposed to be one external-form email address. + */ + if (result != 0 && out_form == MA_FORM_INTERNAL) { + if (int_result == 0) + int_result = vstring_alloc(100); + unquote_822_local(int_result, result); + result = STR(int_result); + } + + /* + * Clean up. + */ + if (msg_verbose) + msg_info("%s: %s -> %s", myname, address, + result ? result : + path->error ? "(try again)" : + "(not found)"); + myfree(int_full_key); + if (int_bare_key) + myfree(int_bare_key); + if (saved_ext) + myfree(saved_ext); + if (int_addr_buf) + vstring_free(int_addr_buf); + if (ext_addr_buf) + vstring_free(ext_addr_buf); + return (result); +} + +#ifdef TEST + + /* + * Proof-of-concept test program. Read an address and expected results from + * stdin, and warn about any discrepancies. + */ +#include <ctype.h> +#include <stdlib.h> + +#include <vstream.h> +#include <vstring_vstream.h> +#include <mail_params.h> + +static NORETURN usage(const char *progname) +{ + msg_fatal("usage: %s [-v]", progname); +} + +int main(int argc, char **argv) +{ + VSTRING *buffer = vstring_alloc(100); + char *bp; + MAPS *path = 0; + const char *result; + char *extent; + char *cmd; + char *in_field; + char *query_field; + char *out_field; + char *strategy_field; + char *key_field; + char *expect_res; + char *expect_ext; + int in_form; + int query_form; + int out_form; + int strategy_flags; + int ch; + int errs = 0; + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "v")) > 0) { + switch (ch) { + case 'v': + msg_verbose++; + break; + default: + usage(argv[0]); + } + } + if (argc != optind) + usage(argv[0]); + + /* + * Initialize. + */ +#define UPDATE(var, val) do { myfree(var); var = mystrdup(val); } while (0) + + mail_params_init(); + + /* + * TODO: move these assignments into the read/eval loop. + */ + UPDATE(var_rcpt_delim, "+"); + UPDATE(var_mydomain, "localdomain"); + UPDATE(var_myorigin, "localdomain"); + UPDATE(var_mydest, "localhost.localdomain"); + while (vstring_fgets_nonl(buffer, VSTREAM_IN)) { + bp = STR(buffer); + if (msg_verbose) + msg_info("> %s", bp); + if ((cmd = mystrtok(&bp, CHARS_SPACE)) == 0 || *cmd == '#') + continue; + while (ISSPACE(*bp)) + bp++; + + /* + * Visible comment. + */ + if (strcmp(cmd, "echo") == 0) { + vstream_printf("%s\n", bp); + } + + /* + * Open maps. + */ + else if (strcmp(cmd, "maps") == 0) { + if (path) + maps_free(path); + path = maps_create(argv[0], bp, DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); + vstream_printf("%s\n", bp); + continue; + } + + /* + * Lookup and verify. + */ + else if (path && strcmp(cmd, "test") == 0) { + + /* + * Parse the input and expectations. + */ + /* internal, external. */ + if ((in_field = mystrtok(&bp, ":")) == 0) + msg_fatal("no input form"); + if ((in_form = mail_addr_form_from_string(in_field)) < 0) + msg_fatal("bad input form: '%s'", in_field); + if ((query_field = mystrtok(&bp, ":")) == 0) + msg_fatal("no query form"); + /* internal, external, external-first. */ + if ((query_form = mail_addr_form_from_string(query_field)) < 0) + msg_fatal("bad query form: '%s'", query_field); + if ((out_field = mystrtok(&bp, ":")) == 0) + msg_fatal("no output form"); + /* internal, external. */ + if ((out_form = mail_addr_form_from_string(out_field)) < 0) + msg_fatal("bad output form: '%s'", out_field); + if ((strategy_field = mystrtok(&bp, ":")) == 0) + msg_fatal("no strategy field"); + if ((strategy_flags = strategy_from_string(strategy_field)) < 0) + msg_fatal("bad strategy field: '%s'", strategy_field); + if ((key_field = mystrtok(&bp, ":")) == 0) + msg_fatal("no search key"); + expect_res = mystrtok(&bp, ":"); + expect_ext = mystrtok(&bp, ":"); + if (mystrtok(&bp, ":") != 0) + msg_fatal("garbage after extension field"); + + /* + * Lookups. + */ + extent = 0; + result = mail_addr_find_opt(path, key_field, &extent, + in_form, query_form, out_form, + strategy_flags); + vstream_printf("%s:%s -%s-> %s:%s (%s)\n", + in_field, key_field, query_field, out_field, result ? result : + path->error ? "(try again)" : + "(not found)", extent ? extent : "null extension"); + vstream_fflush(VSTREAM_OUT); + + /* + * Enforce expectations. + */ + if (expect_res && result) { + if (strcmp(expect_res, result) != 0) { + msg_warn("expect result '%s' but got '%s'", expect_res, result); + errs = 1; + if (expect_ext && extent) { + if (strcmp(expect_ext, extent) != 0) + msg_warn("expect extension '%s' but got '%s'", + expect_ext, extent); + errs = 1; + } else if (expect_ext && !extent) { + msg_warn("expect extension '%s' but got none", expect_ext); + errs = 1; + } else if (!expect_ext && extent) { + msg_warn("expect no extension but got '%s'", extent); + errs = 1; + } + } + } else if (expect_res && !result) { + msg_warn("expect result '%s' but got none", expect_res); + errs = 1; + } else if (!expect_res && result) { + msg_warn("expected no result but got '%s'", result); + errs = 1; + } + vstream_fflush(VSTREAM_OUT); + if (extent) + myfree(extent); + } + + /* + * Unknown request. + */ + else { + msg_warn("bad request: %s", cmd); + } + } + vstring_free(buffer); + + maps_free(path); + return (errs != 0); +} + +#endif |