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