summaryrefslogtreecommitdiffstats
path: root/src/trivial-rewrite/resolve.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/trivial-rewrite/resolve.c')
-rw-r--r--src/trivial-rewrite/resolve.c828
1 files changed, 828 insertions, 0 deletions
diff --git a/src/trivial-rewrite/resolve.c b/src/trivial-rewrite/resolve.c
new file mode 100644
index 0000000..4e9ea2a
--- /dev/null
+++ b/src/trivial-rewrite/resolve.c
@@ -0,0 +1,828 @@
+/*++
+/* NAME
+/* resolve 3
+/* SUMMARY
+/* mail address resolver
+/* SYNOPSIS
+/* #include "trivial-rewrite.h"
+/*
+/* void resolve_init(void)
+/*
+/* int resolve_class(domain)
+/* const char *domain;
+/*
+/* void resolve_proto(context, stream)
+/* RES_CONTEXT *context;
+/* VSTREAM *stream;
+/* DESCRIPTION
+/* This module implements the trivial address resolving engine.
+/* It distinguishes between local and remote mail, and optionally
+/* consults one or more transport tables that map a destination
+/* to a transport, nexthop pair.
+/*
+/* resolve_init() initializes data structures that are private
+/* to this module. It should be called once before using the
+/* actual resolver routines.
+/*
+/* resolve_class() returns the address class for the specified
+/* domain, or -1 in case of error.
+/*
+/* resolve_proto() implements the client-server protocol:
+/* read one address in FQDN form, reply with a (transport,
+/* nexthop, internalized recipient) triple.
+/* STANDARDS
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* BUGS
+/* SEE ALSO
+/* 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 <stdlib.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <split_at.h>
+#include <valid_utf8_hostname.h>
+#include <stringops.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <resolve_local.h>
+#include <mail_conf.h>
+#include <quote_822_local.h>
+#include <tok822.h>
+#include <domain_list.h>
+#include <string_list.h>
+#include <match_parent_style.h>
+#include <maps.h>
+#include <mail_addr_find.h>
+#include <valid_mailhost_addr.h>
+
+/* Application-specific. */
+
+#include "trivial-rewrite.h"
+#include "transport.h"
+
+ /*
+ * The job of the address resolver is to map one recipient address to a
+ * triple of (channel, nexthop, recipient). The channel is the name of the
+ * delivery service specified in master.cf, the nexthop is (usually) a
+ * description of the next host to deliver to, and recipient is the final
+ * recipient address. The latter may differ from the input address as the
+ * result of stripping multiple layers of sender-specified routing.
+ *
+ * Addresses are resolved by their domain name. Known domain names are
+ * categorized into classes: local, virtual alias, virtual mailbox, relay,
+ * and everything else. Finding the address domain class is a matter of
+ * table lookups.
+ *
+ * Different address domain classes generally use different delivery channels,
+ * and may use class dependent ways to arrive at the corresponding nexthop
+ * information. With classes that do final delivery, the nexthop is
+ * typically the local machine hostname.
+ *
+ * The transport lookup table provides a means to override the domain class
+ * channel and/or nexhop information for specific recipients or for entire
+ * domain hierarchies.
+ *
+ * This works well in the general case. The only bug in this approach is that
+ * the structure of the nexthop information is transport dependent.
+ * Typically, the nexthop specifies a hostname, hostname + TCP Port, or the
+ * pathname of a UNIX-domain socket. However, with the error transport the
+ * nexthop field contains free text with the reason for non-delivery.
+ *
+ * Therefore, a transport map entry that overrides the channel but not the
+ * nexthop information (or vice versa) may produce surprising results. In
+ * particular, the free text nexthop information for the error transport is
+ * likely to confuse regular delivery agents; and conversely, a hostname or
+ * socket pathname is not an adequate text as reason for non-delivery.
+ *
+ * In the code below, rcpt_domain specifies the domain name that we will use
+ * when the transport table specifies a non-default channel but no nexthop
+ * information (we use a generic text when that non-default channel is the
+ * error transport).
+ */
+
+#define STR vstring_str
+#define LEN VSTRING_LEN
+
+ /*
+ * Some of the lists that define the address domain classes.
+ */
+static DOMAIN_LIST *relay_domains;
+static STRING_LIST *virt_alias_doms;
+static STRING_LIST *virt_mailbox_doms;
+
+static MAPS *relocated_maps;
+
+/* resolve_class - determine domain address class */
+
+int resolve_class(const char *domain)
+{
+ int ret;
+
+ /*
+ * Same order as in resolve_addr().
+ */
+ if ((ret = resolve_local(domain)) != 0)
+ return (ret > 0 ? RESOLVE_CLASS_LOCAL : -1);
+ if (virt_alias_doms) {
+ if (string_list_match(virt_alias_doms, domain))
+ return (RESOLVE_CLASS_ALIAS);
+ if (virt_alias_doms->error)
+ return (-1);
+ }
+ if (virt_mailbox_doms) {
+ if (string_list_match(virt_mailbox_doms, domain))
+ return (RESOLVE_CLASS_VIRTUAL);
+ if (virt_mailbox_doms->error)
+ return (-1);
+ }
+ if (relay_domains) {
+ if (string_list_match(relay_domains, domain))
+ return (RESOLVE_CLASS_RELAY);
+ if (relay_domains->error)
+ return (-1);
+ }
+ return (RESOLVE_CLASS_DEFAULT);
+}
+
+/* resolve_addr - resolve address according to rule set */
+
+static void resolve_addr(RES_CONTEXT *rp, char *sender, char *addr,
+ VSTRING *channel, VSTRING *nexthop,
+ VSTRING *nextrcpt, int *flags)
+{
+ const char *myname = "resolve_addr";
+ VSTRING *addr_buf = vstring_alloc(100);
+ TOK822 *tree = 0;
+ TOK822 *saved_domain = 0;
+ TOK822 *domain = 0;
+ char *destination;
+ const char *blame = 0;
+ const char *rcpt_domain;
+ ssize_t addr_len;
+ ssize_t loop_count;
+ ssize_t loop_max;
+ char *local;
+ char *oper;
+ char *junk;
+ const char *relay;
+ const char *xport;
+ const char *sender_key;
+ int rc;
+
+ *flags = 0;
+ vstring_strcpy(channel, "CHANNEL NOT UPDATED");
+ vstring_strcpy(nexthop, "NEXTHOP NOT UPDATED");
+ vstring_strcpy(nextrcpt, "NEXTRCPT NOT UPDATED");
+
+ /*
+ * The address is in internalized (unquoted) form.
+ *
+ * In an ideal world we would parse the externalized address form as given
+ * to us by the sender.
+ *
+ * However, in the real world we have to look for routing characters like
+ * %@! in the address local-part, even when that information is quoted
+ * due to the presence of special characters or whitespace. Although
+ * technically incorrect, this is needed to stop user@domain@domain relay
+ * attempts when forwarding mail to a Sendmail MX host.
+ *
+ * This suggests that we parse the address in internalized (unquoted) form.
+ * Unfortunately, if we do that, the unparser generates incorrect white
+ * space between adjacent non-operator tokens. Example: ``first last''
+ * needs white space, but ``stuff[stuff]'' does not. This is is not a
+ * problem when unparsing the result from parsing externalized forms,
+ * because the parser/unparser were designed for valid externalized forms
+ * where ``stuff[stuff]'' does not happen.
+ *
+ * As a workaround we start with the quoted form and then dequote the
+ * local-part only where needed. This will do the right thing in most
+ * (but not all) cases.
+ */
+ addr_len = strlen(addr);
+ quote_822_local(addr_buf, addr);
+ tree = tok822_scan_addr(vstring_str(addr_buf));
+
+ /*
+ * The optimizer will eliminate tests that always fail, and will replace
+ * multiple expansions of this macro by a GOTO to a single instance.
+ */
+#define FREE_MEMORY_AND_RETURN { \
+ if (saved_domain) \
+ tok822_free_tree(saved_domain); \
+ if(tree) \
+ tok822_free_tree(tree); \
+ if (addr_buf) \
+ vstring_free(addr_buf); \
+ return; \
+ }
+
+ /*
+ * Preliminary resolver: strip off all instances of the local domain.
+ * Terminate when no destination domain is left over, or when the
+ * destination domain is remote.
+ *
+ * XXX To whom it may concern. If you change the resolver loop below, or
+ * quote_822_local.c, or tok822_parse.c, be sure to re-run the tests
+ * under "make resolve_clnt_test" in the global directory.
+ */
+#define RESOLVE_LOCAL(domain) \
+ resolve_local(STR(tok822_internalize(addr_buf, domain, TOK822_STR_DEFL)))
+
+ for (loop_count = 0, loop_max = addr_len + 100; /* void */ ; loop_count++) {
+
+ /*
+ * XXX Should never happen, but if this happens with some
+ * pathological address, then that is not sufficient reason to
+ * disrupt the operation of an MTA.
+ */
+ if (loop_count > loop_max) {
+ msg_warn("resolve_addr: <%s>: giving up after %ld iterations",
+ addr, (long) loop_count);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ break;
+ }
+
+ /*
+ * Strip trailing dot at end of domain, but not dot-dot or at-dot.
+ * This merely makes diagnostics more accurate by leaving bogus
+ * addresses alone.
+ */
+ if (tree->tail
+ && tree->tail->type == '.'
+ && tok822_rfind_type(tree->tail, '@') != 0
+ && tree->tail->prev->type != '.'
+ && tree->tail->prev->type != '@')
+ tok822_free_tree(tok822_sub_keep_before(tree, tree->tail));
+
+ /*
+ * Strip trailing @.
+ */
+ if (var_resolve_nulldom
+ && tree->tail
+ && tree->tail->type == '@')
+ tok822_free_tree(tok822_sub_keep_before(tree, tree->tail));
+
+ /*
+ * Strip (and save) @domain if local.
+ *
+ * Grr. resolve_local() table lookups may fail. It may be OK for local
+ * file lookup code to abort upon failure, but with network-based
+ * tables it is preferable to return an error indication to the
+ * requestor.
+ */
+ if ((domain = tok822_rfind_type(tree->tail, '@')) != 0) {
+ if (domain->next && (rc = RESOLVE_LOCAL(domain->next)) <= 0) {
+ if (rc < 0) {
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ }
+ break;
+ }
+ tok822_sub_keep_before(tree, domain);
+ if (saved_domain)
+ tok822_free_tree(saved_domain);
+ saved_domain = domain;
+ domain = 0; /* safety for future change */
+ }
+
+ /*
+ * After stripping the local domain, if any, replace foo%bar by
+ * foo@bar, site!user by user@site, rewrite to canonical form, and
+ * retry.
+ */
+ if (tok822_rfind_type(tree->tail, '@')
+ || (var_swap_bangpath && tok822_rfind_type(tree->tail, '!'))
+ || (var_percent_hack && tok822_rfind_type(tree->tail, '%'))) {
+ rewrite_tree(&local_context, tree);
+ continue;
+ }
+
+ /*
+ * If the local-part is a quoted string, crack it open when we're
+ * permitted to do so and look for routing operators. This is
+ * technically incorrect, but is needed to stop relaying problems.
+ *
+ * XXX Do another feeble attempt to keep local-part info quoted.
+ */
+ if (var_resolve_dequoted
+ && tree->head && tree->head == tree->tail
+ && tree->head->type == TOK822_QSTRING
+ && ((oper = strrchr(local = STR(tree->head->vstr), '@')) != 0
+ || (var_percent_hack && (oper = strrchr(local, '%')) != 0)
+ || (var_swap_bangpath && (oper = strrchr(local, '!')) != 0))) {
+ if (*oper == '%')
+ *oper = '@';
+ tok822_internalize(addr_buf, tree->head, TOK822_STR_DEFL);
+ if (*oper == '@') {
+ junk = mystrdup(STR(addr_buf));
+ quote_822_local(addr_buf, junk);
+ myfree(junk);
+ }
+ tok822_free(tree->head);
+ tree->head = tok822_scan(STR(addr_buf), &tree->tail);
+ rewrite_tree(&local_context, tree);
+ continue;
+ }
+
+ /*
+ * An empty local-part or an empty quoted string local-part becomes
+ * the local MAILER-DAEMON, for consistency with our own From:
+ * message headers.
+ */
+ if (tree->head && tree->head == tree->tail
+ && tree->head->type == TOK822_QSTRING
+ && VSTRING_LEN(tree->head->vstr) == 0) {
+ tok822_free(tree->head);
+ tree->head = 0;
+ }
+ /* XXX Re-resolve the surrogate, in case already in user@domain form. */
+ if (tree->head == 0) {
+ tree->head = tok822_scan(var_empty_addr, &tree->tail);
+ continue;
+ }
+ /* XXX Re-resolve with @$myhostname for backwards compatibility. */
+ if (domain == 0 && saved_domain == 0) {
+ tok822_sub_append(tree, tok822_alloc('@', (char *) 0));
+ tok822_sub_append(tree, tok822_scan(var_myhostname, (TOK822 **) 0));
+ continue;
+ }
+
+ /*
+ * We're done. There are no domains left to strip off the address,
+ * and all null local-part information is sanitized.
+ */
+ domain = 0;
+ break;
+ }
+
+ vstring_free(addr_buf);
+ addr_buf = 0;
+
+ /*
+ * Make sure the resolved envelope recipient has the user@domain form. If
+ * no domain was specified in the address, assume the local machine. See
+ * above for what happens with an empty address.
+ */
+ if (domain == 0) {
+ if (saved_domain) {
+ tok822_sub_append(tree, saved_domain);
+ saved_domain = 0;
+ } else {
+ tok822_sub_append(tree, tok822_alloc('@', (char *) 0));
+ tok822_sub_append(tree, tok822_scan(var_myhostname, (TOK822 **) 0));
+ }
+ }
+
+ /*
+ * Transform the recipient address back to internal form.
+ *
+ * XXX This may produce incorrect results if we cracked open a quoted
+ * local-part with routing operators; see discussion above at the top of
+ * the big loop.
+ *
+ * XXX We explicitly disallow domain names in bare network address form. A
+ * network address destination should be formatted according to RFC 2821:
+ * it should be enclosed in [], and an IPv6 address should have an IPv6:
+ * prefix.
+ */
+ tok822_internalize(nextrcpt, tree, TOK822_STR_DEFL);
+ rcpt_domain = strrchr(STR(nextrcpt), '@') + 1;
+ if (rcpt_domain == (char *) 1)
+ msg_panic("no @ in address: \"%s\"", STR(nextrcpt));
+ if (*rcpt_domain == '[') {
+ if (!valid_mailhost_literal(rcpt_domain, DONT_GRIPE))
+ *flags |= RESOLVE_FLAG_ERROR;
+ } else if (var_smtputf8_enable
+ && valid_utf8_string(STR(nextrcpt), LEN(nextrcpt)) == 0) {
+ *flags |= RESOLVE_FLAG_ERROR;
+ } else if (!valid_utf8_hostname(var_smtputf8_enable, rcpt_domain,
+ DONT_GRIPE)) {
+ if (var_resolve_num_dom && valid_hostaddr(rcpt_domain, DONT_GRIPE)) {
+ vstring_insert(nextrcpt, rcpt_domain - STR(nextrcpt), "[", 1);
+ vstring_strcat(nextrcpt, "]");
+ rcpt_domain = strrchr(STR(nextrcpt), '@') + 1;
+ if ((rc = resolve_local(rcpt_domain)) > 0) /* XXX */
+ domain = 0;
+ else if (rc < 0) {
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ }
+ } else {
+ *flags |= RESOLVE_FLAG_ERROR;
+ }
+ }
+ tok822_free_tree(tree);
+ tree = 0;
+
+ /*
+ * XXX Short-cut invalid address forms.
+ */
+ if (*flags & RESOLVE_FLAG_ERROR) {
+ *flags |= RESOLVE_CLASS_DEFAULT;
+ FREE_MEMORY_AND_RETURN;
+ }
+
+ /*
+ * Recognize routing operators in the local-part, even when we do not
+ * recognize ! or % as valid routing operators locally. This is needed to
+ * prevent backup MX hosts from relaying third-party destinations through
+ * primary MX hosts, otherwise the backup host could end up on black
+ * lists. Ignore local swap_bangpath and percent_hack settings because we
+ * can't know how the next MX host is set up.
+ */
+ if (strcmp(STR(nextrcpt) + strcspn(STR(nextrcpt), "@!%") + 1, rcpt_domain))
+ *flags |= RESOLVE_FLAG_ROUTED;
+
+ /*
+ * With local, virtual, relay, or other non-local destinations, give the
+ * highest precedence to transport associated nexthop information.
+ *
+ * Otherwise, with relay or other non-local destinations, the relayhost
+ * setting overrides the recipient domain name, and the sender-dependent
+ * relayhost overrides both.
+ *
+ * XXX Nag if the recipient domain is listed in multiple domain lists. The
+ * result is implementation defined, and may break when internals change.
+ *
+ * For now, we distinguish only a fixed number of address classes.
+ * Eventually this may become extensible, so that new classes can be
+ * configured with their own domain list, delivery transport, and
+ * recipient table.
+ */
+#define STREQ(x,y) (strcmp((x), (y)) == 0)
+
+ if (domain != 0) {
+
+ /*
+ * Virtual alias domain.
+ */
+ if (virt_alias_doms
+ && string_list_match(virt_alias_doms, rcpt_domain)) {
+ if (var_helpful_warnings) {
+ if (virt_mailbox_doms
+ && string_list_match(virt_mailbox_doms, rcpt_domain))
+ msg_warn("do not list domain %s in BOTH %s and %s",
+ rcpt_domain, VAR_VIRT_ALIAS_DOMS,
+ VAR_VIRT_MAILBOX_DOMS);
+ if (relay_domains
+ && domain_list_match(relay_domains, rcpt_domain))
+ msg_warn("do not list domain %s in BOTH %s and %s",
+ rcpt_domain, VAR_VIRT_ALIAS_DOMS,
+ VAR_RELAY_DOMAINS);
+#if 0
+ if (strcasecmp_utf8(rcpt_domain, var_myorigin) == 0)
+ msg_warn("do not list $%s (%s) in %s",
+ VAR_MYORIGIN, var_myorigin, VAR_VIRT_ALIAS_DOMS);
+#endif
+ }
+ vstring_strcpy(channel, MAIL_SERVICE_ERROR);
+ vstring_sprintf(nexthop, "5.1.1 User unknown%s",
+ var_show_unk_rcpt_table ?
+ " in virtual alias table" : "");
+ *flags |= RESOLVE_CLASS_ALIAS;
+ } else if (virt_alias_doms && virt_alias_doms->error != 0) {
+ msg_warn("%s lookup failure", VAR_VIRT_ALIAS_DOMS);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ }
+
+ /*
+ * Virtual mailbox domain.
+ */
+ else if (virt_mailbox_doms
+ && string_list_match(virt_mailbox_doms, rcpt_domain)) {
+ if (var_helpful_warnings) {
+ if (relay_domains
+ && domain_list_match(relay_domains, rcpt_domain))
+ msg_warn("do not list domain %s in BOTH %s and %s",
+ rcpt_domain, VAR_VIRT_MAILBOX_DOMS,
+ VAR_RELAY_DOMAINS);
+ }
+ vstring_strcpy(channel, RES_PARAM_VALUE(rp->virt_transport));
+ vstring_strcpy(nexthop, rcpt_domain);
+ blame = rp->virt_transport_name;
+ *flags |= RESOLVE_CLASS_VIRTUAL;
+ } else if (virt_mailbox_doms && virt_mailbox_doms->error != 0) {
+ msg_warn("%s lookup failure", VAR_VIRT_MAILBOX_DOMS);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ } else {
+
+ /*
+ * Off-host relay destination.
+ */
+ if (relay_domains
+ && domain_list_match(relay_domains, rcpt_domain)) {
+ vstring_strcpy(channel, RES_PARAM_VALUE(rp->relay_transport));
+ blame = rp->relay_transport_name;
+ *flags |= RESOLVE_CLASS_RELAY;
+ } else if (relay_domains && relay_domains->error != 0) {
+ msg_warn("%s lookup failure", VAR_RELAY_DOMAINS);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ }
+
+ /*
+ * Other off-host destination.
+ */
+ else {
+ if (rp->snd_def_xp_info
+ && (xport = mail_addr_find(rp->snd_def_xp_info,
+ sender_key = (*sender ? sender :
+ var_null_def_xport_maps_key),
+ (char **) 0)) != 0) {
+ if (*xport == 0) {
+ msg_warn("%s: ignoring null lookup result for %s",
+ rp->snd_def_xp_maps_name, sender_key);
+ xport = "DUNNO";
+ }
+ vstring_strcpy(channel, strcasecmp(xport, "DUNNO") == 0 ?
+ RES_PARAM_VALUE(rp->def_transport) : xport);
+ blame = rp->snd_def_xp_maps_name;
+ } else if (rp->snd_def_xp_info
+ && rp->snd_def_xp_info->error != 0) {
+ msg_warn("%s lookup failure", rp->snd_def_xp_maps_name);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ } else {
+ vstring_strcpy(channel, RES_PARAM_VALUE(rp->def_transport));
+ blame = rp->def_transport_name;
+ }
+ *flags |= RESOLVE_CLASS_DEFAULT;
+ }
+
+ /*
+ * With off-host delivery, sender-dependent or global relayhost
+ * override the recipient domain.
+ */
+ if (rp->snd_relay_info
+ && (relay = mail_addr_find(rp->snd_relay_info,
+ sender_key = (*sender ? sender :
+ var_null_relay_maps_key),
+ (char **) 0)) != 0) {
+ if (*relay == 0) {
+ msg_warn("%s: ignoring null lookup result for %s",
+ rp->snd_relay_maps_name, sender_key);
+ relay = 0;
+ } else if (strcasecmp_utf8(relay, "DUNNO") == 0)
+ relay = 0;
+ } else if (rp->snd_relay_info
+ && rp->snd_relay_info->error != 0) {
+ msg_warn("%s lookup failure", rp->snd_relay_maps_name);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ } else {
+ relay = 0;
+ }
+ /* Enforce all the relayhost precedences in one place. */
+ if (relay != 0) {
+ vstring_strcpy(nexthop, relay);
+ } else if (*RES_PARAM_VALUE(rp->relayhost))
+ vstring_strcpy(nexthop, RES_PARAM_VALUE(rp->relayhost));
+ else
+ vstring_strcpy(nexthop, rcpt_domain);
+ }
+ }
+
+ /*
+ * Local delivery.
+ *
+ * XXX Nag if the domain is listed in multiple domain lists. The effect is
+ * implementation defined, and may break when internals change.
+ */
+ else {
+ if (var_helpful_warnings) {
+ if (virt_alias_doms
+ && string_list_match(virt_alias_doms, rcpt_domain))
+ msg_warn("do not list domain %s in BOTH %s and %s",
+ rcpt_domain, VAR_MYDEST, VAR_VIRT_ALIAS_DOMS);
+ if (virt_mailbox_doms
+ && string_list_match(virt_mailbox_doms, rcpt_domain))
+ msg_warn("do not list domain %s in BOTH %s and %s",
+ rcpt_domain, VAR_MYDEST, VAR_VIRT_MAILBOX_DOMS);
+ }
+ vstring_strcpy(channel, RES_PARAM_VALUE(rp->local_transport));
+ vstring_strcpy(nexthop, rcpt_domain);
+ blame = rp->local_transport_name;
+ *flags |= RESOLVE_CLASS_LOCAL;
+ }
+
+ /*
+ * An explicit main.cf transport:nexthop setting overrides the nexthop.
+ *
+ * XXX We depend on this mechanism to enforce per-recipient concurrencies
+ * for local recipients. With "local_transport = local:$myhostname" we
+ * force mail for any domain in $mydestination/${proxy,inet}_interfaces
+ * to share the same queue.
+ */
+ if ((destination = split_at(STR(channel), ':')) != 0 && *destination)
+ vstring_strcpy(nexthop, destination);
+
+ /*
+ * Sanity checks.
+ */
+ if (*STR(channel) == 0) {
+ if (blame == 0)
+ msg_panic("%s: null blame", myname);
+ msg_warn("file %s/%s: parameter %s: null transport is not allowed",
+ var_config_dir, MAIN_CONF_FILE, blame);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ }
+ if (*STR(nexthop) == 0)
+ msg_panic("%s: null nexthop", myname);
+
+ /*
+ * The transport map can selectively override any transport and/or
+ * nexthop host info that is set up above. Unfortunately, the syntax for
+ * nexthop information is transport specific. We therefore need sane and
+ * intuitive semantics for transport map entries that specify a channel
+ * but no nexthop.
+ *
+ * With non-error transports, the initial nexthop information is the
+ * recipient domain. However, specific main.cf transport definitions may
+ * specify a transport-specific destination, such as a host + TCP socket,
+ * or the pathname of a UNIX-domain socket. With less precedence than
+ * main.cf transport definitions, a main.cf relayhost definition may also
+ * override nexthop information for off-host deliveries.
+ *
+ * With the error transport, the nexthop information is free text that
+ * specifies the reason for non-delivery.
+ *
+ * Because nexthop syntax is transport specific we reset the nexthop
+ * information to the recipient domain when the transport table specifies
+ * a transport without also specifying the nexthop information.
+ *
+ * Subtle note: reset nexthop even when the transport table does not change
+ * the transport. Otherwise it is hard to get rid of main.cf specified
+ * nexthop information.
+ *
+ * XXX Don't override the virtual alias class (error:User unknown) result.
+ */
+ if (rp->transport_info && !(*flags & RESOLVE_CLASS_ALIAS)) {
+ if (transport_lookup(rp->transport_info, STR(nextrcpt),
+ rcpt_domain, channel, nexthop) == 0
+ && rp->transport_info->transport_path->error != 0) {
+ msg_warn("%s lookup failure", rp->transport_maps_name);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ }
+ }
+
+ /*
+ * Bounce recipients that have moved, regardless of domain address class.
+ * We do this last, in anticipation of transport maps that can override
+ * the recipient address.
+ *
+ * The downside of not doing this in delivery agents is that this table has
+ * no effect on local alias expansion results. Such mail will have to
+ * make almost an entire iteration through the mail system.
+ */
+#define IGNORE_ADDR_EXTENSION ((char **) 0)
+
+ if (relocated_maps != 0) {
+ const char *newloc;
+
+ if ((newloc = mail_addr_find(relocated_maps, STR(nextrcpt),
+ IGNORE_ADDR_EXTENSION)) != 0) {
+ vstring_strcpy(channel, MAIL_SERVICE_ERROR);
+ /* 5.1.6 is the closest match, but not perfect. */
+ vstring_sprintf(nexthop, "5.1.6 User has moved to %s", newloc);
+ } else if (relocated_maps->error != 0) {
+ msg_warn("%s lookup failure", VAR_RELOCATED_MAPS);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ }
+ }
+
+ /*
+ * Bounce recipient addresses that start with `-'. External commands may
+ * misinterpret such addresses as command-line options.
+ *
+ * In theory I could say people should always carefully set up their
+ * master.cf pipe mailer entries with `--' before the first non-option
+ * argument, but mistakes will happen regardless.
+ *
+ * Therefore the protection is put in place here, where it cannot be
+ * bypassed.
+ */
+ if (var_allow_min_user == 0 && STR(nextrcpt)[0] == '-') {
+ *flags |= RESOLVE_FLAG_ERROR;
+ FREE_MEMORY_AND_RETURN;
+ }
+
+ /*
+ * Clean up.
+ */
+ FREE_MEMORY_AND_RETURN;
+}
+
+/* Static, so they can be used by the network protocol interface only. */
+
+static VSTRING *channel;
+static VSTRING *nexthop;
+static VSTRING *nextrcpt;
+static VSTRING *query;
+static VSTRING *sender;
+
+/* resolve_proto - read request and send reply */
+
+int resolve_proto(RES_CONTEXT *context, VSTREAM *stream)
+{
+ int flags;
+
+ if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_SENDER, sender),
+ RECV_ATTR_STR(MAIL_ATTR_ADDR, query),
+ ATTR_TYPE_END) != 2)
+ return (-1);
+
+ resolve_addr(context, STR(sender), STR(query),
+ channel, nexthop, nextrcpt, &flags);
+
+ if (msg_verbose)
+ msg_info("`%s' -> `%s' -> (`%s' `%s' `%s' `%d')",
+ STR(sender), STR(query), STR(channel),
+ STR(nexthop), STR(nextrcpt), flags);
+
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, server_flags),
+ SEND_ATTR_STR(MAIL_ATTR_TRANSPORT, STR(channel)),
+ SEND_ATTR_STR(MAIL_ATTR_NEXTHOP, STR(nexthop)),
+ SEND_ATTR_STR(MAIL_ATTR_RECIP, STR(nextrcpt)),
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags),
+ ATTR_TYPE_END);
+
+ if (vstream_fflush(stream) != 0) {
+ msg_warn("write resolver reply: %m");
+ return (-1);
+ }
+ return (0);
+}
+
+/* resolve_init - module initializations */
+
+void resolve_init(void)
+{
+ sender = vstring_alloc(100);
+ query = vstring_alloc(100);
+ channel = vstring_alloc(100);
+ nexthop = vstring_alloc(100);
+ nextrcpt = vstring_alloc(100);
+
+ if (*var_virt_alias_doms)
+ virt_alias_doms =
+ string_list_init(VAR_VIRT_ALIAS_DOMS, MATCH_FLAG_RETURN,
+ var_virt_alias_doms);
+
+ if (*var_virt_mailbox_doms)
+ virt_mailbox_doms =
+ string_list_init(VAR_VIRT_MAILBOX_DOMS, MATCH_FLAG_RETURN,
+ var_virt_mailbox_doms);
+
+ if (*var_relay_domains)
+ relay_domains =
+ domain_list_init(VAR_RELAY_DOMAINS, MATCH_FLAG_RETURN
+ | match_parent_style(VAR_RELAY_DOMAINS),
+ var_relay_domains);
+
+ if (*var_relocated_maps)
+ relocated_maps =
+ maps_create(VAR_RELOCATED_MAPS, var_relocated_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+}