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