1 /* $NetBSD: smtp_addr.c,v 1.1.1.2 2011/03/02 19:32:32 tron 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, 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 105 static 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 125 static 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 237 static 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 273 static 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 318 static 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 341 DNS_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 479 DNS_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