1 /* 2 * Copyright (c) 1998-2001 Sendmail, Inc. and its suppliers. 3 * All rights reserved. 4 * Copyright (c) 1986, 1995-1997 Eric P. Allman. All rights reserved. 5 * Copyright (c) 1988, 1993 6 * The Regents of the University of California. All rights reserved. 7 * 8 * By using this file, you agree to the terms and conditions set 9 * forth in the LICENSE file which can be found at the top level of 10 * the sendmail distribution. 11 * 12 */ 13 14 #include <sendmail.h> 15 16 #if NAMED_BIND 17 SM_RCSID("@(#)$Id: domain.c,v 8.177 2001/12/12 01:16:15 ca Exp $ (with name server)") 18 #else /* NAMED_BIND */ 19 SM_RCSID("@(#)$Id: domain.c,v 8.177 2001/12/12 01:16:15 ca Exp $ (without name server)") 20 #endif /* NAMED_BIND */ 21 22 #if NAMED_BIND 23 24 # include <arpa/inet.h> 25 26 27 /* 28 ** The standard udp packet size PACKETSZ (512) is not sufficient for some 29 ** nameserver answers containing very many resource records. The resolver 30 ** may switch to tcp and retry if it detects udp packet overflow. 31 ** Also note that the resolver routines res_query and res_search return 32 ** the size of the *un*truncated answer in case the supplied answer buffer 33 ** it not big enough to accommodate the entire answer. 34 */ 35 36 # ifndef MAXPACKET 37 # define MAXPACKET 8192 /* max packet size used internally by BIND */ 38 # endif /* ! MAXPACKET */ 39 40 typedef union 41 { 42 HEADER qb1; 43 unsigned char qb2[MAXPACKET]; 44 } querybuf; 45 46 # ifndef MXHOSTBUFSIZE 47 # define MXHOSTBUFSIZE (128 * MAXMXHOSTS) 48 # endif /* ! MXHOSTBUFSIZE */ 49 50 static char MXHostBuf[MXHOSTBUFSIZE]; 51 #if (MXHOSTBUFSIZE < 2) || (MXHOSTBUFSIZE >= INT_MAX/2) 52 ERROR: _MXHOSTBUFSIZE is out of range 53 #endif /* (MXHOSTBUFSIZE < 2) || (MXHOSTBUFSIZE >= INT_MAX/2) */ 54 55 # ifndef MAXDNSRCH 56 # define MAXDNSRCH 6 /* number of possible domains to search */ 57 # endif /* ! MAXDNSRCH */ 58 59 # ifndef RES_DNSRCH_VARIABLE 60 # define RES_DNSRCH_VARIABLE _res.dnsrch 61 # endif /* ! RES_DNSRCH_VARIABLE */ 62 63 # ifndef NO_DATA 64 # define NO_DATA NO_ADDRESS 65 # endif /* ! NO_DATA */ 66 67 # ifndef HFIXEDSZ 68 # define HFIXEDSZ 12 /* sizeof(HEADER) */ 69 # endif /* ! HFIXEDSZ */ 70 71 # define MAXCNAMEDEPTH 10 /* maximum depth of CNAME recursion */ 72 73 # if defined(__RES) && (__RES >= 19940415) 74 # define RES_UNC_T char * 75 # else /* defined(__RES) && (__RES >= 19940415) */ 76 # define RES_UNC_T unsigned char * 77 # endif /* defined(__RES) && (__RES >= 19940415) */ 78 79 static char *gethostalias __P((char *)); 80 static int mxrand __P((char *)); 81 static int fallbackmxrr __P((int, unsigned short *, char **)); 82 83 /* 84 ** GETFALLBACKMXRR -- get MX resource records for fallback MX host. 85 ** 86 ** We have to initialize this once before doing anything else. 87 ** Moreover, we have to repeat this from time to time to avoid 88 ** stale data, e.g., in persistent queue runners. 89 ** This should be done in a parent process so the child 90 ** processes have the right data. 91 ** 92 ** Parameters: 93 ** host -- the name of the fallback MX host. 94 ** 95 ** Returns: 96 ** number of MX records. 97 ** 98 ** Side Effects: 99 ** Populates NumFallBackMXHosts and fbhosts. 100 ** Sets renewal time (based on TTL). 101 */ 102 103 int NumFallBackMXHosts = 0; /* Number of fallback MX hosts (after MX expansion) */ 104 static char *fbhosts[MAXMXHOSTS + 1]; 105 106 int 107 getfallbackmxrr(host) 108 char *host; 109 { 110 int i, rcode; 111 int ttl; 112 static time_t renew = 0; 113 114 #if 0 115 /* This is currently done before this function is called. */ 116 if (host == NULL || *host == '\0') 117 return 0; 118 #endif /* 0 */ 119 if (NumFallBackMXHosts > 0 && renew > curtime()) 120 return NumFallBackMXHosts; 121 if (host[0] == '[') 122 { 123 fbhosts[0] = host; 124 NumFallBackMXHosts = 1; 125 } 126 else 127 { 128 /* free old data */ 129 for (i = 0; i < NumFallBackMXHosts; i++) 130 sm_free(fbhosts[i]); 131 132 /* get new data */ 133 NumFallBackMXHosts = getmxrr(host, fbhosts, NULL, false, 134 &rcode, false, &ttl); 135 renew = curtime() + ttl; 136 for (i = 0; i < NumFallBackMXHosts; i++) 137 fbhosts[i] = newstr(fbhosts[i]); 138 } 139 return NumFallBackMXHosts; 140 } 141 142 /* 143 ** FALLBACKMXRR -- add MX resource records for fallback MX host to list. 144 ** 145 ** Parameters: 146 ** nmx -- current number of MX records. 147 ** prefs -- array of preferences. 148 ** mxhosts -- array of MX hosts (maximum size: MAXMXHOSTS) 149 ** 150 ** Returns: 151 ** new number of MX records. 152 ** 153 ** Side Effects: 154 ** If FallBackMX was set, it appends the MX records for 155 ** that host to mxhosts (and modifies prefs accordingly). 156 */ 157 158 static int 159 fallbackmxrr(nmx, prefs, mxhosts) 160 int nmx; 161 unsigned short *prefs; 162 char **mxhosts; 163 { 164 int i; 165 166 for (i = 0; i < NumFallBackMXHosts && nmx < MAXMXHOSTS; i++) 167 { 168 if (nmx > 0) 169 prefs[nmx] = prefs[nmx - 1] + 1; 170 else 171 prefs[nmx] = 0; 172 mxhosts[nmx++] = fbhosts[i]; 173 } 174 return nmx; 175 } 176 177 /* 178 ** GETMXRR -- get MX resource records for a domain 179 ** 180 ** Parameters: 181 ** host -- the name of the host to MX. 182 ** mxhosts -- a pointer to a return buffer of MX records. 183 ** mxprefs -- a pointer to a return buffer of MX preferences. 184 ** If NULL, don't try to populate. 185 ** droplocalhost -- If true, all MX records less preferred 186 ** than the local host (as determined by $=w) will 187 ** be discarded. 188 ** rcode -- a pointer to an EX_ status code. 189 ** tryfallback -- add also fallback MX host? 190 ** pttl -- pointer to return TTL (can be NULL). 191 ** 192 ** Returns: 193 ** The number of MX records found. 194 ** -1 if there is an internal failure. 195 ** If no MX records are found, mxhosts[0] is set to host 196 ** and 1 is returned. 197 ** 198 ** Side Effects: 199 ** The entries made for mxhosts point to a static array 200 ** MXHostBuf[MXHOSTBUFSIZE], so the data needs to be copied, 201 ** if it must be preserved across calls to this function. 202 */ 203 204 int 205 getmxrr(host, mxhosts, mxprefs, droplocalhost, rcode, tryfallback, pttl) 206 char *host; 207 char **mxhosts; 208 unsigned short *mxprefs; 209 bool droplocalhost; 210 int *rcode; 211 bool tryfallback; 212 int *pttl; 213 { 214 register unsigned char *eom, *cp; 215 register int i, j, n; 216 int nmx = 0; 217 register char *bp; 218 HEADER *hp; 219 querybuf answer; 220 int ancount, qdcount, buflen; 221 bool seenlocal = false; 222 unsigned short pref, type; 223 unsigned short localpref = 256; 224 char *fallbackMX = FallBackMX; 225 bool trycanon = false; 226 unsigned short *prefs; 227 int (*resfunc)(); 228 unsigned short prefer[MAXMXHOSTS]; 229 int weight[MAXMXHOSTS]; 230 int ttl = 0; 231 extern int res_query(), res_search(); 232 233 if (tTd(8, 2)) 234 sm_dprintf("getmxrr(%s, droplocalhost=%d)\n", 235 host, droplocalhost); 236 237 if ((fallbackMX != NULL && droplocalhost && 238 wordinclass(fallbackMX, 'w')) || !tryfallback) 239 { 240 /* don't use fallback for this pass */ 241 fallbackMX = NULL; 242 } 243 244 *rcode = EX_OK; 245 246 if (mxprefs != NULL) 247 prefs = mxprefs; 248 else 249 prefs = prefer; 250 251 /* efficiency hack -- numeric or non-MX lookups */ 252 if (host[0] == '[') 253 goto punt; 254 255 /* 256 ** If we don't have MX records in our host switch, don't 257 ** try for MX records. Note that this really isn't "right", 258 ** since we might be set up to try NIS first and then DNS; 259 ** if the host is found in NIS we really shouldn't be doing 260 ** MX lookups. However, that should be a degenerate case. 261 */ 262 263 if (!UseNameServer) 264 goto punt; 265 if (HasWildcardMX && ConfigLevel >= 6) 266 resfunc = res_query; 267 else 268 resfunc = res_search; 269 270 errno = 0; 271 n = (*resfunc)(host, C_IN, T_MX, (unsigned char *) &answer, 272 sizeof(answer)); 273 if (n < 0) 274 { 275 if (tTd(8, 1)) 276 sm_dprintf("getmxrr: res_search(%s) failed (errno=%d, h_errno=%d)\n", 277 host == NULL ? "<NULL>" : host, errno, h_errno); 278 switch (h_errno) 279 { 280 case NO_DATA: 281 trycanon = true; 282 /* FALLTHROUGH */ 283 284 case NO_RECOVERY: 285 /* no MX data on this host */ 286 goto punt; 287 288 case HOST_NOT_FOUND: 289 # if BROKEN_RES_SEARCH 290 case 0: /* Ultrix resolver retns failure w/ h_errno=0 */ 291 # endif /* BROKEN_RES_SEARCH */ 292 /* host doesn't exist in DNS; might be in /etc/hosts */ 293 trycanon = true; 294 *rcode = EX_NOHOST; 295 goto punt; 296 297 case TRY_AGAIN: 298 case -1: 299 /* couldn't connect to the name server */ 300 if (fallbackMX != NULL) 301 { 302 /* name server is hosed -- push to fallback */ 303 return fallbackmxrr(nmx, prefs, mxhosts); 304 } 305 /* it might come up later; better queue it up */ 306 *rcode = EX_TEMPFAIL; 307 break; 308 309 default: 310 syserr("getmxrr: res_search (%s) failed with impossible h_errno (%d)", 311 host, h_errno); 312 *rcode = EX_OSERR; 313 break; 314 } 315 316 /* irreconcilable differences */ 317 return -1; 318 } 319 320 /* avoid problems after truncation in tcp packets */ 321 if (n > sizeof(answer)) 322 n = sizeof(answer); 323 324 /* find first satisfactory answer */ 325 hp = (HEADER *)&answer; 326 cp = (unsigned char *)&answer + HFIXEDSZ; 327 eom = (unsigned char *)&answer + n; 328 for (qdcount = ntohs((unsigned short) hp->qdcount); 329 qdcount--; 330 cp += n + QFIXEDSZ) 331 { 332 if ((n = dn_skipname(cp, eom)) < 0) 333 goto punt; 334 } 335 336 /* NOTE: see definition of MXHostBuf! */ 337 buflen = sizeof(MXHostBuf) - 1; 338 SM_ASSERT(buflen > 0); 339 bp = MXHostBuf; 340 ancount = ntohs((unsigned short) hp->ancount); 341 342 /* See RFC 1035 for layout of RRs. */ 343 /* XXX leave room for FallBackMX ? */ 344 while (--ancount >= 0 && cp < eom && nmx < MAXMXHOSTS - 1) 345 { 346 if ((n = dn_expand((unsigned char *)&answer, eom, cp, 347 (RES_UNC_T) bp, buflen)) < 0) 348 break; 349 cp += n; 350 GETSHORT(type, cp); 351 cp += INT16SZ; /* skip over class */ 352 GETLONG(ttl, cp); 353 GETSHORT(n, cp); /* rdlength */ 354 if (type != T_MX) 355 { 356 if (tTd(8, 8) || _res.options & RES_DEBUG) 357 sm_dprintf("unexpected answer type %d, size %d\n", 358 type, n); 359 cp += n; 360 continue; 361 } 362 GETSHORT(pref, cp); 363 if ((n = dn_expand((unsigned char *)&answer, eom, cp, 364 (RES_UNC_T) bp, buflen)) < 0) 365 break; 366 cp += n; 367 n = strlen(bp); 368 # if 0 369 /* Can this happen? */ 370 if (n == 0) 371 { 372 if (LogLevel > 4) 373 sm_syslog(LOG_ERR, NOQID, 374 "MX records for %s contain empty string", 375 host); 376 continue; 377 } 378 # endif /* 0 */ 379 if (wordinclass(bp, 'w')) 380 { 381 if (tTd(8, 3)) 382 sm_dprintf("found localhost (%s) in MX list, pref=%d\n", 383 bp, pref); 384 if (droplocalhost) 385 { 386 if (!seenlocal || pref < localpref) 387 localpref = pref; 388 seenlocal = true; 389 continue; 390 } 391 weight[nmx] = 0; 392 } 393 else 394 weight[nmx] = mxrand(bp); 395 prefs[nmx] = pref; 396 mxhosts[nmx++] = bp; 397 bp += n; 398 if (bp[-1] != '.') 399 { 400 *bp++ = '.'; 401 n++; 402 } 403 *bp++ = '\0'; 404 if (buflen < n + 1) 405 { 406 /* don't want to wrap buflen */ 407 break; 408 } 409 buflen -= n + 1; 410 } 411 412 /* return only one TTL entry, that should be sufficient */ 413 if (ttl > 0 && pttl != NULL) 414 *pttl = ttl; 415 416 /* sort the records */ 417 for (i = 0; i < nmx; i++) 418 { 419 for (j = i + 1; j < nmx; j++) 420 { 421 if (prefs[i] > prefs[j] || 422 (prefs[i] == prefs[j] && weight[i] > weight[j])) 423 { 424 register int temp; 425 register char *temp1; 426 427 temp = prefs[i]; 428 prefs[i] = prefs[j]; 429 prefs[j] = temp; 430 temp1 = mxhosts[i]; 431 mxhosts[i] = mxhosts[j]; 432 mxhosts[j] = temp1; 433 temp = weight[i]; 434 weight[i] = weight[j]; 435 weight[j] = temp; 436 } 437 } 438 if (seenlocal && prefs[i] >= localpref) 439 { 440 /* truncate higher preference part of list */ 441 nmx = i; 442 } 443 } 444 445 /* delete duplicates from list (yes, some bozos have duplicates) */ 446 for (i = 0; i < nmx - 1; ) 447 { 448 if (sm_strcasecmp(mxhosts[i], mxhosts[i + 1]) != 0) 449 i++; 450 else 451 { 452 /* compress out duplicate */ 453 for (j = i + 1; j < nmx; j++) 454 { 455 mxhosts[j] = mxhosts[j + 1]; 456 prefs[j] = prefs[j + 1]; 457 } 458 nmx--; 459 } 460 } 461 462 if (nmx == 0) 463 { 464 punt: 465 if (seenlocal) 466 { 467 struct hostent *h = NULL; 468 469 /* 470 ** If we have deleted all MX entries, this is 471 ** an error -- we should NEVER send to a host that 472 ** has an MX, and this should have been caught 473 ** earlier in the config file. 474 ** 475 ** Some sites prefer to go ahead and try the 476 ** A record anyway; that case is handled by 477 ** setting TryNullMXList. I believe this is a 478 ** bad idea, but it's up to you.... 479 */ 480 481 if (TryNullMXList) 482 { 483 SM_SET_H_ERRNO(0); 484 errno = 0; 485 h = sm_gethostbyname(host, AF_INET); 486 if (h == NULL) 487 { 488 if (errno == ETIMEDOUT || 489 h_errno == TRY_AGAIN || 490 (errno == ECONNREFUSED && 491 UseNameServer)) 492 { 493 *rcode = EX_TEMPFAIL; 494 return -1; 495 } 496 # if NETINET6 497 SM_SET_H_ERRNO(0); 498 errno = 0; 499 h = sm_gethostbyname(host, AF_INET6); 500 if (h == NULL && 501 (errno == ETIMEDOUT || 502 h_errno == TRY_AGAIN || 503 (errno == ECONNREFUSED && 504 UseNameServer))) 505 { 506 *rcode = EX_TEMPFAIL; 507 return -1; 508 } 509 # endif /* NETINET6 */ 510 } 511 } 512 513 if (h == NULL) 514 { 515 *rcode = EX_CONFIG; 516 syserr("MX list for %s points back to %s", 517 host, MyHostName); 518 return -1; 519 } 520 # if NETINET6 521 freehostent(h); 522 hp = NULL; 523 # endif /* NETINET6 */ 524 } 525 if (strlen(host) >= sizeof MXHostBuf) 526 { 527 *rcode = EX_CONFIG; 528 syserr("Host name %s too long", 529 shortenstring(host, MAXSHORTSTR)); 530 return -1; 531 } 532 (void) sm_strlcpy(MXHostBuf, host, sizeof MXHostBuf); 533 mxhosts[0] = MXHostBuf; 534 prefs[0] = 0; 535 if (host[0] == '[') 536 { 537 register char *p; 538 # if NETINET6 539 struct sockaddr_in6 tmp6; 540 # endif /* NETINET6 */ 541 542 /* this may be an MX suppression-style address */ 543 p = strchr(MXHostBuf, ']'); 544 if (p != NULL) 545 { 546 *p = '\0'; 547 548 if (inet_addr(&MXHostBuf[1]) != INADDR_NONE) 549 { 550 nmx++; 551 *p = ']'; 552 } 553 # if NETINET6 554 else if (anynet_pton(AF_INET6, &MXHostBuf[1], 555 &tmp6.sin6_addr) == 1) 556 { 557 nmx++; 558 *p = ']'; 559 } 560 # endif /* NETINET6 */ 561 else 562 { 563 trycanon = true; 564 mxhosts[0]++; 565 } 566 } 567 } 568 if (trycanon && 569 getcanonname(mxhosts[0], sizeof MXHostBuf - 2, false, pttl)) 570 { 571 /* XXX MXHostBuf == "" ? is that possible? */ 572 bp = &MXHostBuf[strlen(MXHostBuf)]; 573 if (bp[-1] != '.') 574 { 575 *bp++ = '.'; 576 *bp = '\0'; 577 } 578 nmx = 1; 579 } 580 } 581 582 /* if we have a default lowest preference, include that */ 583 if (fallbackMX != NULL && !seenlocal) 584 { 585 nmx = fallbackmxrr(nmx, prefs, mxhosts); 586 } 587 return nmx; 588 } 589 /* 590 ** MXRAND -- create a randomizer for equal MX preferences 591 ** 592 ** If two MX hosts have equal preferences we want to randomize 593 ** the selection. But in order for signatures to be the same, 594 ** we need to randomize the same way each time. This function 595 ** computes a pseudo-random hash function from the host name. 596 ** 597 ** Parameters: 598 ** host -- the name of the host. 599 ** 600 ** Returns: 601 ** A random but repeatable value based on the host name. 602 */ 603 604 static int 605 mxrand(host) 606 register char *host; 607 { 608 int hfunc; 609 static unsigned int seed; 610 611 if (seed == 0) 612 { 613 seed = (int) curtime() & 0xffff; 614 if (seed == 0) 615 seed++; 616 } 617 618 if (tTd(17, 9)) 619 sm_dprintf("mxrand(%s)", host); 620 621 hfunc = seed; 622 while (*host != '\0') 623 { 624 int c = *host++; 625 626 if (isascii(c) && isupper(c)) 627 c = tolower(c); 628 hfunc = ((hfunc << 1) ^ c) % 2003; 629 } 630 631 hfunc &= 0xff; 632 hfunc++; 633 634 if (tTd(17, 9)) 635 sm_dprintf(" = %d\n", hfunc); 636 return hfunc; 637 } 638 /* 639 ** BESTMX -- find the best MX for a name 640 ** 641 ** This is really a hack, but I don't see any obvious way 642 ** to generalize it at the moment. 643 */ 644 645 /* ARGSUSED3 */ 646 char * 647 bestmx_map_lookup(map, name, av, statp) 648 MAP *map; 649 char *name; 650 char **av; 651 int *statp; 652 { 653 int nmx; 654 int saveopts = _res.options; 655 int i; 656 ssize_t len = 0; 657 char *result; 658 char *mxhosts[MAXMXHOSTS + 1]; 659 #if _FFR_BESTMX_BETTER_TRUNCATION 660 char *buf; 661 #else /* _FFR_BESTMX_BETTER_TRUNCATION */ 662 char *p; 663 char buf[PSBUFSIZE / 2]; 664 #endif /* _FFR_BESTMX_BETTER_TRUNCATION */ 665 666 _res.options &= ~(RES_DNSRCH|RES_DEFNAMES); 667 nmx = getmxrr(name, mxhosts, NULL, false, statp, true, NULL); 668 _res.options = saveopts; 669 if (nmx <= 0) 670 return NULL; 671 if (bitset(MF_MATCHONLY, map->map_mflags)) 672 return map_rewrite(map, name, strlen(name), NULL); 673 if ((map->map_coldelim == '\0') || (nmx == 1)) 674 return map_rewrite(map, mxhosts[0], strlen(mxhosts[0]), av); 675 676 /* 677 ** We were given a -z flag (return all MXs) and there are multiple 678 ** ones. We need to build them all into a list. 679 */ 680 681 #if _FFR_BESTMX_BETTER_TRUNCATION 682 for (i = 0; i < nmx; i++) 683 { 684 if (strchr(mxhosts[i], map->map_coldelim) != NULL) 685 { 686 syserr("bestmx_map_lookup: MX host %.64s includes map delimiter character 0x%02X", 687 mxhosts[i], map->map_coldelim); 688 return NULL; 689 } 690 len += strlen(mxhosts[i]) + 1; 691 if (len < 0) 692 { 693 len -= strlen(mxhosts[i]) + 1; 694 break; 695 } 696 } 697 buf = (char *) sm_malloc(len); 698 if (buf == NULL) 699 { 700 *statp = EX_UNAVAILABLE; 701 return NULL; 702 } 703 *buf = '\0'; 704 for (i = 0; i < nmx; i++) 705 { 706 int end; 707 708 end = sm_strlcat(buf, mxhosts[i], len); 709 if (i != nmx && end + 1 < len) 710 { 711 buf[end] = map->map_coldelim; 712 buf[end + 1] = '\0'; 713 } 714 } 715 716 /* Cleanly truncate for rulesets */ 717 truncate_at_delim(buf, PSBUFSIZE / 2, map->map_coldelim); 718 #else /* _FFR_BESTMX_BETTER_TRUNCATION */ 719 p = buf; 720 for (i = 0; i < nmx; i++) 721 { 722 size_t slen; 723 724 if (strchr(mxhosts[i], map->map_coldelim) != NULL) 725 { 726 syserr("bestmx_map_lookup: MX host %.64s includes map delimiter character 0x%02X", 727 mxhosts[i], map->map_coldelim); 728 return NULL; 729 } 730 slen = strlen(mxhosts[i]); 731 if (len + slen + 2 > sizeof buf) 732 break; 733 if (i > 0) 734 { 735 *p++ = map->map_coldelim; 736 len++; 737 } 738 (void) sm_strlcpy(p, mxhosts[i], sizeof buf - len); 739 p += slen; 740 len += slen; 741 } 742 #endif /* _FFR_BESTMX_BETTER_TRUNCATION */ 743 744 result = map_rewrite(map, buf, len, av); 745 #if _FFR_BESTMX_BETTER_TRUNCATION 746 sm_free(buf); 747 #endif /* _FFR_BESTMX_BETTER_TRUNCATION */ 748 return result; 749 } 750 /* 751 ** DNS_GETCANONNAME -- get the canonical name for named host using DNS 752 ** 753 ** This algorithm tries to be smart about wildcard MX records. 754 ** This is hard to do because DNS doesn't tell is if we matched 755 ** against a wildcard or a specific MX. 756 ** 757 ** We always prefer A & CNAME records, since these are presumed 758 ** to be specific. 759 ** 760 ** If we match an MX in one pass and lose it in the next, we use 761 ** the old one. For example, consider an MX matching *.FOO.BAR.COM. 762 ** A hostname bletch.foo.bar.com will match against this MX, but 763 ** will stop matching when we try bletch.bar.com -- so we know 764 ** that bletch.foo.bar.com must have been right. This fails if 765 ** there was also an MX record matching *.BAR.COM, but there are 766 ** some things that just can't be fixed. 767 ** 768 ** Parameters: 769 ** host -- a buffer containing the name of the host. 770 ** This is a value-result parameter. 771 ** hbsize -- the size of the host buffer. 772 ** trymx -- if set, try MX records as well as A and CNAME. 773 ** statp -- pointer to place to store status. 774 ** pttl -- pointer to return TTL (can be NULL). 775 ** 776 ** Returns: 777 ** true -- if the host matched. 778 ** false -- otherwise. 779 */ 780 781 # if NETINET6 782 # define SM_T_INITIAL T_AAAA 783 # else /* NETINET6 */ 784 # define SM_T_INITIAL T_A 785 # endif /* NETINET6 */ 786 787 bool 788 dns_getcanonname(host, hbsize, trymx, statp, pttl) 789 char *host; 790 int hbsize; 791 bool trymx; 792 int *statp; 793 int *pttl; 794 { 795 register unsigned char *eom, *ap; 796 register char *cp; 797 register int n; 798 HEADER *hp; 799 querybuf answer; 800 int ancount, qdcount; 801 int ret; 802 char **domain; 803 int type; 804 int ttl = 0; 805 char **dp; 806 char *mxmatch; 807 bool amatch; 808 bool gotmx = false; 809 int qtype; 810 int loopcnt; 811 char *xp; 812 char nbuf[SM_MAX(MAXPACKET, MAXDNAME*2+2)]; 813 char *searchlist[MAXDNSRCH+2]; 814 815 if (tTd(8, 2)) 816 sm_dprintf("dns_getcanonname(%s, trymx=%d)\n", host, trymx); 817 818 if ((_res.options & RES_INIT) == 0 && res_init() == -1) 819 { 820 *statp = EX_UNAVAILABLE; 821 return false; 822 } 823 824 *statp = EX_OK; 825 826 /* 827 ** Initialize domain search list. If there is at least one 828 ** dot in the name, search the unmodified name first so we 829 ** find "vse.CS" in Czechoslovakia instead of in the local 830 ** domain (e.g., vse.CS.Berkeley.EDU). Note that there is no 831 ** longer a country named Czechoslovakia but this type of problem 832 ** is still present. 833 ** 834 ** Older versions of the resolver could create this 835 ** list by tearing apart the host name. 836 */ 837 838 loopcnt = 0; 839 cnameloop: 840 /* Check for dots in the name */ 841 for (cp = host, n = 0; *cp != '\0'; cp++) 842 if (*cp == '.') 843 n++; 844 845 /* 846 ** If this is a simple name, determine whether it matches an 847 ** alias in the file defined by the environment variable HOSTALIASES. 848 */ 849 850 if (n == 0 && (xp = gethostalias(host)) != NULL) 851 { 852 if (loopcnt++ > MAXCNAMEDEPTH) 853 { 854 syserr("loop in ${HOSTALIASES} file"); 855 } 856 else 857 { 858 (void) sm_strlcpy(host, xp, hbsize); 859 goto cnameloop; 860 } 861 } 862 863 /* 864 ** Build the search list. 865 ** If there is at least one dot in name, start with a null 866 ** domain to search the unmodified name first. 867 ** If name does not end with a dot and search up local domain 868 ** tree desired, append each local domain component to the 869 ** search list; if name contains no dots and default domain 870 ** name is desired, append default domain name to search list; 871 ** else if name ends in a dot, remove that dot. 872 */ 873 874 dp = searchlist; 875 if (n > 0) 876 *dp++ = ""; 877 if (n >= 0 && *--cp != '.' && bitset(RES_DNSRCH, _res.options)) 878 { 879 /* make sure there are less than MAXDNSRCH domains */ 880 for (domain = RES_DNSRCH_VARIABLE, ret = 0; 881 *domain != NULL && ret < MAXDNSRCH; 882 ret++) 883 *dp++ = *domain++; 884 } 885 else if (n == 0 && bitset(RES_DEFNAMES, _res.options)) 886 { 887 *dp++ = _res.defdname; 888 } 889 else if (*cp == '.') 890 { 891 *cp = '\0'; 892 } 893 *dp = NULL; 894 895 /* 896 ** Now loop through the search list, appending each domain in turn 897 ** name and searching for a match. 898 */ 899 900 mxmatch = NULL; 901 qtype = SM_T_INITIAL; 902 903 for (dp = searchlist; *dp != NULL; ) 904 { 905 if (qtype == SM_T_INITIAL) 906 gotmx = false; 907 if (tTd(8, 5)) 908 sm_dprintf("dns_getcanonname: trying %s.%s (%s)\n", 909 host, *dp, 910 # if NETINET6 911 qtype == T_AAAA ? "AAAA" : 912 # endif /* NETINET6 */ 913 qtype == T_A ? "A" : 914 qtype == T_MX ? "MX" : 915 "???"); 916 errno = 0; 917 ret = res_querydomain(host, *dp, C_IN, qtype, 918 answer.qb2, sizeof(answer.qb2)); 919 if (ret <= 0) 920 { 921 int save_errno = errno; 922 923 if (tTd(8, 7)) 924 sm_dprintf("\tNO: errno=%d, h_errno=%d\n", 925 save_errno, h_errno); 926 927 if (save_errno == ECONNREFUSED || h_errno == TRY_AGAIN) 928 { 929 /* 930 ** the name server seems to be down or broken. 931 */ 932 933 SM_SET_H_ERRNO(TRY_AGAIN); 934 *statp = EX_TEMPFAIL; 935 936 if (WorkAroundBrokenAAAA) 937 { 938 /* 939 ** Only return if not TRY_AGAIN as an 940 ** attempt with a different qtype may 941 ** succeed (res_querydomain() calls 942 ** res_query() calls res_send() which 943 ** sets errno to ETIMEDOUT if the 944 ** nameservers could be contacted but 945 ** didn't give an answer). 946 */ 947 948 if (save_errno != ETIMEDOUT) 949 return false; 950 } 951 else 952 return false; 953 } 954 955 if (h_errno != HOST_NOT_FOUND) 956 { 957 /* might have another type of interest */ 958 # if NETINET6 959 if (qtype == T_AAAA) 960 { 961 qtype = T_A; 962 continue; 963 } 964 else 965 # endif /* NETINET6 */ 966 if (qtype == T_A && !gotmx && 967 (trymx || **dp == '\0')) 968 { 969 qtype = T_MX; 970 continue; 971 } 972 } 973 974 /* definite no -- try the next domain */ 975 dp++; 976 qtype = SM_T_INITIAL; 977 continue; 978 } 979 else if (tTd(8, 7)) 980 sm_dprintf("\tYES\n"); 981 982 /* avoid problems after truncation in tcp packets */ 983 if (ret > sizeof(answer)) 984 ret = sizeof(answer); 985 if (ret < 0) 986 { 987 *statp = EX_SOFTWARE; 988 return false; 989 } 990 991 /* 992 ** Appear to have a match. Confirm it by searching for A or 993 ** CNAME records. If we don't have a local domain 994 ** wild card MX record, we will accept MX as well. 995 */ 996 997 hp = (HEADER *) &answer; 998 ap = (unsigned char *) &answer + HFIXEDSZ; 999 eom = (unsigned char *) &answer + ret; 1000 1001 /* skip question part of response -- we know what we asked */ 1002 for (qdcount = ntohs((unsigned short) hp->qdcount); 1003 qdcount--; 1004 ap += ret + QFIXEDSZ) 1005 { 1006 if ((ret = dn_skipname(ap, eom)) < 0) 1007 { 1008 if (tTd(8, 20)) 1009 sm_dprintf("qdcount failure (%d)\n", 1010 ntohs((unsigned short) hp->qdcount)); 1011 *statp = EX_SOFTWARE; 1012 return false; /* ???XXX??? */ 1013 } 1014 } 1015 1016 amatch = false; 1017 for (ancount = ntohs((unsigned short) hp->ancount); 1018 --ancount >= 0 && ap < eom; 1019 ap += n) 1020 { 1021 n = dn_expand((unsigned char *) &answer, eom, ap, 1022 (RES_UNC_T) nbuf, sizeof nbuf); 1023 if (n < 0) 1024 break; 1025 ap += n; 1026 GETSHORT(type, ap); 1027 ap += INT16SZ; /* skip over class */ 1028 GETLONG(ttl, ap); 1029 GETSHORT(n, ap); /* rdlength */ 1030 switch (type) 1031 { 1032 case T_MX: 1033 gotmx = true; 1034 if (**dp != '\0' && HasWildcardMX) 1035 { 1036 /* 1037 ** If we are using MX matches and have 1038 ** not yet gotten one, save this one 1039 ** but keep searching for an A or 1040 ** CNAME match. 1041 */ 1042 1043 if (trymx && mxmatch == NULL) 1044 mxmatch = *dp; 1045 continue; 1046 } 1047 1048 /* 1049 ** If we did not append a domain name, this 1050 ** must have been a canonical name to start 1051 ** with. Even if we did append a domain name, 1052 ** in the absence of a wildcard MX this must 1053 ** still be a real MX match. 1054 ** Such MX matches are as good as an A match, 1055 ** fall through. 1056 */ 1057 /* FALLTHROUGH */ 1058 1059 # if NETINET6 1060 case T_AAAA: 1061 /* Flag that a good match was found */ 1062 amatch = true; 1063 1064 /* continue in case a CNAME also exists */ 1065 continue; 1066 # endif /* NETINET6 */ 1067 1068 case T_A: 1069 /* Flag that a good match was found */ 1070 amatch = true; 1071 1072 /* continue in case a CNAME also exists */ 1073 continue; 1074 1075 case T_CNAME: 1076 if (DontExpandCnames) 1077 { 1078 /* got CNAME -- guaranteed canonical */ 1079 amatch = true; 1080 break; 1081 } 1082 1083 if (loopcnt++ > MAXCNAMEDEPTH) 1084 { 1085 /*XXX should notify postmaster XXX*/ 1086 message("DNS failure: CNAME loop for %s", 1087 host); 1088 if (CurEnv->e_message == NULL) 1089 { 1090 char ebuf[MAXLINE]; 1091 1092 (void) sm_snprintf(ebuf, 1093 sizeof ebuf, 1094 "Deferred: DNS failure: CNAME loop for %.100s", 1095 host); 1096 CurEnv->e_message = 1097 sm_rpool_strdup_x( 1098 CurEnv->e_rpool, ebuf); 1099 } 1100 SM_SET_H_ERRNO(NO_RECOVERY); 1101 *statp = EX_CONFIG; 1102 return false; 1103 } 1104 1105 /* value points at name */ 1106 if ((ret = dn_expand((unsigned char *)&answer, 1107 eom, ap, (RES_UNC_T) nbuf, 1108 sizeof(nbuf))) < 0) 1109 break; 1110 (void) sm_strlcpy(host, nbuf, hbsize); 1111 1112 /* 1113 ** RFC 1034 section 3.6 specifies that CNAME 1114 ** should point at the canonical name -- but 1115 ** urges software to try again anyway. 1116 */ 1117 1118 goto cnameloop; 1119 1120 default: 1121 /* not a record of interest */ 1122 continue; 1123 } 1124 } 1125 1126 if (amatch) 1127 { 1128 /* 1129 ** Got a good match -- either an A, CNAME, or an 1130 ** exact MX record. Save it and get out of here. 1131 */ 1132 1133 mxmatch = *dp; 1134 break; 1135 } 1136 1137 /* 1138 ** Nothing definitive yet. 1139 ** If this was a T_A query and we haven't yet found a MX 1140 ** match, try T_MX if allowed to do so. 1141 ** Otherwise, try the next domain. 1142 */ 1143 1144 # if NETINET6 1145 if (qtype == T_AAAA) 1146 qtype = T_A; 1147 else 1148 # endif /* NETINET6 */ 1149 if (qtype == T_A && !gotmx && (trymx || **dp == '\0')) 1150 qtype = T_MX; 1151 else 1152 { 1153 qtype = SM_T_INITIAL; 1154 dp++; 1155 } 1156 } 1157 1158 /* if nothing was found, we are done */ 1159 if (mxmatch == NULL) 1160 { 1161 if (*statp == EX_OK) 1162 *statp = EX_NOHOST; 1163 return false; 1164 } 1165 1166 /* 1167 ** Create canonical name and return. 1168 ** If saved domain name is null, name was already canonical. 1169 ** Otherwise append the saved domain name. 1170 */ 1171 1172 (void) sm_snprintf(nbuf, sizeof nbuf, "%.*s%s%.*s", MAXDNAME, host, 1173 *mxmatch == '\0' ? "" : ".", 1174 MAXDNAME, mxmatch); 1175 (void) sm_strlcpy(host, nbuf, hbsize); 1176 if (tTd(8, 5)) 1177 sm_dprintf("dns_getcanonname: %s\n", host); 1178 *statp = EX_OK; 1179 1180 /* return only one TTL entry, that should be sufficient */ 1181 if (ttl > 0 && pttl != NULL) 1182 *pttl = ttl; 1183 return true; 1184 } 1185 1186 static char * 1187 gethostalias(host) 1188 char *host; 1189 { 1190 char *fname; 1191 SM_FILE_T *fp; 1192 register char *p = NULL; 1193 long sff = SFF_REGONLY; 1194 char buf[MAXLINE]; 1195 static char hbuf[MAXDNAME]; 1196 1197 if (ResNoAliases) 1198 return NULL; 1199 if (DontLockReadFiles) 1200 sff |= SFF_NOLOCK; 1201 fname = getenv("HOSTALIASES"); 1202 if (fname == NULL || 1203 (fp = safefopen(fname, O_RDONLY, 0, sff)) == NULL) 1204 return NULL; 1205 while (sm_io_fgets(fp, SM_TIME_DEFAULT, buf, sizeof buf) != NULL) 1206 { 1207 for (p = buf; p != '\0' && !(isascii(*p) && isspace(*p)); p++) 1208 continue; 1209 if (*p == 0) 1210 { 1211 /* syntax error */ 1212 continue; 1213 } 1214 *p++ = '\0'; 1215 if (sm_strcasecmp(buf, host) == 0) 1216 break; 1217 } 1218 1219 if (sm_io_eof(fp)) 1220 { 1221 /* no match */ 1222 (void) sm_io_close(fp, SM_TIME_DEFAULT); 1223 return NULL; 1224 } 1225 (void) sm_io_close(fp, SM_TIME_DEFAULT); 1226 1227 /* got a match; extract the equivalent name */ 1228 while (*p != '\0' && isascii(*p) && isspace(*p)) 1229 p++; 1230 host = p; 1231 while (*p != '\0' && !(isascii(*p) && isspace(*p))) 1232 p++; 1233 *p = '\0'; 1234 (void) sm_strlcpy(hbuf, host, sizeof hbuf); 1235 return hbuf; 1236 } 1237 #endif /* NAMED_BIND */ 1238