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