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