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