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