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