1 /* $NetBSD: resolve.c,v 1.1.1.2 2010/04/17 10:24:54 tron 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 /* 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 */ 131 static DOMAIN_LIST *relay_domains; 132 static STRING_LIST *virt_alias_doms; 133 static STRING_LIST *virt_mailbox_doms; 134 135 static MAPS *relocated_maps; 136 137 /* resolve_addr - resolve address according to rule set */ 138 139 static 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 *sender_key; 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 dict_errno = 0; 220 221 for (loop_count = 0, loop_max = addr_len + 100; /* void */ ; loop_count++) { 222 223 /* 224 * Grr. resolve_local() table lookups may fail. It may be OK for 225 * local file lookup code to abort upon failure, but with 226 * network-based tables it is preferable to return an error 227 * indication to the requestor. 228 */ 229 if (dict_errno) { 230 *flags |= RESOLVE_FLAG_FAIL; 231 FREE_MEMORY_AND_RETURN; 232 } 233 234 /* 235 * XXX Should never happen, but if this happens with some 236 * pathological address, then that is not sufficient reason to 237 * disrupt the operation of an MTA. 238 */ 239 if (loop_count > loop_max) { 240 msg_warn("resolve_addr: <%s>: giving up after %ld iterations", 241 addr, (long) loop_count); 242 break; 243 } 244 245 /* 246 * Strip trailing dot at end of domain, but not dot-dot or at-dot. 247 * This merely makes diagnostics more accurate by leaving bogus 248 * addresses alone. 249 */ 250 if (tree->tail 251 && tree->tail->type == '.' 252 && tok822_rfind_type(tree->tail, '@') != 0 253 && tree->tail->prev->type != '.' 254 && tree->tail->prev->type != '@') 255 tok822_free_tree(tok822_sub_keep_before(tree, tree->tail)); 256 257 /* 258 * Strip trailing @. 259 */ 260 if (var_resolve_nulldom 261 && tree->tail 262 && tree->tail->type == '@') 263 tok822_free_tree(tok822_sub_keep_before(tree, tree->tail)); 264 265 /* 266 * Strip (and save) @domain if local. 267 */ 268 if ((domain = tok822_rfind_type(tree->tail, '@')) != 0) { 269 if (domain->next && RESOLVE_LOCAL(domain->next) == 0) 270 break; 271 tok822_sub_keep_before(tree, domain); 272 if (saved_domain) 273 tok822_free_tree(saved_domain); 274 saved_domain = domain; 275 domain = 0; /* safety for future change */ 276 } 277 278 /* 279 * After stripping the local domain, if any, replace foo%bar by 280 * foo@bar, site!user by user@site, rewrite to canonical form, and 281 * retry. 282 */ 283 if (tok822_rfind_type(tree->tail, '@') 284 || (var_swap_bangpath && tok822_rfind_type(tree->tail, '!')) 285 || (var_percent_hack && tok822_rfind_type(tree->tail, '%'))) { 286 rewrite_tree(&local_context, tree); 287 continue; 288 } 289 290 /* 291 * If the local-part is a quoted string, crack it open when we're 292 * permitted to do so and look for routing operators. This is 293 * technically incorrect, but is needed to stop relaying problems. 294 * 295 * XXX Do another feeble attempt to keep local-part info quoted. 296 */ 297 if (var_resolve_dequoted 298 && tree->head && tree->head == tree->tail 299 && tree->head->type == TOK822_QSTRING 300 && ((oper = strrchr(local = STR(tree->head->vstr), '@')) != 0 301 || (var_percent_hack && (oper = strrchr(local, '%')) != 0) 302 || (var_swap_bangpath && (oper = strrchr(local, '!')) != 0))) { 303 if (*oper == '%') 304 *oper = '@'; 305 tok822_internalize(addr_buf, tree->head, TOK822_STR_DEFL); 306 if (*oper == '@') { 307 junk = mystrdup(STR(addr_buf)); 308 quote_822_local(addr_buf, junk); 309 myfree(junk); 310 } 311 tok822_free(tree->head); 312 tree->head = tok822_scan(STR(addr_buf), &tree->tail); 313 rewrite_tree(&local_context, tree); 314 continue; 315 } 316 317 /* 318 * An empty local-part or an empty quoted string local-part becomes 319 * the local MAILER-DAEMON, for consistency with our own From: 320 * message headers. 321 */ 322 if (tree->head && tree->head == tree->tail 323 && tree->head->type == TOK822_QSTRING 324 && VSTRING_LEN(tree->head->vstr) == 0) { 325 tok822_free(tree->head); 326 tree->head = 0; 327 } 328 /* XXX must be localpart only, not user@domain form. */ 329 if (tree->head == 0) 330 tree->head = tok822_scan(var_empty_addr, &tree->tail); 331 332 /* 333 * We're done. There are no domains left to strip off the address, 334 * and all null local-part information is sanitized. 335 */ 336 domain = 0; 337 break; 338 } 339 340 vstring_free(addr_buf); 341 addr_buf = 0; 342 343 /* 344 * Make sure the resolved envelope recipient has the user@domain form. If 345 * no domain was specified in the address, assume the local machine. See 346 * above for what happens with an empty address. 347 */ 348 if (domain == 0) { 349 if (saved_domain) { 350 tok822_sub_append(tree, saved_domain); 351 saved_domain = 0; 352 } else { 353 tok822_sub_append(tree, tok822_alloc('@', (char *) 0)); 354 tok822_sub_append(tree, tok822_scan(var_myhostname, (TOK822 **) 0)); 355 } 356 } 357 358 /* 359 * Transform the recipient address back to internal form. 360 * 361 * XXX This may produce incorrect results if we cracked open a quoted 362 * local-part with routing operators; see discussion above at the top of 363 * the big loop. 364 * 365 * XXX We explicitly disallow domain names in bare network address form. A 366 * network address destination should be formatted according to RFC 2821: 367 * it should be enclosed in [], and an IPv6 address should have an IPv6: 368 * prefix. 369 */ 370 tok822_internalize(nextrcpt, tree, TOK822_STR_DEFL); 371 rcpt_domain = strrchr(STR(nextrcpt), '@') + 1; 372 if (rcpt_domain == 0) 373 msg_panic("no @ in address: \"%s\"", STR(nextrcpt)); 374 if (*rcpt_domain == '[') { 375 if (!valid_mailhost_literal(rcpt_domain, DONT_GRIPE)) 376 *flags |= RESOLVE_FLAG_ERROR; 377 } else if (!valid_hostname(rcpt_domain, DONT_GRIPE)) { 378 if (var_resolve_num_dom && valid_hostaddr(rcpt_domain, DONT_GRIPE)) { 379 vstring_insert(nextrcpt, rcpt_domain - STR(nextrcpt), "[", 1); 380 vstring_strcat(nextrcpt, "]"); 381 rcpt_domain = strrchr(STR(nextrcpt), '@') + 1; 382 if (resolve_local(rcpt_domain)) /* XXX */ 383 domain = 0; 384 } else { 385 *flags |= RESOLVE_FLAG_ERROR; 386 } 387 } 388 tok822_free_tree(tree); 389 tree = 0; 390 391 /* 392 * XXX Short-cut invalid address forms. 393 */ 394 if (*flags & RESOLVE_FLAG_ERROR) { 395 *flags |= RESOLVE_CLASS_DEFAULT; 396 FREE_MEMORY_AND_RETURN; 397 } 398 399 /* 400 * Recognize routing operators in the local-part, even when we do not 401 * recognize ! or % as valid routing operators locally. This is needed to 402 * prevent backup MX hosts from relaying third-party destinations through 403 * primary MX hosts, otherwise the backup host could end up on black 404 * lists. Ignore local swap_bangpath and percent_hack settings because we 405 * can't know how the next MX host is set up. 406 */ 407 if (strcmp(STR(nextrcpt) + strcspn(STR(nextrcpt), "@!%") + 1, rcpt_domain)) 408 *flags |= RESOLVE_FLAG_ROUTED; 409 410 /* 411 * With local, virtual, relay, or other non-local destinations, give the 412 * highest precedence to transport associated nexthop information. 413 * 414 * Otherwise, with relay or other non-local destinations, the relayhost 415 * setting overrides the recipient domain name, and the sender-dependent 416 * relayhost overrides both. 417 * 418 * XXX Nag if the recipient domain is listed in multiple domain lists. The 419 * result is implementation defined, and may break when internals change. 420 * 421 * For now, we distinguish only a fixed number of address classes. 422 * Eventually this may become extensible, so that new classes can be 423 * configured with their own domain list, delivery transport, and 424 * recipient table. 425 */ 426 #define STREQ(x,y) (strcmp((x), (y)) == 0) 427 428 dict_errno = 0; 429 if (domain != 0) { 430 431 /* 432 * Virtual alias domain. 433 */ 434 if (virt_alias_doms 435 && string_list_match(virt_alias_doms, rcpt_domain)) { 436 if (var_helpful_warnings) { 437 if (virt_mailbox_doms 438 && string_list_match(virt_mailbox_doms, rcpt_domain)) 439 msg_warn("do not list domain %s in BOTH %s and %s", 440 rcpt_domain, VAR_VIRT_ALIAS_DOMS, 441 VAR_VIRT_MAILBOX_DOMS); 442 if (relay_domains 443 && domain_list_match(relay_domains, rcpt_domain)) 444 msg_warn("do not list domain %s in BOTH %s and %s", 445 rcpt_domain, VAR_VIRT_ALIAS_DOMS, 446 VAR_RELAY_DOMAINS); 447 #if 0 448 if (strcasecmp(rcpt_domain, var_myorigin) == 0) 449 msg_warn("do not list $%s (%s) in %s", 450 VAR_MYORIGIN, var_myorigin, VAR_VIRT_ALIAS_DOMS); 451 #endif 452 } 453 vstring_strcpy(channel, MAIL_SERVICE_ERROR); 454 vstring_sprintf(nexthop, "User unknown%s", 455 var_show_unk_rcpt_table ? 456 " in virtual alias table" : ""); 457 *flags |= RESOLVE_CLASS_ALIAS; 458 } else if (dict_errno != 0) { 459 msg_warn("%s lookup failure", VAR_VIRT_ALIAS_DOMS); 460 *flags |= RESOLVE_FLAG_FAIL; 461 FREE_MEMORY_AND_RETURN; 462 } 463 464 /* 465 * Virtual mailbox domain. 466 */ 467 else if (virt_mailbox_doms 468 && string_list_match(virt_mailbox_doms, rcpt_domain)) { 469 if (var_helpful_warnings) { 470 if (relay_domains 471 && domain_list_match(relay_domains, rcpt_domain)) 472 msg_warn("do not list domain %s in BOTH %s and %s", 473 rcpt_domain, VAR_VIRT_MAILBOX_DOMS, 474 VAR_RELAY_DOMAINS); 475 } 476 vstring_strcpy(channel, RES_PARAM_VALUE(rp->virt_transport)); 477 vstring_strcpy(nexthop, rcpt_domain); 478 blame = rp->virt_transport_name; 479 *flags |= RESOLVE_CLASS_VIRTUAL; 480 } else if (dict_errno != 0) { 481 msg_warn("%s lookup failure", VAR_VIRT_MAILBOX_DOMS); 482 *flags |= RESOLVE_FLAG_FAIL; 483 FREE_MEMORY_AND_RETURN; 484 } else { 485 486 /* 487 * Off-host relay destination. 488 */ 489 if (relay_domains 490 && domain_list_match(relay_domains, rcpt_domain)) { 491 vstring_strcpy(channel, RES_PARAM_VALUE(rp->relay_transport)); 492 blame = rp->relay_transport_name; 493 *flags |= RESOLVE_CLASS_RELAY; 494 } else if (dict_errno != 0) { 495 msg_warn("%s lookup failure", VAR_RELAY_DOMAINS); 496 *flags |= RESOLVE_FLAG_FAIL; 497 FREE_MEMORY_AND_RETURN; 498 } 499 500 /* 501 * Other off-host destination. 502 */ 503 else { 504 vstring_strcpy(channel, RES_PARAM_VALUE(rp->def_transport)); 505 blame = rp->def_transport_name; 506 *flags |= RESOLVE_CLASS_DEFAULT; 507 } 508 509 /* 510 * With off-host delivery, sender-dependent or global relayhost 511 * override the recipient domain. 512 */ 513 if (rp->snd_relay_info 514 && (relay = mail_addr_find(rp->snd_relay_info, 515 sender_key = (*sender ? sender : 516 var_null_relay_maps_key), 517 (char **) 0)) != 0) { 518 if (*relay == 0) { 519 msg_warn("%s: ignoring null lookup result for %s", 520 rp->snd_relay_maps_name, sender_key); 521 relay = "DUNNO"; 522 } 523 vstring_strcpy(nexthop, strcasecmp(relay, "DUNNO") == 0 ? 524 rcpt_domain : relay); 525 } else if (dict_errno != 0) { 526 msg_warn("%s lookup failure", rp->snd_relay_maps_name); 527 *flags |= RESOLVE_FLAG_FAIL; 528 FREE_MEMORY_AND_RETURN; 529 } else if (*RES_PARAM_VALUE(rp->relayhost)) 530 vstring_strcpy(nexthop, RES_PARAM_VALUE(rp->relayhost)); 531 else 532 vstring_strcpy(nexthop, rcpt_domain); 533 } 534 } 535 536 /* 537 * Local delivery. 538 * 539 * XXX Nag if the domain is listed in multiple domain lists. The effect is 540 * implementation defined, and may break when internals change. 541 */ 542 else { 543 if (var_helpful_warnings) { 544 if (virt_alias_doms 545 && string_list_match(virt_alias_doms, rcpt_domain)) 546 msg_warn("do not list domain %s in BOTH %s and %s", 547 rcpt_domain, VAR_MYDEST, VAR_VIRT_ALIAS_DOMS); 548 if (virt_mailbox_doms 549 && string_list_match(virt_mailbox_doms, rcpt_domain)) 550 msg_warn("do not list domain %s in BOTH %s and %s", 551 rcpt_domain, VAR_MYDEST, VAR_VIRT_MAILBOX_DOMS); 552 } 553 vstring_strcpy(channel, RES_PARAM_VALUE(rp->local_transport)); 554 vstring_strcpy(nexthop, rcpt_domain); 555 blame = rp->local_transport_name; 556 *flags |= RESOLVE_CLASS_LOCAL; 557 } 558 559 /* 560 * An explicit main.cf transport:nexthop setting overrides the nexthop. 561 * 562 * XXX We depend on this mechanism to enforce per-recipient concurrencies 563 * for local recipients. With "local_transport = local:$myhostname" we 564 * force mail for any domain in $mydestination/${proxy,inet}_interfaces 565 * to share the same queue. 566 */ 567 if ((destination = split_at(STR(channel), ':')) != 0 && *destination) 568 vstring_strcpy(nexthop, destination); 569 570 /* 571 * Sanity checks. 572 */ 573 if (*STR(channel) == 0) { 574 if (blame == 0) 575 msg_panic("%s: null blame", myname); 576 msg_warn("file %s/%s: parameter %s: null transport is not allowed", 577 var_config_dir, MAIN_CONF_FILE, blame); 578 *flags |= RESOLVE_FLAG_FAIL; 579 FREE_MEMORY_AND_RETURN; 580 } 581 if (*STR(nexthop) == 0) 582 msg_panic("%s: null nexthop", myname); 583 584 /* 585 * The transport map can selectively override any transport and/or 586 * nexthop host info that is set up above. Unfortunately, the syntax for 587 * nexthop information is transport specific. We therefore need sane and 588 * intuitive semantics for transport map entries that specify a channel 589 * but no nexthop. 590 * 591 * With non-error transports, the initial nexthop information is the 592 * recipient domain. However, specific main.cf transport definitions may 593 * specify a transport-specific destination, such as a host + TCP socket, 594 * or the pathname of a UNIX-domain socket. With less precedence than 595 * main.cf transport definitions, a main.cf relayhost definition may also 596 * override nexthop information for off-host deliveries. 597 * 598 * With the error transport, the nexthop information is free text that 599 * specifies the reason for non-delivery. 600 * 601 * Because nexthop syntax is transport specific we reset the nexthop 602 * information to the recipient domain when the transport table specifies 603 * a transport without also specifying the nexthop information. 604 * 605 * Subtle note: reset nexthop even when the transport table does not change 606 * the transport. Otherwise it is hard to get rid of main.cf specified 607 * nexthop information. 608 * 609 * XXX Don't override the virtual alias class (error:User unknown) result. 610 */ 611 if (rp->transport_info && !(*flags & RESOLVE_CLASS_ALIAS)) { 612 if (transport_lookup(rp->transport_info, STR(nextrcpt), 613 rcpt_domain, channel, nexthop) == 0 614 && dict_errno != 0) { 615 msg_warn("%s lookup failure", rp->transport_maps_name); 616 *flags |= RESOLVE_FLAG_FAIL; 617 FREE_MEMORY_AND_RETURN; 618 } 619 } 620 621 /* 622 * Bounce recipients that have moved, regardless of domain address class. 623 * We do this last, in anticipation of transport maps that can override 624 * the recipient address. 625 * 626 * The downside of not doing this in delivery agents is that this table has 627 * no effect on local alias expansion results. Such mail will have to 628 * make almost an entire iteration through the mail system. 629 */ 630 #define IGNORE_ADDR_EXTENSION ((char **) 0) 631 632 if (relocated_maps != 0) { 633 const char *newloc; 634 635 if ((newloc = mail_addr_find(relocated_maps, STR(nextrcpt), 636 IGNORE_ADDR_EXTENSION)) != 0) { 637 vstring_strcpy(channel, MAIL_SERVICE_ERROR); 638 /* 5.1.6 is the closest match, but not perfect. */ 639 vstring_sprintf(nexthop, "5.1.6 User has moved to %s", newloc); 640 } else if (dict_errno != 0) { 641 msg_warn("%s lookup failure", VAR_RELOCATED_MAPS); 642 *flags |= RESOLVE_FLAG_FAIL; 643 FREE_MEMORY_AND_RETURN; 644 } 645 } 646 647 /* 648 * Bounce recipient addresses that start with `-'. External commands may 649 * misinterpret such addresses as command-line options. 650 * 651 * In theory I could say people should always carefully set up their 652 * master.cf pipe mailer entries with `--' before the first non-option 653 * argument, but mistakes will happen regardless. 654 * 655 * Therefore the protection is put in place here, where it cannot be 656 * bypassed. 657 */ 658 if (var_allow_min_user == 0 && STR(nextrcpt)[0] == '-') { 659 *flags |= RESOLVE_FLAG_ERROR; 660 FREE_MEMORY_AND_RETURN; 661 } 662 663 /* 664 * Clean up. 665 */ 666 FREE_MEMORY_AND_RETURN; 667 } 668 669 /* Static, so they can be used by the network protocol interface only. */ 670 671 static VSTRING *channel; 672 static VSTRING *nexthop; 673 static VSTRING *nextrcpt; 674 static VSTRING *query; 675 static VSTRING *sender; 676 677 /* resolve_proto - read request and send reply */ 678 679 int resolve_proto(RES_CONTEXT *context, VSTREAM *stream) 680 { 681 int flags; 682 683 if (attr_scan(stream, ATTR_FLAG_STRICT, 684 ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender, 685 ATTR_TYPE_STR, MAIL_ATTR_ADDR, query, 686 ATTR_TYPE_END) != 2) 687 return (-1); 688 689 resolve_addr(context, STR(sender), STR(query), 690 channel, nexthop, nextrcpt, &flags); 691 692 if (msg_verbose) 693 msg_info("`%s' -> `%s' -> (`%s' `%s' `%s' `%d')", 694 STR(sender), STR(query), STR(channel), 695 STR(nexthop), STR(nextrcpt), flags); 696 697 attr_print(stream, ATTR_FLAG_NONE, 698 ATTR_TYPE_INT, MAIL_ATTR_FLAGS, server_flags, 699 ATTR_TYPE_STR, MAIL_ATTR_TRANSPORT, STR(channel), 700 ATTR_TYPE_STR, MAIL_ATTR_NEXTHOP, STR(nexthop), 701 ATTR_TYPE_STR, MAIL_ATTR_RECIP, STR(nextrcpt), 702 ATTR_TYPE_INT, MAIL_ATTR_FLAGS, flags, 703 ATTR_TYPE_END); 704 705 if (vstream_fflush(stream) != 0) { 706 msg_warn("write resolver reply: %m"); 707 return (-1); 708 } 709 return (0); 710 } 711 712 /* resolve_init - module initializations */ 713 714 void resolve_init(void) 715 { 716 sender = vstring_alloc(100); 717 query = vstring_alloc(100); 718 channel = vstring_alloc(100); 719 nexthop = vstring_alloc(100); 720 nextrcpt = vstring_alloc(100); 721 722 if (*var_virt_alias_doms) 723 virt_alias_doms = 724 string_list_init(MATCH_FLAG_NONE, var_virt_alias_doms); 725 726 if (*var_virt_mailbox_doms) 727 virt_mailbox_doms = 728 string_list_init(MATCH_FLAG_NONE, var_virt_mailbox_doms); 729 730 if (*var_relay_domains) 731 relay_domains = 732 domain_list_init(match_parent_style(VAR_RELAY_DOMAINS), 733 var_relay_domains); 734 735 if (*var_relocated_maps) 736 relocated_maps = 737 maps_create(VAR_RELOCATED_MAPS, var_relocated_maps, 738 DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); 739 } 740