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