1 /* 2 * Copyright (c) 1986, 1995 Eric P. Allman 3 * Copyright (c) 1988, 1993 4 * The Regents of the University of California. All rights reserved. 5 * 6 * %sccs.include.redist.c% 7 */ 8 9 #include "sendmail.h" 10 11 #ifndef lint 12 #if NAMED_BIND 13 static char sccsid[] = "@(#)domain.c 8.46 (Berkeley) 06/19/95 (with name server)"; 14 #else 15 static char sccsid[] = "@(#)domain.c 8.46 (Berkeley) 06/19/95 (without name server)"; 16 #endif 17 #endif /* not lint */ 18 19 #if NAMED_BIND 20 21 #include <errno.h> 22 #include <resolv.h> 23 24 typedef union 25 { 26 HEADER qb1; 27 u_char qb2[PACKETSZ]; 28 } querybuf; 29 30 static char MXHostBuf[MAXMXHOSTS*PACKETSZ]; 31 32 #ifndef MAXDNSRCH 33 #define MAXDNSRCH 6 /* number of possible domains to search */ 34 #endif 35 36 #ifndef MAX 37 #define MAX(a, b) ((a) > (b) ? (a) : (b)) 38 #endif 39 40 #ifndef NO_DATA 41 # define NO_DATA NO_ADDRESS 42 #endif 43 44 #ifndef HFIXEDSZ 45 # define HFIXEDSZ 12 /* sizeof(HEADER) */ 46 #endif 47 48 #define MAXCNAMEDEPTH 10 /* maximum depth of CNAME recursion */ 49 50 #if defined(__RES) && (__RES >= 19940415) 51 # define RES_UNC_T char * 52 #else 53 # define RES_UNC_T u_char * 54 #endif 55 /* 56 ** GETMXRR -- get MX resource records for a domain 57 ** 58 ** Parameters: 59 ** host -- the name of the host to MX. 60 ** mxhosts -- a pointer to a return buffer of MX records. 61 ** droplocalhost -- If TRUE, all MX records less preferred 62 ** than the local host (as determined by $=w) will 63 ** be discarded. 64 ** rcode -- a pointer to an EX_ status code. 65 ** 66 ** Returns: 67 ** The number of MX records found. 68 ** -1 if there is an internal failure. 69 ** If no MX records are found, mxhosts[0] is set to host 70 ** and 1 is returned. 71 */ 72 73 int 74 getmxrr(host, mxhosts, droplocalhost, rcode) 75 char *host; 76 char **mxhosts; 77 bool droplocalhost; 78 int *rcode; 79 { 80 extern int h_errno; 81 register u_char *eom, *cp; 82 register int i, j, n; 83 int nmx = 0; 84 register char *bp; 85 HEADER *hp; 86 querybuf answer; 87 int ancount, qdcount, buflen; 88 bool seenlocal = FALSE; 89 u_short pref, type; 90 u_short localpref = 256; 91 char *fallbackMX = FallBackMX; 92 static bool firsttime = TRUE; 93 bool trycanon = FALSE; 94 int (*resfunc)(); 95 extern int res_query(), res_search(); 96 u_short prefer[MAXMXHOSTS]; 97 int weight[MAXMXHOSTS]; 98 extern bool getcanonname(); 99 100 if (tTd(8, 2)) 101 printf("getmxrr(%s, droplocalhost=%d)\n", host, droplocalhost); 102 103 if (fallbackMX != NULL) 104 { 105 if (firsttime && 106 res_query(FallBackMX, C_IN, T_A, 107 (u_char *) &answer, sizeof answer) < 0) 108 { 109 /* this entry is bogus */ 110 fallbackMX = FallBackMX = NULL; 111 } 112 else if (droplocalhost && wordinclass(fallbackMX, 'w')) 113 { 114 /* don't use fallback for this pass */ 115 fallbackMX = NULL; 116 } 117 firsttime = FALSE; 118 } 119 120 *rcode = EX_OK; 121 122 /* efficiency hack -- numeric or non-MX lookups */ 123 if (host[0] == '[') 124 goto punt; 125 126 /* 127 ** If we don't have MX records in our host switch, don't 128 ** try for MX records. Note that this really isn't "right", 129 ** since we might be set up to try NIS first and then DNS; 130 ** if the host is found in NIS we really shouldn't be doing 131 ** MX lookups. However, that should be a degenerate case. 132 */ 133 134 if (!UseNameServer) 135 goto punt; 136 if (HasWildcardMX && ConfigLevel >= 6) 137 resfunc = res_query; 138 else 139 resfunc = res_search; 140 141 errno = 0; 142 n = (*resfunc)(host, C_IN, T_MX, (u_char *) &answer, sizeof(answer)); 143 if (n < 0) 144 { 145 if (tTd(8, 1)) 146 printf("getmxrr: res_search(%s) failed (errno=%d, h_errno=%d)\n", 147 (host == NULL) ? "<NULL>" : host, errno, h_errno); 148 switch (h_errno) 149 { 150 case NO_DATA: 151 trycanon = TRUE; 152 /* fall through */ 153 154 case NO_RECOVERY: 155 /* no MX data on this host */ 156 goto punt; 157 158 case HOST_NOT_FOUND: 159 #if BROKEN_RES_SEARCH 160 case 0: /* Ultrix resolver retns failure w/ h_errno=0 */ 161 #endif 162 /* host doesn't exist in DNS; might be in /etc/hosts */ 163 *rcode = EX_NOHOST; 164 goto punt; 165 166 case TRY_AGAIN: 167 /* couldn't connect to the name server */ 168 /* it might come up later; better queue it up */ 169 *rcode = EX_TEMPFAIL; 170 break; 171 172 default: 173 syserr("getmxrr: res_search (%s) failed with impossible h_errno (%d)\n", 174 host, h_errno); 175 *rcode = EX_OSERR; 176 break; 177 } 178 179 /* irreconcilable differences */ 180 return (-1); 181 } 182 183 /* find first satisfactory answer */ 184 hp = (HEADER *)&answer; 185 cp = (u_char *)&answer + HFIXEDSZ; 186 eom = (u_char *)&answer + n; 187 for (qdcount = ntohs(hp->qdcount); qdcount--; cp += n + QFIXEDSZ) 188 if ((n = dn_skipname(cp, eom)) < 0) 189 goto punt; 190 buflen = sizeof(MXHostBuf) - 1; 191 bp = MXHostBuf; 192 ancount = ntohs(hp->ancount); 193 while (--ancount >= 0 && cp < eom && nmx < MAXMXHOSTS - 1) 194 { 195 if ((n = dn_expand((u_char *)&answer, 196 eom, cp, (RES_UNC_T) bp, buflen)) < 0) 197 break; 198 cp += n; 199 GETSHORT(type, cp); 200 cp += INT16SZ + INT32SZ; 201 GETSHORT(n, cp); 202 if (type != T_MX) 203 { 204 if (tTd(8, 8) || _res.options & RES_DEBUG) 205 printf("unexpected answer type %d, size %d\n", 206 type, n); 207 cp += n; 208 continue; 209 } 210 GETSHORT(pref, cp); 211 if ((n = dn_expand((u_char *)&answer, eom, cp, 212 (RES_UNC_T) bp, buflen)) < 0) 213 break; 214 cp += n; 215 if (droplocalhost && wordinclass(bp, 'w')) 216 { 217 if (tTd(8, 3)) 218 printf("found localhost (%s) in MX list, pref=%d\n", 219 bp, pref); 220 if (!seenlocal || pref < localpref) 221 localpref = pref; 222 seenlocal = TRUE; 223 continue; 224 } 225 weight[nmx] = mxrand(bp); 226 prefer[nmx] = pref; 227 mxhosts[nmx++] = bp; 228 n = strlen(bp); 229 bp += n; 230 if (bp[-1] != '.') 231 { 232 *bp++ = '.'; 233 n++; 234 } 235 *bp++ = '\0'; 236 buflen -= n + 1; 237 } 238 239 /* sort the records */ 240 for (i = 0; i < nmx; i++) 241 { 242 for (j = i + 1; j < nmx; j++) 243 { 244 if (prefer[i] > prefer[j] || 245 (prefer[i] == prefer[j] && weight[i] > weight[j])) 246 { 247 register int temp; 248 register char *temp1; 249 250 temp = prefer[i]; 251 prefer[i] = prefer[j]; 252 prefer[j] = temp; 253 temp1 = mxhosts[i]; 254 mxhosts[i] = mxhosts[j]; 255 mxhosts[j] = temp1; 256 temp = weight[i]; 257 weight[i] = weight[j]; 258 weight[j] = temp; 259 } 260 } 261 if (seenlocal && prefer[i] >= localpref) 262 { 263 /* truncate higher preference part of list */ 264 nmx = i; 265 } 266 } 267 268 if (nmx == 0) 269 { 270 punt: 271 if (seenlocal && 272 (!TryNullMXList || sm_gethostbyname(host) == NULL)) 273 { 274 /* 275 ** If we have deleted all MX entries, this is 276 ** an error -- we should NEVER send to a host that 277 ** has an MX, and this should have been caught 278 ** earlier in the config file. 279 ** 280 ** Some sites prefer to go ahead and try the 281 ** A record anyway; that case is handled by 282 ** setting TryNullMXList. I believe this is a 283 ** bad idea, but it's up to you.... 284 */ 285 286 *rcode = EX_CONFIG; 287 syserr("MX list for %s points back to %s", 288 host, MyHostName); 289 return -1; 290 } 291 strcpy(MXHostBuf, host); 292 mxhosts[0] = MXHostBuf; 293 if (host[0] == '[') 294 { 295 register char *p; 296 297 /* this may be an MX suppression-style address */ 298 p = strchr(MXHostBuf, ']'); 299 if (p != NULL) 300 { 301 *p = '\0'; 302 if (inet_addr(&MXHostBuf[1]) != -1) 303 *p = ']'; 304 else 305 { 306 trycanon = TRUE; 307 mxhosts[0]++; 308 } 309 } 310 } 311 if (trycanon && 312 getcanonname(mxhosts[0], sizeof MXHostBuf - 2, FALSE)) 313 { 314 bp = &MXHostBuf[strlen(MXHostBuf)]; 315 if (bp[-1] != '.') 316 { 317 *bp++ = '.'; 318 *bp = '\0'; 319 } 320 nmx = 1; 321 } 322 } 323 324 /* if we have a default lowest preference, include that */ 325 if (fallbackMX != NULL && !seenlocal) 326 mxhosts[nmx++] = fallbackMX; 327 328 return (nmx); 329 } 330 /* 331 ** MXRAND -- create a randomizer for equal MX preferences 332 ** 333 ** If two MX hosts have equal preferences we want to randomize 334 ** the selection. But in order for signatures to be the same, 335 ** we need to randomize the same way each time. This function 336 ** computes a pseudo-random hash function from the host name. 337 ** 338 ** Parameters: 339 ** host -- the name of the host. 340 ** 341 ** Returns: 342 ** A random but repeatable value based on the host name. 343 ** 344 ** Side Effects: 345 ** none. 346 */ 347 348 int 349 mxrand(host) 350 register char *host; 351 { 352 int hfunc; 353 static unsigned int seed; 354 355 if (seed == 0) 356 { 357 seed = (int) curtime() & 0xffff; 358 if (seed == 0) 359 seed++; 360 } 361 362 if (tTd(17, 9)) 363 printf("mxrand(%s)", host); 364 365 hfunc = seed; 366 while (*host != '\0') 367 { 368 int c = *host++; 369 370 if (isascii(c) && isupper(c)) 371 c = tolower(c); 372 hfunc = ((hfunc << 1) ^ c) % 2003; 373 } 374 375 hfunc &= 0xff; 376 377 if (tTd(17, 9)) 378 printf(" = %d\n", hfunc); 379 return hfunc; 380 } 381 /* 382 ** BESTMX -- find the best MX for a name 383 ** 384 ** This is really a hack, but I don't see any obvious way 385 ** to generalize it at the moment. 386 */ 387 388 char * 389 bestmx_map_lookup(map, name, av, statp) 390 MAP *map; 391 char *name; 392 char **av; 393 int *statp; 394 { 395 int nmx; 396 auto int rcode; 397 int saveopts = _res.options; 398 char *mxhosts[MAXMXHOSTS + 1]; 399 400 _res.options &= ~(RES_DNSRCH|RES_DEFNAMES); 401 nmx = getmxrr(name, mxhosts, FALSE, &rcode); 402 _res.options = saveopts; 403 if (nmx <= 0) 404 return NULL; 405 if (bitset(MF_MATCHONLY, map->map_mflags)) 406 return map_rewrite(map, name, strlen(name), NULL); 407 else 408 return map_rewrite(map, mxhosts[0], strlen(mxhosts[0]), av); 409 } 410 /* 411 ** DNS_GETCANONNAME -- get the canonical name for named host using DNS 412 ** 413 ** This algorithm tries to be smart about wildcard MX records. 414 ** This is hard to do because DNS doesn't tell is if we matched 415 ** against a wildcard or a specific MX. 416 ** 417 ** We always prefer A & CNAME records, since these are presumed 418 ** to be specific. 419 ** 420 ** If we match an MX in one pass and lose it in the next, we use 421 ** the old one. For example, consider an MX matching *.FOO.BAR.COM. 422 ** A hostname bletch.foo.bar.com will match against this MX, but 423 ** will stop matching when we try bletch.bar.com -- so we know 424 ** that bletch.foo.bar.com must have been right. This fails if 425 ** there was also an MX record matching *.BAR.COM, but there are 426 ** some things that just can't be fixed. 427 ** 428 ** Parameters: 429 ** host -- a buffer containing the name of the host. 430 ** This is a value-result parameter. 431 ** hbsize -- the size of the host buffer. 432 ** trymx -- if set, try MX records as well as A and CNAME. 433 ** statp -- pointer to place to store status. 434 ** 435 ** Returns: 436 ** TRUE -- if the host matched. 437 ** FALSE -- otherwise. 438 */ 439 440 bool 441 dns_getcanonname(host, hbsize, trymx, statp) 442 char *host; 443 int hbsize; 444 bool trymx; 445 int *statp; 446 { 447 extern int h_errno; 448 register u_char *eom, *ap; 449 register char *cp; 450 register int n; 451 HEADER *hp; 452 querybuf answer; 453 int ancount, qdcount; 454 int ret; 455 char **domain; 456 int type; 457 char **dp; 458 char *mxmatch; 459 bool amatch; 460 bool gotmx = FALSE; 461 int qtype; 462 int loopcnt; 463 char *xp; 464 char nbuf[MAX(PACKETSZ, MAXDNAME*2+2)]; 465 char *searchlist[MAXDNSRCH+2]; 466 extern char *gethostalias(); 467 468 if (tTd(8, 2)) 469 printf("dns_getcanonname(%s, trymx=%d)\n", host, trymx); 470 471 if ((_res.options & RES_INIT) == 0 && res_init() == -1) 472 { 473 *statp = EX_UNAVAILABLE; 474 return FALSE; 475 } 476 477 /* 478 ** Initialize domain search list. If there is at least one 479 ** dot in the name, search the unmodified name first so we 480 ** find "vse.CS" in Czechoslovakia instead of in the local 481 ** domain (e.g., vse.CS.Berkeley.EDU). 482 ** 483 ** Older versions of the resolver could create this 484 ** list by tearing apart the host name. 485 */ 486 487 loopcnt = 0; 488 cnameloop: 489 for (cp = host, n = 0; *cp != '\0'; cp++) 490 if (*cp == '.') 491 n++; 492 493 if (n == 0 && (xp = gethostalias(host)) != NULL) 494 { 495 if (loopcnt++ > MAXCNAMEDEPTH) 496 { 497 syserr("loop in ${HOSTALIASES} file"); 498 } 499 else 500 { 501 strncpy(host, xp, hbsize); 502 host[hbsize - 1] = '\0'; 503 goto cnameloop; 504 } 505 } 506 507 dp = searchlist; 508 if (n > 0) 509 *dp++ = ""; 510 if (n >= 0 && *--cp != '.' && bitset(RES_DNSRCH, _res.options)) 511 { 512 for (domain = _res.dnsrch; *domain != NULL; ) 513 *dp++ = *domain++; 514 } 515 else if (n == 0 && bitset(RES_DEFNAMES, _res.options)) 516 { 517 *dp++ = _res.defdname; 518 } 519 else if (*cp == '.') 520 { 521 *cp = '\0'; 522 } 523 *dp = NULL; 524 525 /* if we have a wildcard MX and no dots, try MX anyhow */ 526 if (n == 0) 527 trymx = TRUE; 528 529 /* 530 ** Now run through the search list for the name in question. 531 */ 532 533 mxmatch = NULL; 534 qtype = T_ANY; 535 536 for (dp = searchlist; *dp != NULL; ) 537 { 538 if (qtype == T_ANY) 539 gotmx = FALSE; 540 if (tTd(8, 5)) 541 printf("dns_getcanonname: trying %s.%s (%s)\n", 542 host, *dp, 543 qtype == T_ANY ? "ANY" : qtype == T_A ? "A" : 544 qtype == T_MX ? "MX" : "???"); 545 ret = res_querydomain(host, *dp, C_IN, qtype, 546 answer.qb2, sizeof(answer.qb2)); 547 if (ret <= 0) 548 { 549 if (tTd(8, 7)) 550 printf("\tNO: errno=%d, h_errno=%d\n", 551 errno, h_errno); 552 553 if (errno == ECONNREFUSED || h_errno == TRY_AGAIN) 554 { 555 /* the name server seems to be down */ 556 h_errno = TRY_AGAIN; 557 *statp = EX_TEMPFAIL; 558 return FALSE; 559 } 560 561 if (h_errno != HOST_NOT_FOUND) 562 { 563 /* might have another type of interest */ 564 if (qtype == T_ANY) 565 { 566 qtype = T_A; 567 continue; 568 } 569 else if (qtype == T_A && !gotmx && trymx) 570 { 571 qtype = T_MX; 572 continue; 573 } 574 } 575 576 /* try the next name */ 577 dp++; 578 qtype = T_ANY; 579 continue; 580 } 581 else if (tTd(8, 7)) 582 printf("\tYES\n"); 583 584 /* 585 ** This might be a bogus match. Search for A or 586 ** CNAME records. If we don't have a matching 587 ** wild card MX record, we will accept MX as well. 588 */ 589 590 hp = (HEADER *) &answer; 591 ap = (u_char *) &answer + HFIXEDSZ; 592 eom = (u_char *) &answer + ret; 593 594 /* skip question part of response -- we know what we asked */ 595 for (qdcount = ntohs(hp->qdcount); qdcount--; ap += ret + QFIXEDSZ) 596 { 597 if ((ret = dn_skipname(ap, eom)) < 0) 598 { 599 if (tTd(8, 20)) 600 printf("qdcount failure (%d)\n", 601 ntohs(hp->qdcount)); 602 *statp = EX_SOFTWARE; 603 return FALSE; /* ???XXX??? */ 604 } 605 } 606 607 amatch = FALSE; 608 for (ancount = ntohs(hp->ancount); --ancount >= 0 && ap < eom; ap += n) 609 { 610 n = dn_expand((u_char *) &answer, eom, ap, 611 (RES_UNC_T) nbuf, sizeof nbuf); 612 if (n < 0) 613 break; 614 ap += n; 615 GETSHORT(type, ap); 616 ap += INT16SZ + INT32SZ; 617 GETSHORT(n, ap); 618 switch (type) 619 { 620 case T_MX: 621 gotmx = TRUE; 622 if (**dp != '\0' || !HasWildcardMX) 623 { 624 /* got a match -- save that info */ 625 if (trymx && mxmatch == NULL) 626 mxmatch = *dp; 627 continue; 628 } 629 630 /* exact MX matches are as good as an A match */ 631 /* fall through */ 632 633 case T_A: 634 /* good show */ 635 amatch = TRUE; 636 637 /* continue in case a CNAME also exists */ 638 continue; 639 640 case T_CNAME: 641 if (DontExpandCnames) 642 { 643 /* got CNAME -- guaranteed canonical */ 644 amatch = TRUE; 645 break; 646 } 647 648 if (loopcnt++ > MAXCNAMEDEPTH) 649 { 650 /*XXX should notify postmaster XXX*/ 651 message("DNS failure: CNAME loop for %s", 652 host); 653 if (CurEnv->e_message == NULL) 654 { 655 char ebuf[MAXLINE]; 656 657 sprintf(ebuf, "Deferred: DNS failure: CNAME loop for %s", 658 host); 659 CurEnv->e_message = newstr(ebuf); 660 } 661 h_errno = NO_RECOVERY; 662 *statp = EX_CONFIG; 663 return FALSE; 664 } 665 666 /* value points at name */ 667 if ((ret = dn_expand((u_char *)&answer, 668 eom, ap, (RES_UNC_T) nbuf, sizeof(nbuf))) < 0) 669 break; 670 (void)strncpy(host, nbuf, hbsize); /* XXX */ 671 host[hbsize - 1] = '\0'; 672 673 /* 674 ** RFC 1034 section 3.6 specifies that CNAME 675 ** should point at the canonical name -- but 676 ** urges software to try again anyway. 677 */ 678 679 goto cnameloop; 680 681 default: 682 /* not a record of interest */ 683 continue; 684 } 685 } 686 687 if (amatch) 688 { 689 /* got an A record and no CNAME */ 690 mxmatch = *dp; 691 break; 692 } 693 694 /* 695 ** If this was a T_ANY query, we may have the info but 696 ** need an explicit query. Try T_A, then T_MX. 697 */ 698 699 if (qtype == T_ANY) 700 qtype = T_A; 701 else if (qtype == T_A && !gotmx && trymx) 702 qtype = T_MX; 703 else 704 { 705 /* really nothing in this domain; try the next */ 706 qtype = T_ANY; 707 dp++; 708 } 709 } 710 711 if (mxmatch == NULL) 712 { 713 *statp = EX_NOHOST; 714 return FALSE; 715 } 716 717 /* create matching name and return */ 718 (void) sprintf(nbuf, "%.*s%s%.*s", MAXDNAME, host, 719 *mxmatch == '\0' ? "" : ".", 720 MAXDNAME, mxmatch); 721 strncpy(host, nbuf, hbsize); 722 host[hbsize - 1] = '\0'; 723 *statp = EX_OK; 724 return TRUE; 725 } 726 727 728 char * 729 gethostalias(host) 730 char *host; 731 { 732 char *fname; 733 FILE *fp; 734 register char *p = NULL; 735 char buf[MAXLINE]; 736 static char hbuf[MAXDNAME]; 737 738 fname = getenv("HOSTALIASES"); 739 if (fname == NULL || 740 (fp = safefopen(fname, O_RDONLY, 0, SFF_REGONLY)) == NULL) 741 return NULL; 742 while (fgets(buf, sizeof buf, fp) != NULL) 743 { 744 for (p = buf; p != '\0' && !(isascii(*p) && isspace(*p)); p++) 745 continue; 746 if (*p == 0) 747 { 748 /* syntax error */ 749 continue; 750 } 751 *p++ = '\0'; 752 if (strcasecmp(buf, host) == 0) 753 break; 754 } 755 756 if (feof(fp)) 757 { 758 /* no match */ 759 fclose(fp); 760 return NULL; 761 } 762 763 /* got a match; extract the equivalent name */ 764 while (*p != '\0' && isascii(*p) && isspace(*p)) 765 p++; 766 host = p; 767 while (*p != '\0' && !(isascii(*p) && isspace(*p))) 768 p++; 769 *p = '\0'; 770 strncpy(hbuf, host, sizeof hbuf - 1); 771 hbuf[sizeof hbuf - 1] = '\0'; 772 return hbuf; 773 } 774 775 #endif /* NAMED_BIND */ 776