resolve.c revision 1.3
1/* $NetBSD: resolve.c,v 1.3 2020/03/18 19:05: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 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 */ 144static DOMAIN_LIST *relay_domains; 145static STRING_LIST *virt_alias_doms; 146static STRING_LIST *virt_mailbox_doms; 147 148static MAPS *relocated_maps; 149 150/* resolve_class - determine domain address class */ 151 152int 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 184static 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 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 758static VSTRING *channel; 759static VSTRING *nexthop; 760static VSTRING *nextrcpt; 761static VSTRING *query; 762static VSTRING *sender; 763 764/* resolve_proto - read request and send reply */ 765 766int 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 801void 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