/*++ /* NAME /* mail_addr_find 3 /* SUMMARY /* generic address-based lookup /* SYNOPSIS /* #include /* /* 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 #include /* Utility library. */ #include #include #include #include #include #include /* Global library. */ #include #include #include #include #include /* 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 #include #include #include #include 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