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