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