1/*	$NetBSD$	*/
2
3/*++
4/* NAME
5/*	resolve 3
6/* SUMMARY
7/*	mail address resolver
8/* SYNOPSIS
9/*	#include "trivial-rewrite.h"
10/*
11/*	void	resolve_init(void)
12/*
13/*	void	resolve_proto(context, stream)
14/*	RES_CONTEXT *context;
15/*	VSTREAM	*stream;
16/* DESCRIPTION
17/*	This module implements the trivial address resolving engine.
18/*	It distinguishes between local and remote mail, and optionally
19/*	consults one or more transport tables that map a destination
20/*	to a transport, nexthop pair.
21/*
22/*	resolve_init() initializes data structures that are private
23/*	to this module. It should be called once before using the
24/*	actual resolver routines.
25/*
26/*	resolve_proto() implements the client-server protocol:
27/*	read one address in FQDN form, reply with a (transport,
28/*	nexthop, internalized recipient) triple.
29/* STANDARDS
30/* DIAGNOSTICS
31/*	Problems and transactions are logged to the syslog daemon.
32/* BUGS
33/* SEE ALSO
34/* LICENSE
35/* .ad
36/* .fi
37/*	The Secure Mailer license must be distributed with this software.
38/* AUTHOR(S)
39/*	Wietse Venema
40/*	IBM T.J. Watson Research
41/*	P.O. Box 704
42/*	Yorktown Heights, NY 10598, USA
43/*--*/
44
45/* System library. */
46
47#include <sys_defs.h>
48#include <stdlib.h>
49#include <string.h>
50
51#ifdef STRCASECMP_IN_STRINGS_H
52#include <strings.h>
53#endif
54
55/* Utility library. */
56
57#include <msg.h>
58#include <vstring.h>
59#include <vstream.h>
60#include <vstring_vstream.h>
61#include <split_at.h>
62#include <valid_hostname.h>
63#include <stringops.h>
64#include <mymalloc.h>
65
66/* Global library. */
67
68#include <mail_params.h>
69#include <mail_proto.h>
70#include <resolve_local.h>
71#include <mail_conf.h>
72#include <quote_822_local.h>
73#include <tok822.h>
74#include <domain_list.h>
75#include <string_list.h>
76#include <match_parent_style.h>
77#include <maps.h>
78#include <mail_addr_find.h>
79#include <valid_mailhost_addr.h>
80
81/* Application-specific. */
82
83#include "trivial-rewrite.h"
84#include "transport.h"
85
86 /*
87  * The job of the address resolver is to map one recipient address to a
88  * triple of (channel, nexthop, recipient). The channel is the name of the
89  * delivery service specified in master.cf, the nexthop is (usually) a
90  * description of the next host to deliver to, and recipient is the final
91  * recipient address. The latter may differ from the input address as the
92  * result of stripping multiple layers of sender-specified routing.
93  *
94  * Addresses are resolved by their domain name. Known domain names are
95  * categorized into classes: local, virtual alias, virtual mailbox, relay,
96  * and everything else. Finding the address domain class is a matter of
97  * table lookups.
98  *
99  * Different address domain classes generally use different delivery channels,
100  * and may use class dependent ways to arrive at the corresponding nexthop
101  * information. With classes that do final delivery, the nexthop is
102  * typically the local machine hostname.
103  *
104  * The transport lookup table provides a means to override the domain class
105  * channel and/or nexhop information for specific recipients or for entire
106  * domain hierarchies.
107  *
108  * This works well in the general case. The only bug in this approach is that
109  * the structure of the nexthop information is transport dependent.
110  * Typically, the nexthop specifies a hostname, hostname + TCP Port, or the
111  * pathname of a UNIX-domain socket. However, with the error transport the
112  * nexthop field contains free text with the reason for non-delivery.
113  *
114  * Therefore, a transport map entry that overrides the channel but not the
115  * nexthop information (or vice versa) may produce surprising results. In
116  * particular, the free text nexthop information for the error transport is
117  * likely to confuse regular delivery agents; and conversely, a hostname or
118  * socket pathname is not an adequate text as reason for non-delivery.
119  *
120  * In the code below, rcpt_domain specifies the domain name that we will use
121  * when the transport table specifies a non-default channel but no nexthop
122  * information (we use a generic text when that non-default channel is the
123  * error transport).
124  */
125
126#define STR	vstring_str
127
128 /*
129  * Some of the lists that define the address domain classes.
130  */
131static DOMAIN_LIST *relay_domains;
132static STRING_LIST *virt_alias_doms;
133static STRING_LIST *virt_mailbox_doms;
134
135static MAPS *relocated_maps;
136
137/* resolve_addr - resolve address according to rule set */
138
139static void resolve_addr(RES_CONTEXT *rp, char *sender, char *addr,
140			         VSTRING *channel, VSTRING *nexthop,
141			         VSTRING *nextrcpt, int *flags)
142{
143    const char *myname = "resolve_addr";
144    VSTRING *addr_buf = vstring_alloc(100);
145    TOK822 *tree = 0;
146    TOK822 *saved_domain = 0;
147    TOK822 *domain = 0;
148    char   *destination;
149    const char *blame = 0;
150    const char *rcpt_domain;
151    ssize_t addr_len;
152    ssize_t loop_count;
153    ssize_t loop_max;
154    char   *local;
155    char   *oper;
156    char   *junk;
157    const char *relay;
158    const char *xport;
159    const char *sender_key;
160
161    *flags = 0;
162    vstring_strcpy(channel, "CHANNEL NOT UPDATED");
163    vstring_strcpy(nexthop, "NEXTHOP NOT UPDATED");
164    vstring_strcpy(nextrcpt, "NEXTRCPT NOT UPDATED");
165
166    /*
167     * The address is in internalized (unquoted) form.
168     *
169     * In an ideal world we would parse the externalized address form as given
170     * to us by the sender.
171     *
172     * However, in the real world we have to look for routing characters like
173     * %@! in the address local-part, even when that information is quoted
174     * due to the presence of special characters or whitespace. Although
175     * technically incorrect, this is needed to stop user@domain@domain relay
176     * attempts when forwarding mail to a Sendmail MX host.
177     *
178     * This suggests that we parse the address in internalized (unquoted) form.
179     * Unfortunately, if we do that, the unparser generates incorrect white
180     * space between adjacent non-operator tokens. Example: ``first last''
181     * needs white space, but ``stuff[stuff]'' does not. This is is not a
182     * problem when unparsing the result from parsing externalized forms,
183     * because the parser/unparser were designed for valid externalized forms
184     * where ``stuff[stuff]'' does not happen.
185     *
186     * As a workaround we start with the quoted form and then dequote the
187     * local-part only where needed. This will do the right thing in most
188     * (but not all) cases.
189     */
190    addr_len = strlen(addr);
191    quote_822_local(addr_buf, addr);
192    tree = tok822_scan_addr(vstring_str(addr_buf));
193
194    /*
195     * The optimizer will eliminate tests that always fail, and will replace
196     * multiple expansions of this macro by a GOTO to a single instance.
197     */
198#define FREE_MEMORY_AND_RETURN { \
199	if (saved_domain) \
200	    tok822_free_tree(saved_domain); \
201	if(tree) \
202	    tok822_free_tree(tree); \
203	if (addr_buf) \
204	    vstring_free(addr_buf); \
205	return; \
206    }
207
208    /*
209     * Preliminary resolver: strip off all instances of the local domain.
210     * Terminate when no destination domain is left over, or when the
211     * destination domain is remote.
212     *
213     * XXX To whom it may concern. If you change the resolver loop below, or
214     * quote_822_local.c, or tok822_parse.c, be sure to re-run the tests
215     * under "make resolve_clnt_test" in the global directory.
216     */
217#define RESOLVE_LOCAL(domain) \
218    resolve_local(STR(tok822_internalize(addr_buf, domain, TOK822_STR_DEFL)))
219
220    dict_errno = 0;
221
222    for (loop_count = 0, loop_max = addr_len + 100; /* void */ ; loop_count++) {
223
224	/*
225	 * Grr. resolve_local() table lookups may fail. It may be OK for
226	 * local file lookup code to abort upon failure, but with
227	 * network-based tables it is preferable to return an error
228	 * indication to the requestor.
229	 */
230	if (dict_errno) {
231	    *flags |= RESOLVE_FLAG_FAIL;
232	    FREE_MEMORY_AND_RETURN;
233	}
234
235	/*
236	 * XXX Should never happen, but if this happens with some
237	 * pathological address, then that is not sufficient reason to
238	 * disrupt the operation of an MTA.
239	 */
240	if (loop_count > loop_max) {
241	    msg_warn("resolve_addr: <%s>: giving up after %ld iterations",
242		     addr, (long) loop_count);
243	    break;
244	}
245
246	/*
247	 * Strip trailing dot at end of domain, but not dot-dot or at-dot.
248	 * This merely makes diagnostics more accurate by leaving bogus
249	 * addresses alone.
250	 */
251	if (tree->tail
252	    && tree->tail->type == '.'
253	    && tok822_rfind_type(tree->tail, '@') != 0
254	    && tree->tail->prev->type != '.'
255	    && tree->tail->prev->type != '@')
256	    tok822_free_tree(tok822_sub_keep_before(tree, tree->tail));
257
258	/*
259	 * Strip trailing @.
260	 */
261	if (var_resolve_nulldom
262	    && tree->tail
263	    && tree->tail->type == '@')
264	    tok822_free_tree(tok822_sub_keep_before(tree, tree->tail));
265
266	/*
267	 * Strip (and save) @domain if local.
268	 */
269	if ((domain = tok822_rfind_type(tree->tail, '@')) != 0) {
270	    if (domain->next && RESOLVE_LOCAL(domain->next) == 0)
271		break;
272	    tok822_sub_keep_before(tree, domain);
273	    if (saved_domain)
274		tok822_free_tree(saved_domain);
275	    saved_domain = domain;
276	    domain = 0;				/* safety for future change */
277	}
278
279	/*
280	 * After stripping the local domain, if any, replace foo%bar by
281	 * foo@bar, site!user by user@site, rewrite to canonical form, and
282	 * retry.
283	 */
284	if (tok822_rfind_type(tree->tail, '@')
285	    || (var_swap_bangpath && tok822_rfind_type(tree->tail, '!'))
286	    || (var_percent_hack && tok822_rfind_type(tree->tail, '%'))) {
287	    rewrite_tree(&local_context, tree);
288	    continue;
289	}
290
291	/*
292	 * If the local-part is a quoted string, crack it open when we're
293	 * permitted to do so and look for routing operators. This is
294	 * technically incorrect, but is needed to stop relaying problems.
295	 *
296	 * XXX Do another feeble attempt to keep local-part info quoted.
297	 */
298	if (var_resolve_dequoted
299	    && tree->head && tree->head == tree->tail
300	    && tree->head->type == TOK822_QSTRING
301	    && ((oper = strrchr(local = STR(tree->head->vstr), '@')) != 0
302		|| (var_percent_hack && (oper = strrchr(local, '%')) != 0)
303	     || (var_swap_bangpath && (oper = strrchr(local, '!')) != 0))) {
304	    if (*oper == '%')
305		*oper = '@';
306	    tok822_internalize(addr_buf, tree->head, TOK822_STR_DEFL);
307	    if (*oper == '@') {
308		junk = mystrdup(STR(addr_buf));
309		quote_822_local(addr_buf, junk);
310		myfree(junk);
311	    }
312	    tok822_free(tree->head);
313	    tree->head = tok822_scan(STR(addr_buf), &tree->tail);
314	    rewrite_tree(&local_context, tree);
315	    continue;
316	}
317
318	/*
319	 * An empty local-part or an empty quoted string local-part becomes
320	 * the local MAILER-DAEMON, for consistency with our own From:
321	 * message headers.
322	 */
323	if (tree->head && tree->head == tree->tail
324	    && tree->head->type == TOK822_QSTRING
325	    && VSTRING_LEN(tree->head->vstr) == 0) {
326	    tok822_free(tree->head);
327	    tree->head = 0;
328	}
329	/* XXX must be localpart only, not user@domain form. */
330	if (tree->head == 0)
331	    tree->head = tok822_scan(var_empty_addr, &tree->tail);
332
333	/*
334	 * We're done. There are no domains left to strip off the address,
335	 * and all null local-part information is sanitized.
336	 */
337	domain = 0;
338	break;
339    }
340
341    vstring_free(addr_buf);
342    addr_buf = 0;
343
344    /*
345     * Make sure the resolved envelope recipient has the user@domain form. If
346     * no domain was specified in the address, assume the local machine. See
347     * above for what happens with an empty address.
348     */
349    if (domain == 0) {
350	if (saved_domain) {
351	    tok822_sub_append(tree, saved_domain);
352	    saved_domain = 0;
353	} else {
354	    tok822_sub_append(tree, tok822_alloc('@', (char *) 0));
355	    tok822_sub_append(tree, tok822_scan(var_myhostname, (TOK822 **) 0));
356	}
357    }
358
359    /*
360     * Transform the recipient address back to internal form.
361     *
362     * XXX This may produce incorrect results if we cracked open a quoted
363     * local-part with routing operators; see discussion above at the top of
364     * the big loop.
365     *
366     * XXX We explicitly disallow domain names in bare network address form. A
367     * network address destination should be formatted according to RFC 2821:
368     * it should be enclosed in [], and an IPv6 address should have an IPv6:
369     * prefix.
370     */
371    tok822_internalize(nextrcpt, tree, TOK822_STR_DEFL);
372    rcpt_domain = strrchr(STR(nextrcpt), '@') + 1;
373    if (rcpt_domain == 0)
374	msg_panic("no @ in address: \"%s\"", STR(nextrcpt));
375    if (*rcpt_domain == '[') {
376	if (!valid_mailhost_literal(rcpt_domain, DONT_GRIPE))
377	    *flags |= RESOLVE_FLAG_ERROR;
378    } else if (!valid_hostname(rcpt_domain, DONT_GRIPE)) {
379	if (var_resolve_num_dom && valid_hostaddr(rcpt_domain, DONT_GRIPE)) {
380	    vstring_insert(nextrcpt, rcpt_domain - STR(nextrcpt), "[", 1);
381	    vstring_strcat(nextrcpt, "]");
382	    rcpt_domain = strrchr(STR(nextrcpt), '@') + 1;
383	    if (resolve_local(rcpt_domain))	/* XXX */
384		domain = 0;
385	} else {
386	    *flags |= RESOLVE_FLAG_ERROR;
387	}
388    }
389    tok822_free_tree(tree);
390    tree = 0;
391
392    /*
393     * XXX Short-cut invalid address forms.
394     */
395    if (*flags & RESOLVE_FLAG_ERROR) {
396	*flags |= RESOLVE_CLASS_DEFAULT;
397	FREE_MEMORY_AND_RETURN;
398    }
399
400    /*
401     * Recognize routing operators in the local-part, even when we do not
402     * recognize ! or % as valid routing operators locally. This is needed to
403     * prevent backup MX hosts from relaying third-party destinations through
404     * primary MX hosts, otherwise the backup host could end up on black
405     * lists. Ignore local swap_bangpath and percent_hack settings because we
406     * can't know how the next MX host is set up.
407     */
408    if (strcmp(STR(nextrcpt) + strcspn(STR(nextrcpt), "@!%") + 1, rcpt_domain))
409	*flags |= RESOLVE_FLAG_ROUTED;
410
411    /*
412     * With local, virtual, relay, or other non-local destinations, give the
413     * highest precedence to transport associated nexthop information.
414     *
415     * Otherwise, with relay or other non-local destinations, the relayhost
416     * setting overrides the recipient domain name, and the sender-dependent
417     * relayhost overrides both.
418     *
419     * XXX Nag if the recipient domain is listed in multiple domain lists. The
420     * result is implementation defined, and may break when internals change.
421     *
422     * For now, we distinguish only a fixed number of address classes.
423     * Eventually this may become extensible, so that new classes can be
424     * configured with their own domain list, delivery transport, and
425     * recipient table.
426     */
427#define STREQ(x,y) (strcmp((x), (y)) == 0)
428
429    dict_errno = 0;
430    if (domain != 0) {
431
432	/*
433	 * Virtual alias domain.
434	 */
435	if (virt_alias_doms
436	    && string_list_match(virt_alias_doms, rcpt_domain)) {
437	    if (var_helpful_warnings) {
438		if (virt_mailbox_doms
439		    && string_list_match(virt_mailbox_doms, rcpt_domain))
440		    msg_warn("do not list domain %s in BOTH %s and %s",
441			     rcpt_domain, VAR_VIRT_ALIAS_DOMS,
442			     VAR_VIRT_MAILBOX_DOMS);
443		if (relay_domains
444		    && domain_list_match(relay_domains, rcpt_domain))
445		    msg_warn("do not list domain %s in BOTH %s and %s",
446			     rcpt_domain, VAR_VIRT_ALIAS_DOMS,
447			     VAR_RELAY_DOMAINS);
448#if 0
449		if (strcasecmp(rcpt_domain, var_myorigin) == 0)
450		    msg_warn("do not list $%s (%s) in %s",
451			   VAR_MYORIGIN, var_myorigin, VAR_VIRT_ALIAS_DOMS);
452#endif
453	    }
454	    vstring_strcpy(channel, MAIL_SERVICE_ERROR);
455	    vstring_sprintf(nexthop, "User unknown%s",
456			    var_show_unk_rcpt_table ?
457			    " in virtual alias table" : "");
458	    *flags |= RESOLVE_CLASS_ALIAS;
459	} else if (dict_errno != 0) {
460	    msg_warn("%s lookup failure", VAR_VIRT_ALIAS_DOMS);
461	    *flags |= RESOLVE_FLAG_FAIL;
462	    FREE_MEMORY_AND_RETURN;
463	}
464
465	/*
466	 * Virtual mailbox domain.
467	 */
468	else if (virt_mailbox_doms
469		 && string_list_match(virt_mailbox_doms, rcpt_domain)) {
470	    if (var_helpful_warnings) {
471		if (relay_domains
472		    && domain_list_match(relay_domains, rcpt_domain))
473		    msg_warn("do not list domain %s in BOTH %s and %s",
474			     rcpt_domain, VAR_VIRT_MAILBOX_DOMS,
475			     VAR_RELAY_DOMAINS);
476	    }
477	    vstring_strcpy(channel, RES_PARAM_VALUE(rp->virt_transport));
478	    vstring_strcpy(nexthop, rcpt_domain);
479	    blame = rp->virt_transport_name;
480	    *flags |= RESOLVE_CLASS_VIRTUAL;
481	} else if (dict_errno != 0) {
482	    msg_warn("%s lookup failure", VAR_VIRT_MAILBOX_DOMS);
483	    *flags |= RESOLVE_FLAG_FAIL;
484	    FREE_MEMORY_AND_RETURN;
485	} else {
486
487	    /*
488	     * Off-host relay destination.
489	     */
490	    if (relay_domains
491		&& domain_list_match(relay_domains, rcpt_domain)) {
492		vstring_strcpy(channel, RES_PARAM_VALUE(rp->relay_transport));
493		blame = rp->relay_transport_name;
494		*flags |= RESOLVE_CLASS_RELAY;
495	    } else if (dict_errno != 0) {
496		msg_warn("%s lookup failure", VAR_RELAY_DOMAINS);
497		*flags |= RESOLVE_FLAG_FAIL;
498		FREE_MEMORY_AND_RETURN;
499	    }
500
501	    /*
502	     * Other off-host destination.
503	     */
504	    else {
505		if (rp->snd_def_xp_info
506		    && (xport = mail_addr_find(rp->snd_def_xp_info,
507					    sender_key = (*sender ? sender :
508					       var_null_def_xport_maps_key),
509					       (char **) 0)) != 0) {
510		    if (*xport == 0) {
511			msg_warn("%s: ignoring null lookup result for %s",
512				 rp->snd_def_xp_maps_name, sender_key);
513			xport = "DUNNO";
514		    }
515		    vstring_strcpy(channel, strcasecmp(xport, "DUNNO") == 0 ?
516				RES_PARAM_VALUE(rp->def_transport) : xport);
517		    blame = rp->snd_def_xp_maps_name;
518		} else if (dict_errno != 0) {
519		    msg_warn("%s lookup failure", rp->snd_def_xp_maps_name);
520		    *flags |= RESOLVE_FLAG_FAIL;
521		    FREE_MEMORY_AND_RETURN;
522		} else {
523		    vstring_strcpy(channel, RES_PARAM_VALUE(rp->def_transport));
524		    blame = rp->def_transport_name;
525		}
526		*flags |= RESOLVE_CLASS_DEFAULT;
527	    }
528
529	    /*
530	     * With off-host delivery, sender-dependent or global relayhost
531	     * override the recipient domain.
532	     */
533	    if (rp->snd_relay_info
534		&& (relay = mail_addr_find(rp->snd_relay_info,
535					   sender_key = (*sender ? sender :
536						   var_null_relay_maps_key),
537					   (char **) 0)) != 0) {
538		if (*relay == 0) {
539		    msg_warn("%s: ignoring null lookup result for %s",
540			     rp->snd_relay_maps_name, sender_key);
541		    relay = "DUNNO";
542		}
543		vstring_strcpy(nexthop, strcasecmp(relay, "DUNNO") == 0 ?
544			       rcpt_domain : relay);
545	    } else if (dict_errno != 0) {
546		msg_warn("%s lookup failure", rp->snd_relay_maps_name);
547		*flags |= RESOLVE_FLAG_FAIL;
548		FREE_MEMORY_AND_RETURN;
549	    } else if (*RES_PARAM_VALUE(rp->relayhost))
550		vstring_strcpy(nexthop, RES_PARAM_VALUE(rp->relayhost));
551	    else
552		vstring_strcpy(nexthop, rcpt_domain);
553	}
554    }
555
556    /*
557     * Local delivery.
558     *
559     * XXX Nag if the domain is listed in multiple domain lists. The effect is
560     * implementation defined, and may break when internals change.
561     */
562    else {
563	if (var_helpful_warnings) {
564	    if (virt_alias_doms
565		&& string_list_match(virt_alias_doms, rcpt_domain))
566		msg_warn("do not list domain %s in BOTH %s and %s",
567			 rcpt_domain, VAR_MYDEST, VAR_VIRT_ALIAS_DOMS);
568	    if (virt_mailbox_doms
569		&& string_list_match(virt_mailbox_doms, rcpt_domain))
570		msg_warn("do not list domain %s in BOTH %s and %s",
571			 rcpt_domain, VAR_MYDEST, VAR_VIRT_MAILBOX_DOMS);
572	}
573	vstring_strcpy(channel, RES_PARAM_VALUE(rp->local_transport));
574	vstring_strcpy(nexthop, rcpt_domain);
575	blame = rp->local_transport_name;
576	*flags |= RESOLVE_CLASS_LOCAL;
577    }
578
579    /*
580     * An explicit main.cf transport:nexthop setting overrides the nexthop.
581     *
582     * XXX We depend on this mechanism to enforce per-recipient concurrencies
583     * for local recipients. With "local_transport = local:$myhostname" we
584     * force mail for any domain in $mydestination/${proxy,inet}_interfaces
585     * to share the same queue.
586     */
587    if ((destination = split_at(STR(channel), ':')) != 0 && *destination)
588	vstring_strcpy(nexthop, destination);
589
590    /*
591     * Sanity checks.
592     */
593    if (*STR(channel) == 0) {
594	if (blame == 0)
595	    msg_panic("%s: null blame", myname);
596	msg_warn("file %s/%s: parameter %s: null transport is not allowed",
597		 var_config_dir, MAIN_CONF_FILE, blame);
598	*flags |= RESOLVE_FLAG_FAIL;
599	FREE_MEMORY_AND_RETURN;
600    }
601    if (*STR(nexthop) == 0)
602	msg_panic("%s: null nexthop", myname);
603
604    /*
605     * The transport map can selectively override any transport and/or
606     * nexthop host info that is set up above. Unfortunately, the syntax for
607     * nexthop information is transport specific. We therefore need sane and
608     * intuitive semantics for transport map entries that specify a channel
609     * but no nexthop.
610     *
611     * With non-error transports, the initial nexthop information is the
612     * recipient domain. However, specific main.cf transport definitions may
613     * specify a transport-specific destination, such as a host + TCP socket,
614     * or the pathname of a UNIX-domain socket. With less precedence than
615     * main.cf transport definitions, a main.cf relayhost definition may also
616     * override nexthop information for off-host deliveries.
617     *
618     * With the error transport, the nexthop information is free text that
619     * specifies the reason for non-delivery.
620     *
621     * Because nexthop syntax is transport specific we reset the nexthop
622     * information to the recipient domain when the transport table specifies
623     * a transport without also specifying the nexthop information.
624     *
625     * Subtle note: reset nexthop even when the transport table does not change
626     * the transport. Otherwise it is hard to get rid of main.cf specified
627     * nexthop information.
628     *
629     * XXX Don't override the virtual alias class (error:User unknown) result.
630     */
631    if (rp->transport_info && !(*flags & RESOLVE_CLASS_ALIAS)) {
632	if (transport_lookup(rp->transport_info, STR(nextrcpt),
633			     rcpt_domain, channel, nexthop) == 0
634	    && dict_errno != 0) {
635	    msg_warn("%s lookup failure", rp->transport_maps_name);
636	    *flags |= RESOLVE_FLAG_FAIL;
637	    FREE_MEMORY_AND_RETURN;
638	}
639    }
640
641    /*
642     * Bounce recipients that have moved, regardless of domain address class.
643     * We do this last, in anticipation of transport maps that can override
644     * the recipient address.
645     *
646     * The downside of not doing this in delivery agents is that this table has
647     * no effect on local alias expansion results. Such mail will have to
648     * make almost an entire iteration through the mail system.
649     */
650#define IGNORE_ADDR_EXTENSION   ((char **) 0)
651
652    if (relocated_maps != 0) {
653	const char *newloc;
654
655	if ((newloc = mail_addr_find(relocated_maps, STR(nextrcpt),
656				     IGNORE_ADDR_EXTENSION)) != 0) {
657	    vstring_strcpy(channel, MAIL_SERVICE_ERROR);
658	    /* 5.1.6 is the closest match, but not perfect. */
659	    vstring_sprintf(nexthop, "5.1.6 User has moved to %s", newloc);
660	} else if (dict_errno != 0) {
661	    msg_warn("%s lookup failure", VAR_RELOCATED_MAPS);
662	    *flags |= RESOLVE_FLAG_FAIL;
663	    FREE_MEMORY_AND_RETURN;
664	}
665    }
666
667    /*
668     * Bounce recipient addresses that start with `-'. External commands may
669     * misinterpret such addresses as command-line options.
670     *
671     * In theory I could say people should always carefully set up their
672     * master.cf pipe mailer entries with `--' before the first non-option
673     * argument, but mistakes will happen regardless.
674     *
675     * Therefore the protection is put in place here, where it cannot be
676     * bypassed.
677     */
678    if (var_allow_min_user == 0 && STR(nextrcpt)[0] == '-') {
679	*flags |= RESOLVE_FLAG_ERROR;
680	FREE_MEMORY_AND_RETURN;
681    }
682
683    /*
684     * Clean up.
685     */
686    FREE_MEMORY_AND_RETURN;
687}
688
689/* Static, so they can be used by the network protocol interface only. */
690
691static VSTRING *channel;
692static VSTRING *nexthop;
693static VSTRING *nextrcpt;
694static VSTRING *query;
695static VSTRING *sender;
696
697/* resolve_proto - read request and send reply */
698
699int     resolve_proto(RES_CONTEXT *context, VSTREAM *stream)
700{
701    int     flags;
702
703    if (attr_scan(stream, ATTR_FLAG_STRICT,
704		  ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender,
705		  ATTR_TYPE_STR, MAIL_ATTR_ADDR, query,
706		  ATTR_TYPE_END) != 2)
707	return (-1);
708
709    resolve_addr(context, STR(sender), STR(query),
710		 channel, nexthop, nextrcpt, &flags);
711
712    if (msg_verbose)
713	msg_info("`%s' -> `%s' -> (`%s' `%s' `%s' `%d')",
714		 STR(sender), STR(query), STR(channel),
715		 STR(nexthop), STR(nextrcpt), flags);
716
717    attr_print(stream, ATTR_FLAG_NONE,
718	       ATTR_TYPE_INT, MAIL_ATTR_FLAGS, server_flags,
719	       ATTR_TYPE_STR, MAIL_ATTR_TRANSPORT, STR(channel),
720	       ATTR_TYPE_STR, MAIL_ATTR_NEXTHOP, STR(nexthop),
721	       ATTR_TYPE_STR, MAIL_ATTR_RECIP, STR(nextrcpt),
722	       ATTR_TYPE_INT, MAIL_ATTR_FLAGS, flags,
723	       ATTR_TYPE_END);
724
725    if (vstream_fflush(stream) != 0) {
726	msg_warn("write resolver reply: %m");
727	return (-1);
728    }
729    return (0);
730}
731
732/* resolve_init - module initializations */
733
734void    resolve_init(void)
735{
736    sender = vstring_alloc(100);
737    query = vstring_alloc(100);
738    channel = vstring_alloc(100);
739    nexthop = vstring_alloc(100);
740    nextrcpt = vstring_alloc(100);
741
742    if (*var_virt_alias_doms)
743	virt_alias_doms =
744	    string_list_init(MATCH_FLAG_NONE, var_virt_alias_doms);
745
746    if (*var_virt_mailbox_doms)
747	virt_mailbox_doms =
748	    string_list_init(MATCH_FLAG_NONE, var_virt_mailbox_doms);
749
750    if (*var_relay_domains)
751	relay_domains =
752	    domain_list_init(match_parent_style(VAR_RELAY_DOMAINS),
753			     var_relay_domains);
754
755    if (*var_relocated_maps)
756	relocated_maps =
757	    maps_create(VAR_RELOCATED_MAPS, var_relocated_maps,
758			DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
759}
760