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