1 /*
2 * Copyright (c) 1998-2004, 2006 Sendmail, Inc. and its suppliers.
3 * All rights reserved.
4 * Copyright (c) 1986, 1995-1997 Eric P. Allman. All rights reserved.
5 * Copyright (c) 1988, 1993
6 * The Regents of the University of California. All rights reserved.
7 *
8 * By using this file, you agree to the terms and conditions set
9 * forth in the LICENSE file which can be found at the top level of
10 * the sendmail distribution.
11 *
12 */
13
14 #include <sendmail.h>
15 #include "map.h"
16
17 #if NAMED_BIND
18 SM_RCSID("@(#)$Id: domain.c,v 8.202 2006/12/19 01:15:07 ca Exp $ (with name server)")
19 #else /* NAMED_BIND */
20 SM_RCSID("@(#)$Id: domain.c,v 8.202 2006/12/19 01:15:07 ca Exp $ (without name server)")
21 #endif /* NAMED_BIND */
22
23 #if NAMED_BIND
24
25 # include <arpa/inet.h>
26
27
28 /*
29 ** The standard udp packet size PACKETSZ (512) is not sufficient for some
30 ** nameserver answers containing very many resource records. The resolver
31 ** may switch to tcp and retry if it detects udp packet overflow.
32 ** Also note that the resolver routines res_query and res_search return
33 ** the size of the *un*truncated answer in case the supplied answer buffer
34 ** it not big enough to accommodate the entire answer.
35 */
36
37 # ifndef MAXPACKET
38 # define MAXPACKET 8192 /* max packet size used internally by BIND */
39 # endif /* ! MAXPACKET */
40
41 typedef union
42 {
43 HEADER qb1;
44 unsigned char qb2[MAXPACKET];
45 } querybuf;
46
47 # ifndef MXHOSTBUFSIZE
48 # define MXHOSTBUFSIZE (128 * MAXMXHOSTS)
49 # endif /* ! MXHOSTBUFSIZE */
50
51 static char MXHostBuf[MXHOSTBUFSIZE];
52 #if (MXHOSTBUFSIZE < 2) || (MXHOSTBUFSIZE >= INT_MAX/2)
53 ERROR: _MXHOSTBUFSIZE is out of range
54 #endif /* (MXHOSTBUFSIZE < 2) || (MXHOSTBUFSIZE >= INT_MAX/2) */
55
56 # ifndef MAXDNSRCH
57 # define MAXDNSRCH 6 /* number of possible domains to search */
58 # endif /* ! MAXDNSRCH */
59
60 # ifndef RES_DNSRCH_VARIABLE
61 # define RES_DNSRCH_VARIABLE _res.dnsrch
62 # endif /* ! RES_DNSRCH_VARIABLE */
63
64 # ifndef NO_DATA
65 # define NO_DATA NO_ADDRESS
66 # endif /* ! NO_DATA */
67
68 # ifndef HFIXEDSZ
69 # define HFIXEDSZ 12 /* sizeof(HEADER) */
70 # endif /* ! HFIXEDSZ */
71
72 # define MAXCNAMEDEPTH 10 /* maximum depth of CNAME recursion */
73
74 # if defined(__RES) && (__RES >= 19940415)
75 # define RES_UNC_T char *
76 # else /* defined(__RES) && (__RES >= 19940415) */
77 # define RES_UNC_T unsigned char *
78 # endif /* defined(__RES) && (__RES >= 19940415) */
79
80 static int mxrand __P((char *));
81 static int fallbackmxrr __P((int, unsigned short *, char **));
82
83 /*
84 ** GETFALLBACKMXRR -- get MX resource records for fallback MX host.
85 **
86 ** We have to initialize this once before doing anything else.
87 ** Moreover, we have to repeat this from time to time to avoid
88 ** stale data, e.g., in persistent queue runners.
89 ** This should be done in a parent process so the child
90 ** processes have the right data.
91 **
92 ** Parameters:
93 ** host -- the name of the fallback MX host.
94 **
95 ** Returns:
96 ** number of MX records.
97 **
98 ** Side Effects:
99 ** Populates NumFallbackMXHosts and fbhosts.
100 ** Sets renewal time (based on TTL).
101 */
102
103 int NumFallbackMXHosts = 0; /* Number of fallback MX hosts (after MX expansion) */
104 static char *fbhosts[MAXMXHOSTS + 1];
105
106 int
getfallbackmxrr(host)107 getfallbackmxrr(host)
108 char *host;
109 {
110 int i, rcode;
111 int ttl;
112 static time_t renew = 0;
113
114 #if 0
115 /* This is currently done before this function is called. */
116 if (host == NULL || *host == '\0')
117 return 0;
118 #endif /* 0 */
119 if (NumFallbackMXHosts > 0 && renew > curtime())
120 return NumFallbackMXHosts;
121 if (host[0] == '[')
122 {
123 fbhosts[0] = host;
124 NumFallbackMXHosts = 1;
125 }
126 else
127 {
128 /* free old data */
129 for (i = 0; i < NumFallbackMXHosts; i++)
130 sm_free(fbhosts[i]);
131
132 /* get new data */
133 NumFallbackMXHosts = getmxrr(host, fbhosts, NULL, false,
134 &rcode, false, &ttl);
135 renew = curtime() + ttl;
136 for (i = 0; i < NumFallbackMXHosts; i++)
137 fbhosts[i] = newstr(fbhosts[i]);
138 }
139 return NumFallbackMXHosts;
140 }
141
142 /*
143 ** FALLBACKMXRR -- add MX resource records for fallback MX host to list.
144 **
145 ** Parameters:
146 ** nmx -- current number of MX records.
147 ** prefs -- array of preferences.
148 ** mxhosts -- array of MX hosts (maximum size: MAXMXHOSTS)
149 **
150 ** Returns:
151 ** new number of MX records.
152 **
153 ** Side Effects:
154 ** If FallbackMX was set, it appends the MX records for
155 ** that host to mxhosts (and modifies prefs accordingly).
156 */
157
158 static int
fallbackmxrr(nmx,prefs,mxhosts)159 fallbackmxrr(nmx, prefs, mxhosts)
160 int nmx;
161 unsigned short *prefs;
162 char **mxhosts;
163 {
164 int i;
165
166 for (i = 0; i < NumFallbackMXHosts && nmx < MAXMXHOSTS; i++)
167 {
168 if (nmx > 0)
169 prefs[nmx] = prefs[nmx - 1] + 1;
170 else
171 prefs[nmx] = 0;
172 mxhosts[nmx++] = fbhosts[i];
173 }
174 return nmx;
175 }
176
177 /*
178 ** GETMXRR -- get MX resource records for a domain
179 **
180 ** Parameters:
181 ** host -- the name of the host to MX.
182 ** mxhosts -- a pointer to a return buffer of MX records.
183 ** mxprefs -- a pointer to a return buffer of MX preferences.
184 ** If NULL, don't try to populate.
185 ** droplocalhost -- If true, all MX records less preferred
186 ** than the local host (as determined by $=w) will
187 ** be discarded.
188 ** rcode -- a pointer to an EX_ status code.
189 ** tryfallback -- add also fallback MX host?
190 ** pttl -- pointer to return TTL (can be NULL).
191 **
192 ** Returns:
193 ** The number of MX records found.
194 ** -1 if there is an internal failure.
195 ** If no MX records are found, mxhosts[0] is set to host
196 ** and 1 is returned.
197 **
198 ** Side Effects:
199 ** The entries made for mxhosts point to a static array
200 ** MXHostBuf[MXHOSTBUFSIZE], so the data needs to be copied,
201 ** if it must be preserved across calls to this function.
202 */
203
204 int
getmxrr(host,mxhosts,mxprefs,droplocalhost,rcode,tryfallback,pttl)205 getmxrr(host, mxhosts, mxprefs, droplocalhost, rcode, tryfallback, pttl)
206 char *host;
207 char **mxhosts;
208 unsigned short *mxprefs;
209 bool droplocalhost;
210 int *rcode;
211 bool tryfallback;
212 int *pttl;
213 {
214 register unsigned char *eom, *cp;
215 register int i, j, n;
216 int nmx = 0;
217 register char *bp;
218 HEADER *hp;
219 querybuf answer;
220 int ancount, qdcount, buflen;
221 bool seenlocal = false;
222 unsigned short pref, type;
223 unsigned short localpref = 256;
224 char *fallbackMX = FallbackMX;
225 bool trycanon = false;
226 unsigned short *prefs;
227 int (*resfunc) __P((const char *, int, int, u_char *, int));
228 unsigned short prefer[MAXMXHOSTS];
229 int weight[MAXMXHOSTS];
230 int ttl = 0;
231 extern int res_query(), res_search();
232
233 if (tTd(8, 2))
234 sm_dprintf("getmxrr(%s, droplocalhost=%d)\n",
235 host, droplocalhost);
236 *rcode = EX_OK;
237 if (pttl != NULL)
238 *pttl = SM_DEFAULT_TTL;
239 if (*host == '\0')
240 return 0;
241
242 if ((fallbackMX != NULL && droplocalhost &&
243 wordinclass(fallbackMX, 'w')) || !tryfallback)
244 {
245 /* don't use fallback for this pass */
246 fallbackMX = NULL;
247 }
248
249 if (mxprefs != NULL)
250 prefs = mxprefs;
251 else
252 prefs = prefer;
253
254 /* efficiency hack -- numeric or non-MX lookups */
255 if (host[0] == '[')
256 goto punt;
257
258 /*
259 ** If we don't have MX records in our host switch, don't
260 ** try for MX records. Note that this really isn't "right",
261 ** since we might be set up to try NIS first and then DNS;
262 ** if the host is found in NIS we really shouldn't be doing
263 ** MX lookups. However, that should be a degenerate case.
264 */
265
266 if (!UseNameServer)
267 goto punt;
268 if (HasWildcardMX && ConfigLevel >= 6)
269 resfunc = res_query;
270 else
271 resfunc = res_search;
272
273 errno = 0;
274 n = (*resfunc)(host, C_IN, T_MX, (unsigned char *) &answer,
275 sizeof(answer));
276 if (n < 0)
277 {
278 if (tTd(8, 1))
279 sm_dprintf("getmxrr: res_search(%s) failed (errno=%d, h_errno=%d)\n",
280 host, errno, h_errno);
281 switch (h_errno)
282 {
283 case NO_DATA:
284 trycanon = true;
285 /* FALLTHROUGH */
286
287 case NO_RECOVERY:
288 /* no MX data on this host */
289 goto punt;
290
291 case HOST_NOT_FOUND:
292 # if BROKEN_RES_SEARCH
293 case 0: /* Ultrix resolver retns failure w/ h_errno=0 */
294 # endif /* BROKEN_RES_SEARCH */
295 /* host doesn't exist in DNS; might be in /etc/hosts */
296 trycanon = true;
297 *rcode = EX_NOHOST;
298 goto punt;
299
300 case TRY_AGAIN:
301 case -1:
302 /* couldn't connect to the name server */
303 if (fallbackMX != NULL)
304 {
305 /* name server is hosed -- push to fallback */
306 return fallbackmxrr(nmx, prefs, mxhosts);
307 }
308 /* it might come up later; better queue it up */
309 *rcode = EX_TEMPFAIL;
310 break;
311
312 default:
313 syserr("getmxrr: res_search (%s) failed with impossible h_errno (%d)",
314 host, h_errno);
315 *rcode = EX_OSERR;
316 break;
317 }
318
319 /* irreconcilable differences */
320 return -1;
321 }
322
323 /* avoid problems after truncation in tcp packets */
324 if (n > sizeof(answer))
325 n = sizeof(answer);
326
327 /* find first satisfactory answer */
328 hp = (HEADER *)&answer;
329 cp = (unsigned char *)&answer + HFIXEDSZ;
330 eom = (unsigned char *)&answer + n;
331 for (qdcount = ntohs((unsigned short) hp->qdcount);
332 qdcount--;
333 cp += n + QFIXEDSZ)
334 {
335 if ((n = dn_skipname(cp, eom)) < 0)
336 goto punt;
337 }
338
339 /* NOTE: see definition of MXHostBuf! */
340 buflen = sizeof(MXHostBuf) - 1;
341 SM_ASSERT(buflen > 0);
342 bp = MXHostBuf;
343 ancount = ntohs((unsigned short) hp->ancount);
344
345 /* See RFC 1035 for layout of RRs. */
346 /* XXX leave room for FallbackMX ? */
347 while (--ancount >= 0 && cp < eom && nmx < MAXMXHOSTS - 1)
348 {
349 if ((n = dn_expand((unsigned char *)&answer, eom, cp,
350 (RES_UNC_T) bp, buflen)) < 0)
351 break;
352 cp += n;
353 GETSHORT(type, cp);
354 cp += INT16SZ; /* skip over class */
355 GETLONG(ttl, cp);
356 GETSHORT(n, cp); /* rdlength */
357 if (type != T_MX)
358 {
359 if (tTd(8, 8) || _res.options & RES_DEBUG)
360 sm_dprintf("unexpected answer type %d, size %d\n",
361 type, n);
362 cp += n;
363 continue;
364 }
365 GETSHORT(pref, cp);
366 if ((n = dn_expand((unsigned char *)&answer, eom, cp,
367 (RES_UNC_T) bp, buflen)) < 0)
368 break;
369 cp += n;
370 n = strlen(bp);
371 # if 0
372 /* Can this happen? */
373 if (n == 0)
374 {
375 if (LogLevel > 4)
376 sm_syslog(LOG_ERR, NOQID,
377 "MX records for %s contain empty string",
378 host);
379 continue;
380 }
381 # endif /* 0 */
382 if (wordinclass(bp, 'w'))
383 {
384 if (tTd(8, 3))
385 sm_dprintf("found localhost (%s) in MX list, pref=%d\n",
386 bp, pref);
387 if (droplocalhost)
388 {
389 if (!seenlocal || pref < localpref)
390 localpref = pref;
391 seenlocal = true;
392 continue;
393 }
394 weight[nmx] = 0;
395 }
396 else
397 weight[nmx] = mxrand(bp);
398 prefs[nmx] = pref;
399 mxhosts[nmx++] = bp;
400 bp += n;
401 if (bp[-1] != '.')
402 {
403 *bp++ = '.';
404 n++;
405 }
406 *bp++ = '\0';
407 if (buflen < n + 1)
408 {
409 /* don't want to wrap buflen */
410 break;
411 }
412 buflen -= n + 1;
413 }
414
415 /* return only one TTL entry, that should be sufficient */
416 if (ttl > 0 && pttl != NULL)
417 *pttl = ttl;
418
419 /* sort the records */
420 for (i = 0; i < nmx; i++)
421 {
422 for (j = i + 1; j < nmx; j++)
423 {
424 if (prefs[i] > prefs[j] ||
425 (prefs[i] == prefs[j] && weight[i] > weight[j]))
426 {
427 register int temp;
428 register char *temp1;
429
430 temp = prefs[i];
431 prefs[i] = prefs[j];
432 prefs[j] = temp;
433 temp1 = mxhosts[i];
434 mxhosts[i] = mxhosts[j];
435 mxhosts[j] = temp1;
436 temp = weight[i];
437 weight[i] = weight[j];
438 weight[j] = temp;
439 }
440 }
441 if (seenlocal && prefs[i] >= localpref)
442 {
443 /* truncate higher preference part of list */
444 nmx = i;
445 }
446 }
447
448 /* delete duplicates from list (yes, some bozos have duplicates) */
449 for (i = 0; i < nmx - 1; )
450 {
451 if (sm_strcasecmp(mxhosts[i], mxhosts[i + 1]) != 0)
452 i++;
453 else
454 {
455 /* compress out duplicate */
456 for (j = i + 1; j < nmx; j++)
457 {
458 mxhosts[j] = mxhosts[j + 1];
459 prefs[j] = prefs[j + 1];
460 }
461 nmx--;
462 }
463 }
464
465 if (nmx == 0)
466 {
467 punt:
468 if (seenlocal)
469 {
470 struct hostent *h = NULL;
471
472 /*
473 ** If we have deleted all MX entries, this is
474 ** an error -- we should NEVER send to a host that
475 ** has an MX, and this should have been caught
476 ** earlier in the config file.
477 **
478 ** Some sites prefer to go ahead and try the
479 ** A record anyway; that case is handled by
480 ** setting TryNullMXList. I believe this is a
481 ** bad idea, but it's up to you....
482 */
483
484 if (TryNullMXList)
485 {
486 SM_SET_H_ERRNO(0);
487 errno = 0;
488 h = sm_gethostbyname(host, AF_INET);
489 if (h == NULL)
490 {
491 if (errno == ETIMEDOUT ||
492 h_errno == TRY_AGAIN ||
493 (errno == ECONNREFUSED &&
494 UseNameServer))
495 {
496 *rcode = EX_TEMPFAIL;
497 return -1;
498 }
499 # if NETINET6
500 SM_SET_H_ERRNO(0);
501 errno = 0;
502 h = sm_gethostbyname(host, AF_INET6);
503 if (h == NULL &&
504 (errno == ETIMEDOUT ||
505 h_errno == TRY_AGAIN ||
506 (errno == ECONNREFUSED &&
507 UseNameServer)))
508 {
509 *rcode = EX_TEMPFAIL;
510 return -1;
511 }
512 # endif /* NETINET6 */
513 }
514 }
515
516 if (h == NULL)
517 {
518 *rcode = EX_CONFIG;
519 syserr("MX list for %s points back to %s",
520 host, MyHostName);
521 return -1;
522 }
523 # if NETINET6
524 freehostent(h);
525 h = NULL;
526 # endif /* NETINET6 */
527 }
528 if (strlen(host) >= sizeof(MXHostBuf))
529 {
530 *rcode = EX_CONFIG;
531 syserr("Host name %s too long",
532 shortenstring(host, MAXSHORTSTR));
533 return -1;
534 }
535 (void) sm_strlcpy(MXHostBuf, host, sizeof(MXHostBuf));
536 mxhosts[0] = MXHostBuf;
537 prefs[0] = 0;
538 if (host[0] == '[')
539 {
540 register char *p;
541 # if NETINET6
542 struct sockaddr_in6 tmp6;
543 # endif /* NETINET6 */
544
545 /* this may be an MX suppression-style address */
546 p = strchr(MXHostBuf, ']');
547 if (p != NULL)
548 {
549 *p = '\0';
550
551 if (inet_addr(&MXHostBuf[1]) != INADDR_NONE)
552 {
553 nmx++;
554 *p = ']';
555 }
556 # if NETINET6
557 else if (anynet_pton(AF_INET6, &MXHostBuf[1],
558 &tmp6.sin6_addr) == 1)
559 {
560 nmx++;
561 *p = ']';
562 }
563 # endif /* NETINET6 */
564 else
565 {
566 trycanon = true;
567 mxhosts[0]++;
568 }
569 }
570 }
571 if (trycanon &&
572 getcanonname(mxhosts[0], sizeof(MXHostBuf) - 2, false, pttl))
573 {
574 /* XXX MXHostBuf == "" ? is that possible? */
575 bp = &MXHostBuf[strlen(MXHostBuf)];
576 if (bp[-1] != '.')
577 {
578 *bp++ = '.';
579 *bp = '\0';
580 }
581 nmx = 1;
582 }
583 }
584
585 /* if we have a default lowest preference, include that */
586 if (fallbackMX != NULL && !seenlocal)
587 {
588 nmx = fallbackmxrr(nmx, prefs, mxhosts);
589 }
590 return nmx;
591 }
592 /*
593 ** MXRAND -- create a randomizer for equal MX preferences
594 **
595 ** If two MX hosts have equal preferences we want to randomize
596 ** the selection. But in order for signatures to be the same,
597 ** we need to randomize the same way each time. This function
598 ** computes a pseudo-random hash function from the host name.
599 **
600 ** Parameters:
601 ** host -- the name of the host.
602 **
603 ** Returns:
604 ** A random but repeatable value based on the host name.
605 */
606
607 static int
mxrand(host)608 mxrand(host)
609 register char *host;
610 {
611 int hfunc;
612 static unsigned int seed;
613
614 if (seed == 0)
615 {
616 seed = (int) curtime() & 0xffff;
617 if (seed == 0)
618 seed++;
619 }
620
621 if (tTd(17, 9))
622 sm_dprintf("mxrand(%s)", host);
623
624 hfunc = seed;
625 while (*host != '\0')
626 {
627 int c = *host++;
628
629 if (isascii(c) && isupper(c))
630 c = tolower(c);
631 hfunc = ((hfunc << 1) ^ c) % 2003;
632 }
633
634 hfunc &= 0xff;
635 hfunc++;
636
637 if (tTd(17, 9))
638 sm_dprintf(" = %d\n", hfunc);
639 return hfunc;
640 }
641 /*
642 ** BESTMX -- find the best MX for a name
643 **
644 ** This is really a hack, but I don't see any obvious way
645 ** to generalize it at the moment.
646 */
647
648 /* ARGSUSED3 */
649 char *
bestmx_map_lookup(map,name,av,statp)650 bestmx_map_lookup(map, name, av, statp)
651 MAP *map;
652 char *name;
653 char **av;
654 int *statp;
655 {
656 int nmx;
657 int saveopts = _res.options;
658 int i;
659 ssize_t len = 0;
660 char *result;
661 char *mxhosts[MAXMXHOSTS + 1];
662 #if _FFR_BESTMX_BETTER_TRUNCATION
663 char *buf;
664 #else /* _FFR_BESTMX_BETTER_TRUNCATION */
665 char *p;
666 char buf[PSBUFSIZE / 2];
667 #endif /* _FFR_BESTMX_BETTER_TRUNCATION */
668
669 _res.options &= ~(RES_DNSRCH|RES_DEFNAMES);
670 nmx = getmxrr(name, mxhosts, NULL, false, statp, false, NULL);
671 _res.options = saveopts;
672 if (nmx <= 0)
673 return NULL;
674 if (bitset(MF_MATCHONLY, map->map_mflags))
675 return map_rewrite(map, name, strlen(name), NULL);
676 if ((map->map_coldelim == '\0') || (nmx == 1))
677 return map_rewrite(map, mxhosts[0], strlen(mxhosts[0]), av);
678
679 /*
680 ** We were given a -z flag (return all MXs) and there are multiple
681 ** ones. We need to build them all into a list.
682 */
683
684 #if _FFR_BESTMX_BETTER_TRUNCATION
685 for (i = 0; i < nmx; i++)
686 {
687 if (strchr(mxhosts[i], map->map_coldelim) != NULL)
688 {
689 syserr("bestmx_map_lookup: MX host %.64s includes map delimiter character 0x%02X",
690 mxhosts[i], map->map_coldelim);
691 return NULL;
692 }
693 len += strlen(mxhosts[i]) + 1;
694 if (len < 0)
695 {
696 len -= strlen(mxhosts[i]) + 1;
697 break;
698 }
699 }
700 buf = (char *) sm_malloc(len);
701 if (buf == NULL)
702 {
703 *statp = EX_UNAVAILABLE;
704 return NULL;
705 }
706 *buf = '\0';
707 for (i = 0; i < nmx; i++)
708 {
709 int end;
710
711 end = sm_strlcat(buf, mxhosts[i], len);
712 if (i != nmx && end + 1 < len)
713 {
714 buf[end] = map->map_coldelim;
715 buf[end + 1] = '\0';
716 }
717 }
718
719 /* Cleanly truncate for rulesets */
720 truncate_at_delim(buf, PSBUFSIZE / 2, map->map_coldelim);
721 #else /* _FFR_BESTMX_BETTER_TRUNCATION */
722 p = buf;
723 for (i = 0; i < nmx; i++)
724 {
725 size_t slen;
726
727 if (strchr(mxhosts[i], map->map_coldelim) != NULL)
728 {
729 syserr("bestmx_map_lookup: MX host %.64s includes map delimiter character 0x%02X",
730 mxhosts[i], map->map_coldelim);
731 return NULL;
732 }
733 slen = strlen(mxhosts[i]);
734 if (len + slen + 2 > sizeof(buf))
735 break;
736 if (i > 0)
737 {
738 *p++ = map->map_coldelim;
739 len++;
740 }
741 (void) sm_strlcpy(p, mxhosts[i], sizeof(buf) - len);
742 p += slen;
743 len += slen;
744 }
745 #endif /* _FFR_BESTMX_BETTER_TRUNCATION */
746
747 result = map_rewrite(map, buf, len, av);
748 #if _FFR_BESTMX_BETTER_TRUNCATION
749 sm_free(buf);
750 #endif /* _FFR_BESTMX_BETTER_TRUNCATION */
751 return result;
752 }
753 /*
754 ** DNS_GETCANONNAME -- get the canonical name for named host using DNS
755 **
756 ** This algorithm tries to be smart about wildcard MX records.
757 ** This is hard to do because DNS doesn't tell is if we matched
758 ** against a wildcard or a specific MX.
759 **
760 ** We always prefer A & CNAME records, since these are presumed
761 ** to be specific.
762 **
763 ** If we match an MX in one pass and lose it in the next, we use
764 ** the old one. For example, consider an MX matching *.FOO.BAR.COM.
765 ** A hostname bletch.foo.bar.com will match against this MX, but
766 ** will stop matching when we try bletch.bar.com -- so we know
767 ** that bletch.foo.bar.com must have been right. This fails if
768 ** there was also an MX record matching *.BAR.COM, but there are
769 ** some things that just can't be fixed.
770 **
771 ** Parameters:
772 ** host -- a buffer containing the name of the host.
773 ** This is a value-result parameter.
774 ** hbsize -- the size of the host buffer.
775 ** trymx -- if set, try MX records as well as A and CNAME.
776 ** statp -- pointer to place to store status.
777 ** pttl -- pointer to return TTL (can be NULL).
778 **
779 ** Returns:
780 ** true -- if the host matched.
781 ** false -- otherwise.
782 */
783
784 bool
dns_getcanonname(host,hbsize,trymx,statp,pttl)785 dns_getcanonname(host, hbsize, trymx, statp, pttl)
786 char *host;
787 int hbsize;
788 bool trymx;
789 int *statp;
790 int *pttl;
791 {
792 register unsigned char *eom, *ap;
793 register char *cp;
794 register int n;
795 HEADER *hp;
796 querybuf answer;
797 int ancount, qdcount;
798 int ret;
799 char **domain;
800 int type;
801 int ttl = 0;
802 char **dp;
803 char *mxmatch;
804 bool amatch;
805 bool gotmx = false;
806 int qtype;
807 int initial;
808 int loopcnt;
809 char nbuf[SM_MAX(MAXPACKET, MAXDNAME*2+2)];
810 char *searchlist[MAXDNSRCH + 2];
811
812 if (tTd(8, 2))
813 sm_dprintf("dns_getcanonname(%s, trymx=%d)\n", host, trymx);
814
815 if ((_res.options & RES_INIT) == 0 && res_init() == -1)
816 {
817 *statp = EX_UNAVAILABLE;
818 return false;
819 }
820
821 *statp = EX_OK;
822
823 /*
824 ** Initialize domain search list. If there is at least one
825 ** dot in the name, search the unmodified name first so we
826 ** find "vse.CS" in Czechoslovakia instead of in the local
827 ** domain (e.g., vse.CS.Berkeley.EDU). Note that there is no
828 ** longer a country named Czechoslovakia but this type of problem
829 ** is still present.
830 **
831 ** Older versions of the resolver could create this
832 ** list by tearing apart the host name.
833 */
834
835 loopcnt = 0;
836 cnameloop:
837 /* Check for dots in the name */
838 for (cp = host, n = 0; *cp != '\0'; cp++)
839 if (*cp == '.')
840 n++;
841
842 /*
843 ** Build the search list.
844 ** If there is at least one dot in name, start with a null
845 ** domain to search the unmodified name first.
846 ** If name does not end with a dot and search up local domain
847 ** tree desired, append each local domain component to the
848 ** search list; if name contains no dots and default domain
849 ** name is desired, append default domain name to search list;
850 ** else if name ends in a dot, remove that dot.
851 */
852
853 dp = searchlist;
854 if (n > 0)
855 *dp++ = "";
856 if (n >= 0 && *--cp != '.' && bitset(RES_DNSRCH, _res.options))
857 {
858 /* make sure there are less than MAXDNSRCH domains */
859 for (domain = RES_DNSRCH_VARIABLE, ret = 0;
860 *domain != NULL && ret < MAXDNSRCH;
861 ret++)
862 *dp++ = *domain++;
863 }
864 else if (n == 0 && bitset(RES_DEFNAMES, _res.options))
865 {
866 *dp++ = _res.defdname;
867 }
868 else if (*cp == '.')
869 {
870 *cp = '\0';
871 }
872 *dp = NULL;
873
874 /*
875 ** Now loop through the search list, appending each domain in turn
876 ** name and searching for a match.
877 */
878
879 mxmatch = NULL;
880 initial = T_A;
881 # if NETINET6
882 if (InetMode == AF_INET6)
883 initial = T_AAAA;
884 # endif /* NETINET6 */
885 qtype = initial;
886
887 for (dp = searchlist; *dp != NULL; )
888 {
889 if (qtype == initial)
890 gotmx = false;
891 if (tTd(8, 5))
892 sm_dprintf("dns_getcanonname: trying %s.%s (%s)\n",
893 host, *dp,
894 # if NETINET6
895 qtype == T_AAAA ? "AAAA" :
896 # endif /* NETINET6 */
897 qtype == T_A ? "A" :
898 qtype == T_MX ? "MX" :
899 "???");
900 errno = 0;
901 ret = res_querydomain(host, *dp, C_IN, qtype,
902 answer.qb2, sizeof(answer.qb2));
903 if (ret <= 0)
904 {
905 int save_errno = errno;
906
907 if (tTd(8, 7))
908 sm_dprintf("\tNO: errno=%d, h_errno=%d\n",
909 save_errno, h_errno);
910
911 if (save_errno == ECONNREFUSED || h_errno == TRY_AGAIN)
912 {
913 /*
914 ** the name server seems to be down or broken.
915 */
916
917 SM_SET_H_ERRNO(TRY_AGAIN);
918 if (**dp == '\0')
919 {
920 if (*statp == EX_OK)
921 *statp = EX_TEMPFAIL;
922 goto nexttype;
923 }
924 *statp = EX_TEMPFAIL;
925
926 if (WorkAroundBrokenAAAA)
927 {
928 /*
929 ** Only return if not TRY_AGAIN as an
930 ** attempt with a different qtype may
931 ** succeed (res_querydomain() calls
932 ** res_query() calls res_send() which
933 ** sets errno to ETIMEDOUT if the
934 ** nameservers could be contacted but
935 ** didn't give an answer).
936 */
937
938 if (save_errno != ETIMEDOUT)
939 return false;
940 }
941 else
942 return false;
943 }
944
945 nexttype:
946 if (h_errno != HOST_NOT_FOUND)
947 {
948 /* might have another type of interest */
949 # if NETINET6
950 if (qtype == T_AAAA)
951 {
952 qtype = T_A;
953 continue;
954 }
955 else
956 # endif /* NETINET6 */
957 if (qtype == T_A && !gotmx &&
958 (trymx || **dp == '\0'))
959 {
960 qtype = T_MX;
961 continue;
962 }
963 }
964
965 /* definite no -- try the next domain */
966 dp++;
967 qtype = initial;
968 continue;
969 }
970 else if (tTd(8, 7))
971 sm_dprintf("\tYES\n");
972
973 /* avoid problems after truncation in tcp packets */
974 if (ret > sizeof(answer))
975 ret = sizeof(answer);
976 SM_ASSERT(ret >= 0);
977
978 /*
979 ** Appear to have a match. Confirm it by searching for A or
980 ** CNAME records. If we don't have a local domain
981 ** wild card MX record, we will accept MX as well.
982 */
983
984 hp = (HEADER *) &answer;
985 ap = (unsigned char *) &answer + HFIXEDSZ;
986 eom = (unsigned char *) &answer + ret;
987
988 /* skip question part of response -- we know what we asked */
989 for (qdcount = ntohs((unsigned short) hp->qdcount);
990 qdcount--;
991 ap += ret + QFIXEDSZ)
992 {
993 if ((ret = dn_skipname(ap, eom)) < 0)
994 {
995 if (tTd(8, 20))
996 sm_dprintf("qdcount failure (%d)\n",
997 ntohs((unsigned short) hp->qdcount));
998 *statp = EX_SOFTWARE;
999 return false; /* ???XXX??? */
1000 }
1001 }
1002
1003 amatch = false;
1004 for (ancount = ntohs((unsigned short) hp->ancount);
1005 --ancount >= 0 && ap < eom;
1006 ap += n)
1007 {
1008 n = dn_expand((unsigned char *) &answer, eom, ap,
1009 (RES_UNC_T) nbuf, sizeof(nbuf));
1010 if (n < 0)
1011 break;
1012 ap += n;
1013 GETSHORT(type, ap);
1014 ap += INT16SZ; /* skip over class */
1015 GETLONG(ttl, ap);
1016 GETSHORT(n, ap); /* rdlength */
1017 switch (type)
1018 {
1019 case T_MX:
1020 gotmx = true;
1021 if (**dp != '\0' && HasWildcardMX)
1022 {
1023 /*
1024 ** If we are using MX matches and have
1025 ** not yet gotten one, save this one
1026 ** but keep searching for an A or
1027 ** CNAME match.
1028 */
1029
1030 if (trymx && mxmatch == NULL)
1031 mxmatch = *dp;
1032 continue;
1033 }
1034
1035 /*
1036 ** If we did not append a domain name, this
1037 ** must have been a canonical name to start
1038 ** with. Even if we did append a domain name,
1039 ** in the absence of a wildcard MX this must
1040 ** still be a real MX match.
1041 ** Such MX matches are as good as an A match,
1042 ** fall through.
1043 */
1044
1045 # if NETINET6
1046 /* FALLTHROUGH */
1047 case T_AAAA:
1048 # endif /* NETINET6 */
1049 case T_A:
1050 /* Flag that a good match was found */
1051 amatch = true;
1052
1053 /* continue in case a CNAME also exists */
1054 continue;
1055
1056 case T_CNAME:
1057 if (DontExpandCnames)
1058 {
1059 /* got CNAME -- guaranteed canonical */
1060 amatch = true;
1061 break;
1062 }
1063
1064 if (loopcnt++ > MAXCNAMEDEPTH)
1065 {
1066 /*XXX should notify postmaster XXX*/
1067 message("DNS failure: CNAME loop for %s",
1068 host);
1069 if (CurEnv->e_message == NULL)
1070 {
1071 char ebuf[MAXLINE];
1072
1073 (void) sm_snprintf(ebuf,
1074 sizeof(ebuf),
1075 "Deferred: DNS failure: CNAME loop for %.100s",
1076 host);
1077 CurEnv->e_message =
1078 sm_rpool_strdup_x(
1079 CurEnv->e_rpool, ebuf);
1080 }
1081 SM_SET_H_ERRNO(NO_RECOVERY);
1082 *statp = EX_CONFIG;
1083 return false;
1084 }
1085
1086 /* value points at name */
1087 if ((ret = dn_expand((unsigned char *)&answer,
1088 eom, ap, (RES_UNC_T) nbuf,
1089 sizeof(nbuf))) < 0)
1090 break;
1091 (void) sm_strlcpy(host, nbuf, hbsize);
1092
1093 /*
1094 ** RFC 1034 section 3.6 specifies that CNAME
1095 ** should point at the canonical name -- but
1096 ** urges software to try again anyway.
1097 */
1098
1099 goto cnameloop;
1100
1101 default:
1102 /* not a record of interest */
1103 continue;
1104 }
1105 }
1106
1107 if (amatch)
1108 {
1109 /*
1110 ** Got a good match -- either an A, CNAME, or an
1111 ** exact MX record. Save it and get out of here.
1112 */
1113
1114 mxmatch = *dp;
1115 break;
1116 }
1117
1118 /*
1119 ** Nothing definitive yet.
1120 ** If this was a T_A query and we haven't yet found a MX
1121 ** match, try T_MX if allowed to do so.
1122 ** Otherwise, try the next domain.
1123 */
1124
1125 # if NETINET6
1126 if (qtype == T_AAAA)
1127 qtype = T_A;
1128 else
1129 # endif /* NETINET6 */
1130 if (qtype == T_A && !gotmx && (trymx || **dp == '\0'))
1131 qtype = T_MX;
1132 else
1133 {
1134 qtype = initial;
1135 dp++;
1136 }
1137 }
1138
1139 /* if nothing was found, we are done */
1140 if (mxmatch == NULL)
1141 {
1142 if (*statp == EX_OK)
1143 *statp = EX_NOHOST;
1144 return false;
1145 }
1146
1147 /*
1148 ** Create canonical name and return.
1149 ** If saved domain name is null, name was already canonical.
1150 ** Otherwise append the saved domain name.
1151 */
1152
1153 (void) sm_snprintf(nbuf, sizeof(nbuf), "%.*s%s%.*s", MAXDNAME, host,
1154 *mxmatch == '\0' ? "" : ".",
1155 MAXDNAME, mxmatch);
1156 (void) sm_strlcpy(host, nbuf, hbsize);
1157 if (tTd(8, 5))
1158 sm_dprintf("dns_getcanonname: %s\n", host);
1159 *statp = EX_OK;
1160
1161 /* return only one TTL entry, that should be sufficient */
1162 if (ttl > 0 && pttl != NULL)
1163 *pttl = ttl;
1164 return true;
1165 }
1166 #endif /* NAMED_BIND */
1167