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