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