source: trunk/third/sendmail/sendmail/domain.c @ 19204

Revision 19204, 28.0 KB checked in by zacheiss, 21 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r19203, which included commits to RCS files with non-trunk default branches.
Line 
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
17SM_RCSID("@(#)$Id: domain.c,v 1.1.1.1 2003-04-08 15:08:48 zacheiss Exp $ (with name server)")
18#else /* NAMED_BIND */
19SM_RCSID("@(#)$Id: domain.c,v 1.1.1.1 2003-04-08 15:08:48 zacheiss 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
40typedef 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
50static 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
79static char     *gethostalias __P((char *));
80static int      mxrand __P((char *));
81static 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
103int NumFallBackMXHosts = 0;     /* Number of fallback MX hosts (after MX expansion) */
104static char *fbhosts[MAXMXHOSTS + 1];
105
106int
107getfallbackmxrr(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
158static int
159fallbackmxrr(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
204int
205getmxrr(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        {
464punt:
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
604static int
605mxrand(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 */
646char *
647bestmx_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
787bool
788dns_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;
839cnameloop:
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
964nexttype:
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
1197static char *
1198gethostalias(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 */
Note: See TracBrowser for help on using the repository browser.