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