summaryrefslogtreecommitdiffstats
path: root/src/global/mail_addr_find.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/global/mail_addr_find.c')
-rw-r--r--src/global/mail_addr_find.c670
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