1 /* $NetBSD: smtp_addr.c,v 1.2 2017/02/14 01:16:48 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* smtp_addr 3 6 /* SUMMARY 7 /* SMTP server address lookup 8 /* SYNOPSIS 9 /* #include "smtp_addr.h" 10 /* 11 /* DNS_RR *smtp_domain_addr(name, mxrr, misc_flags, why, found_myself) 12 /* char *name; 13 /* DNS_RR **mxrr; 14 /* int misc_flags; 15 /* DSN_BUF *why; 16 /* int *found_myself; 17 /* 18 /* DNS_RR *smtp_host_addr(name, misc_flags, why) 19 /* char *name; 20 /* int misc_flags; 21 /* DSN_BUF *why; 22 /* DESCRIPTION 23 /* This module implements Internet address lookups. By default, 24 /* lookups are done via the Internet domain name service (DNS). 25 /* A reasonable number of CNAME indirections is permitted. When 26 /* DNS lookups are disabled, host address lookup is done with 27 /* getnameinfo() or gethostbyname(). 28 /* 29 /* smtp_domain_addr() looks up the network addresses for mail 30 /* exchanger hosts listed for the named domain. Addresses are 31 /* returned in most-preferred first order. The result is truncated 32 /* so that it contains only hosts that are more preferred than the 33 /* local mail server itself. The found_myself result parameter 34 /* is updated when the local MTA is MX host for the specified 35 /* destination. If MX records were found, the rname, qname, 36 /* and dnssec validation status of the MX RRset are returned 37 /* via mxrr, which the caller must free with dns_rr_free(). 38 /* 39 /* When no mail exchanger is listed in the DNS for \fIname\fR, the 40 /* request is passed to smtp_host_addr(). 41 /* 42 /* It is an error to call smtp_domain_addr() when DNS lookups are 43 /* disabled. 44 /* 45 /* smtp_host_addr() looks up all addresses listed for the named 46 /* host. The host can be specified as a numerical Internet network 47 /* address, or as a symbolic host name. 48 /* 49 /* Results from smtp_domain_addr() or smtp_host_addr() are 50 /* destroyed by dns_rr_free(), including null lists. 51 /* DIAGNOSTICS 52 /* Panics: interface violations. For example, calling smtp_domain_addr() 53 /* when DNS lookups are explicitly disabled. 54 /* 55 /* All routines either return a DNS_RR pointer, or return a null 56 /* pointer and update the \fIwhy\fR argument accordingly. 57 /* LICENSE 58 /* .ad 59 /* .fi 60 /* The Secure Mailer license must be distributed with this software. 61 /* AUTHOR(S) 62 /* Wietse Venema 63 /* IBM T.J. Watson Research 64 /* P.O. Box 704 65 /* Yorktown Heights, NY 10598, USA 66 /*--*/ 67 68 /* System library. */ 69 70 #include <sys_defs.h> 71 #include <sys/socket.h> 72 #include <netinet/in.h> 73 #include <arpa/inet.h> 74 #include <stdlib.h> 75 #include <netdb.h> 76 #include <ctype.h> 77 #include <string.h> 78 #include <unistd.h> 79 #include <errno.h> 80 81 /* Utility library. */ 82 83 #include <msg.h> 84 #include <vstring.h> 85 #include <mymalloc.h> 86 #include <inet_addr_list.h> 87 #include <stringops.h> 88 #include <myaddrinfo.h> 89 #include <inet_proto.h> 90 #include <midna_domain.h> 91 92 /* Global library. */ 93 94 #include <mail_params.h> 95 #include <own_inet_addr.h> 96 #include <dsn_buf.h> 97 98 /* DNS library. */ 99 100 #include <dns.h> 101 102 /* Application-specific. */ 103 104 #include "smtp.h" 105 #include "smtp_addr.h" 106 107 /* smtp_print_addr - print address list */ 108 109 static void smtp_print_addr(const char *what, DNS_RR *addr_list) 110 { 111 DNS_RR *addr; 112 MAI_HOSTADDR_STR hostaddr; 113 114 msg_info("begin %s address list", what); 115 for (addr = addr_list; addr; addr = addr->next) { 116 if (dns_rr_to_pa(addr, &hostaddr) == 0) { 117 msg_warn("skipping record type %s: %m", dns_strtype(addr->type)); 118 } else { 119 msg_info("pref %4d host %s/%s", 120 addr->pref, SMTP_HNAME(addr), 121 hostaddr.buf); 122 } 123 } 124 msg_info("end %s address list", what); 125 } 126 127 /* smtp_addr_one - address lookup for one host name */ 128 129 static DNS_RR *smtp_addr_one(DNS_RR *addr_list, const char *host, int res_opt, 130 unsigned pref, DSN_BUF *why) 131 { 132 const char *myname = "smtp_addr_one"; 133 DNS_RR *addr = 0; 134 DNS_RR *rr; 135 int aierr; 136 struct addrinfo *res0; 137 struct addrinfo *res; 138 INET_PROTO_INFO *proto_info = inet_proto_info(); 139 unsigned char *proto_family_list = proto_info->sa_family_list; 140 int found; 141 142 if (msg_verbose) 143 msg_info("%s: host %s", myname, host); 144 145 /* 146 * Interpret a numerical name as an address. 147 */ 148 if (hostaddr_to_sockaddr(host, (char *) 0, 0, &res0) == 0) { 149 if (strchr((char *) proto_family_list, res0->ai_family) != 0) { 150 if ((addr = dns_sa_to_rr(host, pref, res0->ai_addr)) == 0) 151 msg_fatal("host %s: conversion error for address family " 152 "%d: %m", host, res0->ai_addr->sa_family); 153 addr_list = dns_rr_append(addr_list, addr); 154 freeaddrinfo(res0); 155 return (addr_list); 156 } 157 freeaddrinfo(res0); 158 } 159 160 /* 161 * Use DNS lookup, but keep the option open to use native name service. 162 * 163 * XXX A soft error dominates past and future hard errors. Therefore we 164 * should not clobber a soft error text and status code. 165 */ 166 if (smtp_host_lookup_mask & SMTP_HOST_FLAG_DNS) { 167 res_opt |= smtp_dns_res_opt; 168 switch (dns_lookup_v(host, res_opt, &addr, (VSTRING *) 0, 169 why->reason, DNS_REQ_FLAG_NONE, 170 proto_info->dns_atype_list)) { 171 case DNS_OK: 172 for (rr = addr; rr; rr = rr->next) 173 rr->pref = pref; 174 addr_list = dns_rr_append(addr_list, addr); 175 return (addr_list); 176 default: 177 dsb_status(why, "4.4.3"); 178 return (addr_list); 179 case DNS_FAIL: 180 dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.3" : "5.4.3"); 181 return (addr_list); 182 case DNS_INVAL: 183 dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4"); 184 return (addr_list); 185 case DNS_POLICY: 186 dsb_status(why, "4.7.0"); 187 return (addr_list); 188 case DNS_NOTFOUND: 189 dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4"); 190 /* maybe native naming service will succeed */ 191 break; 192 } 193 } 194 195 /* 196 * Use the native name service which also looks in /etc/hosts. 197 * 198 * XXX A soft error dominates past and future hard errors. Therefore we 199 * should not clobber a soft error text and status code. 200 */ 201 #define RETRY_AI_ERROR(e) \ 202 ((e) == EAI_AGAIN || (e) == EAI_MEMORY || (e) == EAI_SYSTEM) 203 #ifdef EAI_NODATA 204 #define DSN_NOHOST(e) \ 205 ((e) == EAI_AGAIN || (e) == EAI_NODATA || (e) == EAI_NONAME) 206 #else 207 #define DSN_NOHOST(e) \ 208 ((e) == EAI_AGAIN || (e) == EAI_NONAME) 209 #endif 210 211 if (smtp_host_lookup_mask & SMTP_HOST_FLAG_NATIVE) { 212 if ((aierr = hostname_to_sockaddr(host, (char *) 0, 0, &res0)) != 0) { 213 dsb_simple(why, (SMTP_HAS_SOFT_DSN(why) || RETRY_AI_ERROR(aierr)) ? 214 (DSN_NOHOST(aierr) ? "4.4.4" : "4.3.0") : 215 (DSN_NOHOST(aierr) ? "5.4.4" : "5.3.0"), 216 "unable to look up host %s: %s", 217 host, MAI_STRERROR(aierr)); 218 } else { 219 for (found = 0, res = res0; res != 0; res = res->ai_next) { 220 if (strchr((char *) proto_family_list, res->ai_family) == 0) { 221 msg_info("skipping address family %d for host %s", 222 res->ai_family, host); 223 continue; 224 } 225 found++; 226 if ((addr = dns_sa_to_rr(host, pref, res->ai_addr)) == 0) 227 msg_fatal("host %s: conversion error for address family " 228 "%d: %m", host, res0->ai_addr->sa_family); 229 addr_list = dns_rr_append(addr_list, addr); 230 } 231 freeaddrinfo(res0); 232 if (found == 0) { 233 dsb_simple(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4", 234 "%s: host not found", host); 235 } 236 return (addr_list); 237 } 238 } 239 240 /* 241 * No further alternatives for host lookup. 242 */ 243 return (addr_list); 244 } 245 246 /* smtp_addr_list - address lookup for a list of mail exchangers */ 247 248 static DNS_RR *smtp_addr_list(DNS_RR *mx_names, DSN_BUF *why) 249 { 250 DNS_RR *addr_list = 0; 251 DNS_RR *rr; 252 int res_opt = 0; 253 254 if (mx_names->dnssec_valid) 255 res_opt = RES_USE_DNSSEC; 256 #ifdef USE_TLS 257 else if (smtp_tls_insecure_mx_policy > TLS_LEV_MAY) 258 res_opt = RES_USE_DNSSEC; 259 #endif 260 261 /* 262 * As long as we are able to look up any host address, we ignore problems 263 * with DNS lookups (except if we're backup MX, and all the better MX 264 * hosts can't be found). 265 * 266 * XXX 2821: update the error status (0->FAIL upon unrecoverable lookup 267 * error, any->RETRY upon temporary lookup error) so that we can 268 * correctly handle the case of no resolvable MX host. Currently this is 269 * always treated as a soft error. RFC 2821 wants a more precise 270 * response. 271 * 272 * XXX dns_lookup() enables RES_DEFNAMES. This is wrong for names found in 273 * MX records - we should not append the local domain to dot-less names. 274 * 275 * XXX However, this is not the only problem. If we use the native name 276 * service for host lookup, then it will usually enable RES_DNSRCH which 277 * appends local domain information to all lookups. In particular, 278 * getaddrinfo() may invoke a resolver that runs in a different process 279 * (NIS server, nscd), so we can't even reliably turn this off by 280 * tweaking the in-process resolver flags. 281 */ 282 for (rr = mx_names; rr; rr = rr->next) { 283 if (rr->type != T_MX) 284 msg_panic("smtp_addr_list: bad resource type: %d", rr->type); 285 addr_list = smtp_addr_one(addr_list, (char *) rr->data, res_opt, 286 rr->pref, why); 287 } 288 return (addr_list); 289 } 290 291 /* smtp_find_self - spot myself in a crowd of mail exchangers */ 292 293 static DNS_RR *smtp_find_self(DNS_RR *addr_list) 294 { 295 const char *myname = "smtp_find_self"; 296 INET_ADDR_LIST *self; 297 INET_ADDR_LIST *proxy; 298 DNS_RR *addr; 299 int i; 300 301 self = own_inet_addr_list(); 302 proxy = proxy_inet_addr_list(); 303 304 for (addr = addr_list; addr; addr = addr->next) { 305 306 /* 307 * Find out if this mail system is listening on this address. 308 */ 309 for (i = 0; i < self->used; i++) 310 if (DNS_RR_EQ_SA(addr, (struct sockaddr *) (self->addrs + i))) { 311 if (msg_verbose) 312 msg_info("%s: found self at pref %d", myname, addr->pref); 313 return (addr); 314 } 315 316 /* 317 * Find out if this mail system has a proxy listening on this 318 * address. 319 */ 320 for (i = 0; i < proxy->used; i++) 321 if (DNS_RR_EQ_SA(addr, (struct sockaddr *) (proxy->addrs + i))) { 322 if (msg_verbose) 323 msg_info("%s: found proxy at pref %d", myname, addr->pref); 324 return (addr); 325 } 326 } 327 328 /* 329 * Didn't find myself, or my proxy. 330 */ 331 if (msg_verbose) 332 msg_info("%s: not found", myname); 333 return (0); 334 } 335 336 /* smtp_truncate_self - truncate address list at self and equivalents */ 337 338 static DNS_RR *smtp_truncate_self(DNS_RR *addr_list, unsigned pref) 339 { 340 DNS_RR *addr; 341 DNS_RR *last; 342 343 for (last = 0, addr = addr_list; addr; last = addr, addr = addr->next) { 344 if (pref == addr->pref) { 345 if (msg_verbose) 346 smtp_print_addr("truncated", addr); 347 dns_rr_free(addr); 348 if (last == 0) { 349 addr_list = 0; 350 } else { 351 last->next = 0; 352 } 353 break; 354 } 355 } 356 return (addr_list); 357 } 358 359 /* smtp_domain_addr - mail exchanger address lookup */ 360 361 DNS_RR *smtp_domain_addr(const char *name, DNS_RR **mxrr, int misc_flags, 362 DSN_BUF *why, int *found_myself) 363 { 364 DNS_RR *mx_names; 365 DNS_RR *addr_list = 0; 366 DNS_RR *self = 0; 367 unsigned best_pref; 368 unsigned best_found; 369 int r = 0; /* Resolver flags */ 370 const char *aname; 371 372 dsb_reset(why); /* Paranoia */ 373 374 /* 375 * Preferences from DNS use 0..32767, fall-backs use 32768+. 376 */ 377 #define IMPOSSIBLE_PREFERENCE (~0) 378 379 /* 380 * Sanity check. 381 */ 382 if (smtp_dns_support == SMTP_DNS_DISABLED) 383 msg_panic("smtp_domain_addr: DNS lookup is disabled"); 384 if (smtp_dns_support == SMTP_DNS_DNSSEC) 385 r |= RES_USE_DNSSEC; 386 387 /* 388 * IDNA support. 389 */ 390 #ifndef NO_EAI 391 if (!allascii(name) && (aname = midna_domain_to_ascii(name)) != 0) { 392 if (msg_verbose) 393 msg_info("%s asciified to %s", name, aname); 394 } else 395 #endif 396 aname = name; 397 398 /* 399 * Look up the mail exchanger hosts listed for this name. Sort the 400 * results by preference. Look up the corresponding host addresses, and 401 * truncate the list so that it contains only hosts that are more 402 * preferred than myself. When no MX resource records exist, look up the 403 * addresses listed for this name. 404 * 405 * According to RFC 974: "It is possible that the list of MXs in the 406 * response to the query will be empty. This is a special case. If the 407 * list is empty, mailers should treat it as if it contained one RR, an 408 * MX RR with a preference value of 0, and a host name of REMOTE. (I.e., 409 * REMOTE is its only MX). In addition, the mailer should do no further 410 * processing on the list, but should attempt to deliver the message to 411 * REMOTE." 412 * 413 * Normally it is OK if an MX host cannot be found in the DNS; we'll just 414 * use a backup one, and silently ignore the better MX host. However, if 415 * the best backup that we can find in the DNS is the local machine, then 416 * we must remember that the local machine is not the primary MX host, or 417 * else we will claim that mail loops back. 418 * 419 * XXX Optionally do A lookups even when the MX lookup didn't complete. 420 * Unfortunately with some DNS servers this is not a transient problem. 421 * 422 * XXX Ideally we would perform A lookups only as far as needed. But as long 423 * as we're looking up all the hosts, it would be better to look up the 424 * least preferred host first, so that DNS lookup error messages make 425 * more sense. 426 * 427 * XXX 2821: RFC 2821 says that the sender must shuffle equal-preference MX 428 * hosts, whereas multiple A records per hostname must be used in the 429 * order as received. They make the bogus assumption that a hostname with 430 * multiple A records corresponds to one machine with multiple network 431 * interfaces. 432 * 433 * XXX 2821: Postfix recognizes the local machine by looking for its own IP 434 * address in the list of mail exchangers. RFC 2821 says one has to look 435 * at the mail exchanger hostname as well, making the bogus assumption 436 * that an IP address is listed only under one hostname. However, looking 437 * at hostnames provides a partial solution for MX hosts behind a NAT 438 * gateway. 439 */ 440 switch (dns_lookup(aname, T_MX, r, &mx_names, (VSTRING *) 0, why->reason)) { 441 default: 442 dsb_status(why, "4.4.3"); 443 if (var_ign_mx_lookup_err) 444 addr_list = smtp_host_addr(aname, misc_flags, why); 445 break; 446 case DNS_INVAL: 447 dsb_status(why, "5.4.4"); 448 if (var_ign_mx_lookup_err) 449 addr_list = smtp_host_addr(aname, misc_flags, why); 450 break; 451 case DNS_NULLMX: 452 dsb_status(why, "5.1.0"); 453 break; 454 case DNS_POLICY: 455 dsb_status(why, "4.7.0"); 456 break; 457 case DNS_FAIL: 458 dsb_status(why, "5.4.3"); 459 if (var_ign_mx_lookup_err) 460 addr_list = smtp_host_addr(aname, misc_flags, why); 461 break; 462 case DNS_OK: 463 mx_names = dns_rr_sort(mx_names, dns_rr_compare_pref_any); 464 best_pref = (mx_names ? mx_names->pref : IMPOSSIBLE_PREFERENCE); 465 addr_list = smtp_addr_list(mx_names, why); 466 if (mxrr) 467 *mxrr = dns_rr_copy(mx_names); /* copies one record! */ 468 dns_rr_free(mx_names); 469 if (addr_list == 0) { 470 /* Text does not change. */ 471 if (var_smtp_defer_mxaddr) { 472 /* Don't clobber the null terminator. */ 473 if (SMTP_HAS_HARD_DSN(why)) 474 SMTP_SET_SOFT_DSN(why); /* XXX */ 475 /* Require some error status. */ 476 else if (!SMTP_HAS_SOFT_DSN(why)) 477 msg_panic("smtp_domain_addr: bad status"); 478 } 479 msg_warn("no MX host for %s has a valid address record", name); 480 break; 481 } 482 best_found = (addr_list ? addr_list->pref : IMPOSSIBLE_PREFERENCE); 483 if (msg_verbose) 484 smtp_print_addr(name, addr_list); 485 if ((misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) 486 && (self = smtp_find_self(addr_list)) != 0) { 487 addr_list = smtp_truncate_self(addr_list, self->pref); 488 if (addr_list == 0) { 489 if (best_pref != best_found) { 490 dsb_simple(why, "4.4.4", 491 "unable to find primary relay for %s", name); 492 } else { 493 dsb_simple(why, "5.4.6", "mail for %s loops back to myself", 494 name); 495 } 496 } 497 } 498 #define SMTP_COMPARE_ADDR(flags) \ 499 (((flags) & SMTP_MISC_FLAG_PREF_IPV6) ? dns_rr_compare_pref_ipv6 : \ 500 ((flags) & SMTP_MISC_FLAG_PREF_IPV4) ? dns_rr_compare_pref_ipv4 : \ 501 dns_rr_compare_pref_any) 502 503 if (addr_list && addr_list->next && var_smtp_rand_addr) { 504 addr_list = dns_rr_shuffle(addr_list); 505 addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags)); 506 } 507 break; 508 case DNS_NOTFOUND: 509 addr_list = smtp_host_addr(aname, misc_flags, why); 510 break; 511 } 512 513 /* 514 * Clean up. 515 */ 516 *found_myself |= (self != 0); 517 return (addr_list); 518 } 519 520 /* smtp_host_addr - direct host lookup */ 521 522 DNS_RR *smtp_host_addr(const char *host, int misc_flags, DSN_BUF *why) 523 { 524 DNS_RR *addr_list; 525 int res_opt = 0; 526 const char *ahost; 527 528 dsb_reset(why); /* Paranoia */ 529 530 if (smtp_dns_support == SMTP_DNS_DNSSEC) 531 res_opt |= RES_USE_DNSSEC; 532 533 /* 534 * IDNA support. 535 */ 536 #ifndef NO_EAI 537 if (!allascii(host) && (ahost = midna_domain_to_ascii(host)) != 0) { 538 if (msg_verbose) 539 msg_info("%s asciified to %s", host, ahost); 540 } else 541 #endif 542 ahost = host; 543 544 /* 545 * If the host is specified by numerical address, just convert the 546 * address to internal form. Otherwise, the host is specified by name. 547 */ 548 #define PREF0 0 549 addr_list = smtp_addr_one((DNS_RR *) 0, ahost, res_opt, PREF0, why); 550 if (addr_list 551 && (misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) 552 && smtp_find_self(addr_list) != 0) { 553 dns_rr_free(addr_list); 554 dsb_simple(why, "5.4.6", "mail for %s loops back to myself", host); 555 return (0); 556 } 557 if (addr_list && addr_list->next) { 558 if (var_smtp_rand_addr) 559 addr_list = dns_rr_shuffle(addr_list); 560 /* The following changes the order of equal-preference hosts. */ 561 if (inet_proto_info()->ai_family_list[1] != 0) 562 addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags)); 563 } 564 if (msg_verbose) 565 smtp_print_addr(host, addr_list); 566 return (addr_list); 567 } 568