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