xref: /netbsd-src/external/ibm-public/postfix/dist/src/trivial-rewrite/resolve.c (revision d6384a751f5f4beb133abed41aa9964df66d9f1c)
1 /*	$NetBSD: resolve.c,v 1.1.1.5 2013/08/21 20:09:58 tron 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 /*	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   */
131 static DOMAIN_LIST *relay_domains;
132 static STRING_LIST *virt_alias_doms;
133 static STRING_LIST *virt_mailbox_doms;
134 
135 static MAPS *relocated_maps;
136 
137 /* resolve_addr - resolve address according to rule set */
138 
139 static 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     int     rc;
161 
162     *flags = 0;
163     vstring_strcpy(channel, "CHANNEL NOT UPDATED");
164     vstring_strcpy(nexthop, "NEXTHOP NOT UPDATED");
165     vstring_strcpy(nextrcpt, "NEXTRCPT NOT UPDATED");
166 
167     /*
168      * The address is in internalized (unquoted) form.
169      *
170      * In an ideal world we would parse the externalized address form as given
171      * to us by the sender.
172      *
173      * However, in the real world we have to look for routing characters like
174      * %@! in the address local-part, even when that information is quoted
175      * due to the presence of special characters or whitespace. Although
176      * technically incorrect, this is needed to stop user@domain@domain relay
177      * attempts when forwarding mail to a Sendmail MX host.
178      *
179      * This suggests that we parse the address in internalized (unquoted) form.
180      * Unfortunately, if we do that, the unparser generates incorrect white
181      * space between adjacent non-operator tokens. Example: ``first last''
182      * needs white space, but ``stuff[stuff]'' does not. This is is not a
183      * problem when unparsing the result from parsing externalized forms,
184      * because the parser/unparser were designed for valid externalized forms
185      * where ``stuff[stuff]'' does not happen.
186      *
187      * As a workaround we start with the quoted form and then dequote the
188      * local-part only where needed. This will do the right thing in most
189      * (but not all) cases.
190      */
191     addr_len = strlen(addr);
192     quote_822_local(addr_buf, addr);
193     tree = tok822_scan_addr(vstring_str(addr_buf));
194 
195     /*
196      * The optimizer will eliminate tests that always fail, and will replace
197      * multiple expansions of this macro by a GOTO to a single instance.
198      */
199 #define FREE_MEMORY_AND_RETURN { \
200 	if (saved_domain) \
201 	    tok822_free_tree(saved_domain); \
202 	if(tree) \
203 	    tok822_free_tree(tree); \
204 	if (addr_buf) \
205 	    vstring_free(addr_buf); \
206 	return; \
207     }
208 
209     /*
210      * Preliminary resolver: strip off all instances of the local domain.
211      * Terminate when no destination domain is left over, or when the
212      * destination domain is remote.
213      *
214      * XXX To whom it may concern. If you change the resolver loop below, or
215      * quote_822_local.c, or tok822_parse.c, be sure to re-run the tests
216      * under "make resolve_clnt_test" in the global directory.
217      */
218 #define RESOLVE_LOCAL(domain) \
219     resolve_local(STR(tok822_internalize(addr_buf, domain, TOK822_STR_DEFL)))
220 
221     for (loop_count = 0, loop_max = addr_len + 100; /* void */ ; loop_count++) {
222 
223 	/*
224 	 * XXX Should never happen, but if this happens with some
225 	 * pathological address, then that is not sufficient reason to
226 	 * disrupt the operation of an MTA.
227 	 */
228 	if (loop_count > loop_max) {
229 	    msg_warn("resolve_addr: <%s>: giving up after %ld iterations",
230 		     addr, (long) loop_count);
231 	    *flags |= RESOLVE_FLAG_FAIL;
232 	    FREE_MEMORY_AND_RETURN;
233 	    break;
234 	}
235 
236 	/*
237 	 * Strip trailing dot at end of domain, but not dot-dot or at-dot.
238 	 * This merely makes diagnostics more accurate by leaving bogus
239 	 * addresses alone.
240 	 */
241 	if (tree->tail
242 	    && tree->tail->type == '.'
243 	    && tok822_rfind_type(tree->tail, '@') != 0
244 	    && tree->tail->prev->type != '.'
245 	    && tree->tail->prev->type != '@')
246 	    tok822_free_tree(tok822_sub_keep_before(tree, tree->tail));
247 
248 	/*
249 	 * Strip trailing @.
250 	 */
251 	if (var_resolve_nulldom
252 	    && tree->tail
253 	    && tree->tail->type == '@')
254 	    tok822_free_tree(tok822_sub_keep_before(tree, tree->tail));
255 
256 	/*
257 	 * Strip (and save) @domain if local.
258 	 *
259 	 * Grr. resolve_local() table lookups may fail. It may be OK for local
260 	 * file lookup code to abort upon failure, but with network-based
261 	 * tables it is preferable to return an error indication to the
262 	 * requestor.
263 	 */
264 	if ((domain = tok822_rfind_type(tree->tail, '@')) != 0) {
265 	    if (domain->next && (rc = RESOLVE_LOCAL(domain->next)) <= 0) {
266 		if (rc < 0) {
267 		    *flags |= RESOLVE_FLAG_FAIL;
268 		    FREE_MEMORY_AND_RETURN;
269 		}
270 		break;
271 	    }
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 Re-resolve the surrogate, in case already in user@domain form. */
330 	if (tree->head == 0) {
331 	    tree->head = tok822_scan(var_empty_addr, &tree->tail);
332 	    continue;
333 	}
334 
335 	/* XXX Re-resolve with @$myhostname for backwards compatibility. */
336 	if (domain == 0 && saved_domain == 0) {
337 	    tok822_sub_append(tree, tok822_alloc('@', (char *) 0));
338 	    tok822_sub_append(tree, tok822_scan(var_myhostname, (TOK822 **) 0));
339 	    continue;
340 	}
341 
342 	/*
343 	 * We're done. There are no domains left to strip off the address,
344 	 * and all null local-part information is sanitized.
345 	 */
346 	domain = 0;
347 	break;
348     }
349 
350     vstring_free(addr_buf);
351     addr_buf = 0;
352 
353     /*
354      * Make sure the resolved envelope recipient has the user@domain form. If
355      * no domain was specified in the address, assume the local machine. See
356      * above for what happens with an empty address.
357      */
358     if (domain == 0) {
359 	if (saved_domain) {
360 	    tok822_sub_append(tree, saved_domain);
361 	    saved_domain = 0;
362 	} else {
363 	    tok822_sub_append(tree, tok822_alloc('@', (char *) 0));
364 	    tok822_sub_append(tree, tok822_scan(var_myhostname, (TOK822 **) 0));
365 	}
366     }
367 
368     /*
369      * Transform the recipient address back to internal form.
370      *
371      * XXX This may produce incorrect results if we cracked open a quoted
372      * local-part with routing operators; see discussion above at the top of
373      * the big loop.
374      *
375      * XXX We explicitly disallow domain names in bare network address form. A
376      * network address destination should be formatted according to RFC 2821:
377      * it should be enclosed in [], and an IPv6 address should have an IPv6:
378      * prefix.
379      */
380     tok822_internalize(nextrcpt, tree, TOK822_STR_DEFL);
381     rcpt_domain = strrchr(STR(nextrcpt), '@') + 1;
382     if (rcpt_domain == 0)
383 	msg_panic("no @ in address: \"%s\"", STR(nextrcpt));
384     if (*rcpt_domain == '[') {
385 	if (!valid_mailhost_literal(rcpt_domain, DONT_GRIPE))
386 	    *flags |= RESOLVE_FLAG_ERROR;
387     } else if (!valid_hostname(rcpt_domain, DONT_GRIPE)) {
388 	if (var_resolve_num_dom && valid_hostaddr(rcpt_domain, DONT_GRIPE)) {
389 	    vstring_insert(nextrcpt, rcpt_domain - STR(nextrcpt), "[", 1);
390 	    vstring_strcat(nextrcpt, "]");
391 	    rcpt_domain = strrchr(STR(nextrcpt), '@') + 1;
392 	    if ((rc = resolve_local(rcpt_domain)) > 0)	/* XXX */
393 		domain = 0;
394 	    else if (rc < 0) {
395 		*flags |= RESOLVE_FLAG_FAIL;
396 		FREE_MEMORY_AND_RETURN;
397 	    }
398 	} else {
399 	    *flags |= RESOLVE_FLAG_ERROR;
400 	}
401     }
402     tok822_free_tree(tree);
403     tree = 0;
404 
405     /*
406      * XXX Short-cut invalid address forms.
407      */
408     if (*flags & RESOLVE_FLAG_ERROR) {
409 	*flags |= RESOLVE_CLASS_DEFAULT;
410 	FREE_MEMORY_AND_RETURN;
411     }
412 
413     /*
414      * Recognize routing operators in the local-part, even when we do not
415      * recognize ! or % as valid routing operators locally. This is needed to
416      * prevent backup MX hosts from relaying third-party destinations through
417      * primary MX hosts, otherwise the backup host could end up on black
418      * lists. Ignore local swap_bangpath and percent_hack settings because we
419      * can't know how the next MX host is set up.
420      */
421     if (strcmp(STR(nextrcpt) + strcspn(STR(nextrcpt), "@!%") + 1, rcpt_domain))
422 	*flags |= RESOLVE_FLAG_ROUTED;
423 
424     /*
425      * With local, virtual, relay, or other non-local destinations, give the
426      * highest precedence to transport associated nexthop information.
427      *
428      * Otherwise, with relay or other non-local destinations, the relayhost
429      * setting overrides the recipient domain name, and the sender-dependent
430      * relayhost overrides both.
431      *
432      * XXX Nag if the recipient domain is listed in multiple domain lists. The
433      * result is implementation defined, and may break when internals change.
434      *
435      * For now, we distinguish only a fixed number of address classes.
436      * Eventually this may become extensible, so that new classes can be
437      * configured with their own domain list, delivery transport, and
438      * recipient table.
439      */
440 #define STREQ(x,y) (strcmp((x), (y)) == 0)
441 
442     if (domain != 0) {
443 
444 	/*
445 	 * Virtual alias domain.
446 	 */
447 	if (virt_alias_doms
448 	    && string_list_match(virt_alias_doms, rcpt_domain)) {
449 	    if (var_helpful_warnings) {
450 		if (virt_mailbox_doms
451 		    && string_list_match(virt_mailbox_doms, rcpt_domain))
452 		    msg_warn("do not list domain %s in BOTH %s and %s",
453 			     rcpt_domain, VAR_VIRT_ALIAS_DOMS,
454 			     VAR_VIRT_MAILBOX_DOMS);
455 		if (relay_domains
456 		    && domain_list_match(relay_domains, rcpt_domain))
457 		    msg_warn("do not list domain %s in BOTH %s and %s",
458 			     rcpt_domain, VAR_VIRT_ALIAS_DOMS,
459 			     VAR_RELAY_DOMAINS);
460 #if 0
461 		if (strcasecmp(rcpt_domain, var_myorigin) == 0)
462 		    msg_warn("do not list $%s (%s) in %s",
463 			   VAR_MYORIGIN, var_myorigin, VAR_VIRT_ALIAS_DOMS);
464 #endif
465 	    }
466 	    vstring_strcpy(channel, MAIL_SERVICE_ERROR);
467 	    vstring_sprintf(nexthop, "User unknown%s",
468 			    var_show_unk_rcpt_table ?
469 			    " in virtual alias table" : "");
470 	    *flags |= RESOLVE_CLASS_ALIAS;
471 	} else if (virt_alias_doms && virt_alias_doms->error != 0) {
472 	    msg_warn("%s lookup failure", VAR_VIRT_ALIAS_DOMS);
473 	    *flags |= RESOLVE_FLAG_FAIL;
474 	    FREE_MEMORY_AND_RETURN;
475 	}
476 
477 	/*
478 	 * Virtual mailbox domain.
479 	 */
480 	else if (virt_mailbox_doms
481 		 && string_list_match(virt_mailbox_doms, rcpt_domain)) {
482 	    if (var_helpful_warnings) {
483 		if (relay_domains
484 		    && domain_list_match(relay_domains, rcpt_domain))
485 		    msg_warn("do not list domain %s in BOTH %s and %s",
486 			     rcpt_domain, VAR_VIRT_MAILBOX_DOMS,
487 			     VAR_RELAY_DOMAINS);
488 	    }
489 	    vstring_strcpy(channel, RES_PARAM_VALUE(rp->virt_transport));
490 	    vstring_strcpy(nexthop, rcpt_domain);
491 	    blame = rp->virt_transport_name;
492 	    *flags |= RESOLVE_CLASS_VIRTUAL;
493 	} else if (virt_mailbox_doms && virt_mailbox_doms->error != 0) {
494 	    msg_warn("%s lookup failure", VAR_VIRT_MAILBOX_DOMS);
495 	    *flags |= RESOLVE_FLAG_FAIL;
496 	    FREE_MEMORY_AND_RETURN;
497 	} else {
498 
499 	    /*
500 	     * Off-host relay destination.
501 	     */
502 	    if (relay_domains
503 		&& domain_list_match(relay_domains, rcpt_domain)) {
504 		vstring_strcpy(channel, RES_PARAM_VALUE(rp->relay_transport));
505 		blame = rp->relay_transport_name;
506 		*flags |= RESOLVE_CLASS_RELAY;
507 	    } else if (relay_domains && relay_domains->error != 0) {
508 		msg_warn("%s lookup failure", VAR_RELAY_DOMAINS);
509 		*flags |= RESOLVE_FLAG_FAIL;
510 		FREE_MEMORY_AND_RETURN;
511 	    }
512 
513 	    /*
514 	     * Other off-host destination.
515 	     */
516 	    else {
517 		if (rp->snd_def_xp_info
518 		    && (xport = mail_addr_find(rp->snd_def_xp_info,
519 					    sender_key = (*sender ? sender :
520 					       var_null_def_xport_maps_key),
521 					       (char **) 0)) != 0) {
522 		    if (*xport == 0) {
523 			msg_warn("%s: ignoring null lookup result for %s",
524 				 rp->snd_def_xp_maps_name, sender_key);
525 			xport = "DUNNO";
526 		    }
527 		    vstring_strcpy(channel, strcasecmp(xport, "DUNNO") == 0 ?
528 				RES_PARAM_VALUE(rp->def_transport) : xport);
529 		    blame = rp->snd_def_xp_maps_name;
530 		} else if (rp->snd_def_xp_info
531 			   && rp->snd_def_xp_info->error != 0) {
532 		    msg_warn("%s lookup failure", rp->snd_def_xp_maps_name);
533 		    *flags |= RESOLVE_FLAG_FAIL;
534 		    FREE_MEMORY_AND_RETURN;
535 		} else {
536 		    vstring_strcpy(channel, RES_PARAM_VALUE(rp->def_transport));
537 		    blame = rp->def_transport_name;
538 		}
539 		*flags |= RESOLVE_CLASS_DEFAULT;
540 	    }
541 
542 	    /*
543 	     * With off-host delivery, sender-dependent or global relayhost
544 	     * override the recipient domain.
545 	     */
546 	    if (rp->snd_relay_info
547 		&& (relay = mail_addr_find(rp->snd_relay_info,
548 					   sender_key = (*sender ? sender :
549 						   var_null_relay_maps_key),
550 					   (char **) 0)) != 0) {
551 		if (*relay == 0) {
552 		    msg_warn("%s: ignoring null lookup result for %s",
553 			     rp->snd_relay_maps_name, sender_key);
554 		    relay = "DUNNO";
555 		}
556 		vstring_strcpy(nexthop, strcasecmp(relay, "DUNNO") == 0 ?
557 			       rcpt_domain : relay);
558 	    } else if (rp->snd_relay_info
559 		       && rp->snd_relay_info->error != 0) {
560 		msg_warn("%s lookup failure", rp->snd_relay_maps_name);
561 		*flags |= RESOLVE_FLAG_FAIL;
562 		FREE_MEMORY_AND_RETURN;
563 	    } else if (*RES_PARAM_VALUE(rp->relayhost))
564 		vstring_strcpy(nexthop, RES_PARAM_VALUE(rp->relayhost));
565 	    else
566 		vstring_strcpy(nexthop, rcpt_domain);
567 	}
568     }
569 
570     /*
571      * Local delivery.
572      *
573      * XXX Nag if the domain is listed in multiple domain lists. The effect is
574      * implementation defined, and may break when internals change.
575      */
576     else {
577 	if (var_helpful_warnings) {
578 	    if (virt_alias_doms
579 		&& string_list_match(virt_alias_doms, rcpt_domain))
580 		msg_warn("do not list domain %s in BOTH %s and %s",
581 			 rcpt_domain, VAR_MYDEST, VAR_VIRT_ALIAS_DOMS);
582 	    if (virt_mailbox_doms
583 		&& string_list_match(virt_mailbox_doms, rcpt_domain))
584 		msg_warn("do not list domain %s in BOTH %s and %s",
585 			 rcpt_domain, VAR_MYDEST, VAR_VIRT_MAILBOX_DOMS);
586 	}
587 	vstring_strcpy(channel, RES_PARAM_VALUE(rp->local_transport));
588 	vstring_strcpy(nexthop, rcpt_domain);
589 	blame = rp->local_transport_name;
590 	*flags |= RESOLVE_CLASS_LOCAL;
591     }
592 
593     /*
594      * An explicit main.cf transport:nexthop setting overrides the nexthop.
595      *
596      * XXX We depend on this mechanism to enforce per-recipient concurrencies
597      * for local recipients. With "local_transport = local:$myhostname" we
598      * force mail for any domain in $mydestination/${proxy,inet}_interfaces
599      * to share the same queue.
600      */
601     if ((destination = split_at(STR(channel), ':')) != 0 && *destination)
602 	vstring_strcpy(nexthop, destination);
603 
604     /*
605      * Sanity checks.
606      */
607     if (*STR(channel) == 0) {
608 	if (blame == 0)
609 	    msg_panic("%s: null blame", myname);
610 	msg_warn("file %s/%s: parameter %s: null transport is not allowed",
611 		 var_config_dir, MAIN_CONF_FILE, blame);
612 	*flags |= RESOLVE_FLAG_FAIL;
613 	FREE_MEMORY_AND_RETURN;
614     }
615     if (*STR(nexthop) == 0)
616 	msg_panic("%s: null nexthop", myname);
617 
618     /*
619      * The transport map can selectively override any transport and/or
620      * nexthop host info that is set up above. Unfortunately, the syntax for
621      * nexthop information is transport specific. We therefore need sane and
622      * intuitive semantics for transport map entries that specify a channel
623      * but no nexthop.
624      *
625      * With non-error transports, the initial nexthop information is the
626      * recipient domain. However, specific main.cf transport definitions may
627      * specify a transport-specific destination, such as a host + TCP socket,
628      * or the pathname of a UNIX-domain socket. With less precedence than
629      * main.cf transport definitions, a main.cf relayhost definition may also
630      * override nexthop information for off-host deliveries.
631      *
632      * With the error transport, the nexthop information is free text that
633      * specifies the reason for non-delivery.
634      *
635      * Because nexthop syntax is transport specific we reset the nexthop
636      * information to the recipient domain when the transport table specifies
637      * a transport without also specifying the nexthop information.
638      *
639      * Subtle note: reset nexthop even when the transport table does not change
640      * the transport. Otherwise it is hard to get rid of main.cf specified
641      * nexthop information.
642      *
643      * XXX Don't override the virtual alias class (error:User unknown) result.
644      */
645     if (rp->transport_info && !(*flags & RESOLVE_CLASS_ALIAS)) {
646 	if (transport_lookup(rp->transport_info, STR(nextrcpt),
647 			     rcpt_domain, channel, nexthop) == 0
648 	    && rp->transport_info->transport_path->error != 0) {
649 	    msg_warn("%s lookup failure", rp->transport_maps_name);
650 	    *flags |= RESOLVE_FLAG_FAIL;
651 	    FREE_MEMORY_AND_RETURN;
652 	}
653     }
654 
655     /*
656      * Bounce recipients that have moved, regardless of domain address class.
657      * We do this last, in anticipation of transport maps that can override
658      * the recipient address.
659      *
660      * The downside of not doing this in delivery agents is that this table has
661      * no effect on local alias expansion results. Such mail will have to
662      * make almost an entire iteration through the mail system.
663      */
664 #define IGNORE_ADDR_EXTENSION   ((char **) 0)
665 
666     if (relocated_maps != 0) {
667 	const char *newloc;
668 
669 	if ((newloc = mail_addr_find(relocated_maps, STR(nextrcpt),
670 				     IGNORE_ADDR_EXTENSION)) != 0) {
671 	    vstring_strcpy(channel, MAIL_SERVICE_ERROR);
672 	    /* 5.1.6 is the closest match, but not perfect. */
673 	    vstring_sprintf(nexthop, "5.1.6 User has moved to %s", newloc);
674 	} else if (relocated_maps->error != 0) {
675 	    msg_warn("%s lookup failure", VAR_RELOCATED_MAPS);
676 	    *flags |= RESOLVE_FLAG_FAIL;
677 	    FREE_MEMORY_AND_RETURN;
678 	}
679     }
680 
681     /*
682      * Bounce recipient addresses that start with `-'. External commands may
683      * misinterpret such addresses as command-line options.
684      *
685      * In theory I could say people should always carefully set up their
686      * master.cf pipe mailer entries with `--' before the first non-option
687      * argument, but mistakes will happen regardless.
688      *
689      * Therefore the protection is put in place here, where it cannot be
690      * bypassed.
691      */
692     if (var_allow_min_user == 0 && STR(nextrcpt)[0] == '-') {
693 	*flags |= RESOLVE_FLAG_ERROR;
694 	FREE_MEMORY_AND_RETURN;
695     }
696 
697     /*
698      * Clean up.
699      */
700     FREE_MEMORY_AND_RETURN;
701 }
702 
703 /* Static, so they can be used by the network protocol interface only. */
704 
705 static VSTRING *channel;
706 static VSTRING *nexthop;
707 static VSTRING *nextrcpt;
708 static VSTRING *query;
709 static VSTRING *sender;
710 
711 /* resolve_proto - read request and send reply */
712 
713 int     resolve_proto(RES_CONTEXT *context, VSTREAM *stream)
714 {
715     int     flags;
716 
717     if (attr_scan(stream, ATTR_FLAG_STRICT,
718 		  ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender,
719 		  ATTR_TYPE_STR, MAIL_ATTR_ADDR, query,
720 		  ATTR_TYPE_END) != 2)
721 	return (-1);
722 
723     resolve_addr(context, STR(sender), STR(query),
724 		 channel, nexthop, nextrcpt, &flags);
725 
726     if (msg_verbose)
727 	msg_info("`%s' -> `%s' -> (`%s' `%s' `%s' `%d')",
728 		 STR(sender), STR(query), STR(channel),
729 		 STR(nexthop), STR(nextrcpt), flags);
730 
731     attr_print(stream, ATTR_FLAG_NONE,
732 	       ATTR_TYPE_INT, MAIL_ATTR_FLAGS, server_flags,
733 	       ATTR_TYPE_STR, MAIL_ATTR_TRANSPORT, STR(channel),
734 	       ATTR_TYPE_STR, MAIL_ATTR_NEXTHOP, STR(nexthop),
735 	       ATTR_TYPE_STR, MAIL_ATTR_RECIP, STR(nextrcpt),
736 	       ATTR_TYPE_INT, MAIL_ATTR_FLAGS, flags,
737 	       ATTR_TYPE_END);
738 
739     if (vstream_fflush(stream) != 0) {
740 	msg_warn("write resolver reply: %m");
741 	return (-1);
742     }
743     return (0);
744 }
745 
746 /* resolve_init - module initializations */
747 
748 void    resolve_init(void)
749 {
750     sender = vstring_alloc(100);
751     query = vstring_alloc(100);
752     channel = vstring_alloc(100);
753     nexthop = vstring_alloc(100);
754     nextrcpt = vstring_alloc(100);
755 
756     if (*var_virt_alias_doms)
757 	virt_alias_doms =
758 	    string_list_init(MATCH_FLAG_RETURN, var_virt_alias_doms);
759 
760     if (*var_virt_mailbox_doms)
761 	virt_mailbox_doms =
762 	    string_list_init(MATCH_FLAG_RETURN, var_virt_mailbox_doms);
763 
764     if (*var_relay_domains)
765 	relay_domains =
766 	    domain_list_init(MATCH_FLAG_RETURN
767 			     | match_parent_style(VAR_RELAY_DOMAINS),
768 			     var_relay_domains);
769 
770     if (*var_relocated_maps)
771 	relocated_maps =
772 	    maps_create(VAR_RELOCATED_MAPS, var_relocated_maps,
773 			DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX);
774 }
775