BSD 4_4_Lite2 release
[unix-history] / usr / src / usr.sbin / sendmail / src / domain.c
index 6a662f3..61570ed 100644 (file)
 /*
 /*
- * Copyright (c) 1986 Eric P. Allman
- * Copyright (c) 1988 Regents of the University of California.
- * All rights reserved.
+ * Copyright (c) 1986, 1995 Eric P. Allman
+ * Copyright (c) 1988, 1993
+ *     The Regents of the University of California.  All rights reserved.
  *
  *
- * Redistribution and use in source and binary forms are permitted
- * provided that the above copyright notice and this paragraph are
- * duplicated in all such forms and that any documentation,
- * advertising materials, and other materials related to such
- * distribution and use acknowledge that the software was developed
- * by the University of California, Berkeley.  The name of the
- * University may not be used to endorse or promote products derived
- * from this software without specific prior written permission.
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
- * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
  */
 
  */
 
-#include <sendmail.h>
+#include "sendmail.h"
 
 #ifndef lint
 
 #ifndef lint
-#ifdef NAMED_BIND
-static char sccsid[] = "@(#)domain.c   5.20 (Berkeley) %G% (with name server)";
+#if NAMED_BIND
+static char sccsid[] = "@(#)domain.c   8.47 (Berkeley) 6/20/95 (with name server)";
 #else
 #else
-static char sccsid[] = "@(#)domain.c   5.20 (Berkeley) %G% (without name server)";
+static char sccsid[] = "@(#)domain.c   8.47 (Berkeley) 6/20/95 (without name server)";
 #endif
 #endif /* not lint */
 
 #endif
 #endif /* not lint */
 
-#ifdef NAMED_BIND
+#if NAMED_BIND
 
 
-#include <sys/param.h>
 #include <errno.h>
 #include <errno.h>
-#include <arpa/nameser.h>
 #include <resolv.h>
 #include <resolv.h>
-#include <netdb.h>
 
 
-typedef union {
-       HEADER qb1;
-       char qb2[PACKETSZ];
+typedef union
+{
+       HEADER  qb1;
+       u_char  qb2[PACKETSZ];
 } querybuf;
 
 } querybuf;
 
-static char hostbuf[MAXMXHOSTS*PACKETSZ];
+static char    MXHostBuf[MAXMXHOSTS*PACKETSZ];
+
+#ifndef MAXDNSRCH
+#define MAXDNSRCH      6       /* number of possible domains to search */
+#endif
+
+#ifndef MAX
+#define MAX(a, b)      ((a) > (b) ? (a) : (b))
+#endif
+
+#ifndef NO_DATA
+# define NO_DATA       NO_ADDRESS
+#endif
+
+#ifndef HFIXEDSZ
+# define HFIXEDSZ      12      /* sizeof(HEADER) */
+#endif
+
+#define MAXCNAMEDEPTH  10      /* maximum depth of CNAME recursion */
 
 
-getmxrr(host, mxhosts, localhost, rcode)
-       char *host, **mxhosts, *localhost;
+#if defined(__RES) && (__RES >= 19940415)
+# define RES_UNC_T     char *
+#else
+# define RES_UNC_T     u_char *
+#endif
+\f/*
+**  GETMXRR -- get MX resource records for a domain
+**
+**     Parameters:
+**             host -- the name of the host to MX.
+**             mxhosts -- a pointer to a return buffer of MX records.
+**             droplocalhost -- If TRUE, all MX records less preferred
+**                     than the local host (as determined by $=w) will
+**                     be discarded.
+**             rcode -- a pointer to an EX_ status code.
+**
+**     Returns:
+**             The number of MX records found.
+**             -1 if there is an internal failure.
+**             If no MX records are found, mxhosts[0] is set to host
+**                     and 1 is returned.
+*/
+
+int
+getmxrr(host, mxhosts, droplocalhost, rcode)
+       char *host;
+       char **mxhosts;
+       bool droplocalhost;
        int *rcode;
 {
        int *rcode;
 {
-       extern int h_errno;
        register u_char *eom, *cp;
        register u_char *eom, *cp;
-       register int i, j, n, nmx;
+       register int i, j, n;
+       int nmx = 0;
        register char *bp;
        HEADER *hp;
        querybuf answer;
        register char *bp;
        HEADER *hp;
        querybuf answer;
-       int ancount, qdcount, buflen, seenlocal;
-       u_short pref, localpref, type, prefer[MAXMXHOSTS];
+       int ancount, qdcount, buflen;
+       bool seenlocal = FALSE;
+       u_short pref, type;
+       u_short localpref = 256;
+       char *fallbackMX = FallBackMX;
+       static bool firsttime = TRUE;
+       bool trycanon = FALSE;
+       int (*resfunc)();
+       extern int res_query(), res_search();
+       u_short prefer[MAXMXHOSTS];
+       int weight[MAXMXHOSTS];
+       extern bool getcanonname();
+
+       if (tTd(8, 2))
+               printf("getmxrr(%s, droplocalhost=%d)\n", host, droplocalhost);
+
+       if (fallbackMX != NULL)
+       {
+               if (firsttime &&
+                   res_query(FallBackMX, C_IN, T_A,
+                             (u_char *) &answer, sizeof answer) < 0)
+               {
+                       /* this entry is bogus */
+                       fallbackMX = FallBackMX = NULL;
+               }
+               else if (droplocalhost && wordinclass(fallbackMX, 'w'))
+               {
+                       /* don't use fallback for this pass */
+                       fallbackMX = NULL;
+               }
+               firsttime = FALSE;
+       }
+
+       *rcode = EX_OK;
+
+       /* efficiency hack -- numeric or non-MX lookups */
+       if (host[0] == '[')
+               goto punt;
+
+       /*
+       **  If we don't have MX records in our host switch, don't
+       **  try for MX records.  Note that this really isn't "right",
+       **  since we might be set up to try NIS first and then DNS;
+       **  if the host is found in NIS we really shouldn't be doing
+       **  MX lookups.  However, that should be a degenerate case.
+       */
+
+       if (!UseNameServer)
+               goto punt;
+       if (HasWildcardMX && ConfigLevel >= 6)
+               resfunc = res_query;
+       else
+               resfunc = res_search;
 
        errno = 0;
 
        errno = 0;
-       n = res_search(host, C_IN, T_MX, (char *)&answer, sizeof(answer));
+       n = (*resfunc)(host, C_IN, T_MX, (u_char *) &answer, sizeof(answer));
        if (n < 0)
        {
                if (tTd(8, 1))
        if (n < 0)
        {
                if (tTd(8, 1))
-                       printf("getmxrr: res_search failed (errno=%d, h_errno=%d)\n",
-                           errno, h_errno);
+                       printf("getmxrr: res_search(%s) failed (errno=%d, h_errno=%d)\n",
+                           (host == NULL) ? "<NULL>" : host, errno, h_errno);
                switch (h_errno)
                {
                  case NO_DATA:
                switch (h_errno)
                {
                  case NO_DATA:
+                       trycanon = TRUE;
+                       /* fall through */
+
                  case NO_RECOVERY:
                        /* no MX data on this host */
                        goto punt;
 
                  case HOST_NOT_FOUND:
                  case NO_RECOVERY:
                        /* no MX data on this host */
                        goto punt;
 
                  case HOST_NOT_FOUND:
-                       /* the host just doesn't exist */
+#if BROKEN_RES_SEARCH
+                 case 0:       /* Ultrix resolver retns failure w/ h_errno=0 */
+#endif
+                       /* host doesn't exist in DNS; might be in /etc/hosts */
                        *rcode = EX_NOHOST;
                        *rcode = EX_NOHOST;
-                       break;
+                       goto punt;
 
                  case TRY_AGAIN:
                        /* couldn't connect to the name server */
 
                  case TRY_AGAIN:
                        /* couldn't connect to the name server */
-                       if (!UseNameServer && errno == ECONNREFUSED)
-                               goto punt;
-
                        /* it might come up later; better queue it up */
                        *rcode = EX_TEMPFAIL;
                        break;
                        /* it might come up later; better queue it up */
                        *rcode = EX_TEMPFAIL;
                        break;
+
+                 default:
+                       syserr("getmxrr: res_search (%s) failed with impossible h_errno (%d)\n",
+                               host, h_errno);
+                       *rcode = EX_OSERR;
+                       break;
                }
 
                /* irreconcilable differences */
                }
 
                /* irreconcilable differences */
@@ -89,56 +207,68 @@ getmxrr(host, mxhosts, localhost, rcode)
 
        /* find first satisfactory answer */
        hp = (HEADER *)&answer;
 
        /* find first satisfactory answer */
        hp = (HEADER *)&answer;
-       cp = (u_char *)&answer + sizeof(HEADER);
+       cp = (u_char *)&answer + HFIXEDSZ;
        eom = (u_char *)&answer + n;
        for (qdcount = ntohs(hp->qdcount); qdcount--; cp += n + QFIXEDSZ)
                if ((n = dn_skipname(cp, eom)) < 0)
                        goto punt;
        eom = (u_char *)&answer + n;
        for (qdcount = ntohs(hp->qdcount); qdcount--; cp += n + QFIXEDSZ)
                if ((n = dn_skipname(cp, eom)) < 0)
                        goto punt;
-       nmx = 0;
-       seenlocal = 0;
-       buflen = sizeof(hostbuf);
-       bp = hostbuf;
+       buflen = sizeof(MXHostBuf) - 1;
+       bp = MXHostBuf;
        ancount = ntohs(hp->ancount);
        ancount = ntohs(hp->ancount);
-       while (--ancount >= 0 && cp < eom && nmx < MAXMXHOSTS) {
-               if ((n = dn_expand((char *)&answer, eom, cp, bp, buflen)) < 0)
+       while (--ancount >= 0 && cp < eom && nmx < MAXMXHOSTS - 1)
+       {
+               if ((n = dn_expand((u_char *)&answer,
+                   eom, cp, (RES_UNC_T) bp, buflen)) < 0)
                        break;
                cp += n;
                GETSHORT(type, cp);
                        break;
                cp += n;
                GETSHORT(type, cp);
-               cp += sizeof(u_short) + sizeof(u_long);
+               cp += INT16SZ + INT32SZ;
                GETSHORT(n, cp);
                GETSHORT(n, cp);
-               if (type != T_MX)  {
-                       if (tTd(8, 1) || _res.options & RES_DEBUG)
+               if (type != T_MX)
+               {
+                       if (tTd(8, 8) || _res.options & RES_DEBUG)
                                printf("unexpected answer type %d, size %d\n",
                                    type, n);
                        cp += n;
                        continue;
                }
                GETSHORT(pref, cp);
                                printf("unexpected answer type %d, size %d\n",
                                    type, n);
                        cp += n;
                        continue;
                }
                GETSHORT(pref, cp);
-               if ((n = dn_expand((char *)&answer, eom, cp, bp, buflen)) < 0)
+               if ((n = dn_expand((u_char *)&answer, eom, cp,
+                                  (RES_UNC_T) bp, buflen)) < 0)
                        break;
                cp += n;
                        break;
                cp += n;
-               if (!strcasecmp(bp, localhost)) {
-                       if (seenlocal == 0 || pref < localpref)
+               if (droplocalhost && wordinclass(bp, 'w'))
+               {
+                       if (tTd(8, 3))
+                               printf("found localhost (%s) in MX list, pref=%d\n",
+                                       bp, pref);
+                       if (!seenlocal || pref < localpref)
                                localpref = pref;
                                localpref = pref;
-                       seenlocal = 1;
+                       seenlocal = TRUE;
                        continue;
                }
                        continue;
                }
+               weight[nmx] = mxrand(bp);
                prefer[nmx] = pref;
                mxhosts[nmx++] = bp;
                prefer[nmx] = pref;
                mxhosts[nmx++] = bp;
-               n = strlen(bp) + 1;
+               n = strlen(bp);
                bp += n;
                bp += n;
-               buflen -= n;
-       }
-       if (nmx == 0) {
-punt:          mxhosts[0] = strcpy(hostbuf, host);
-               return(1);
+               if (bp[-1] != '.')
+               {
+                       *bp++ = '.';
+                       n++;
+               }
+               *bp++ = '\0';
+               buflen -= n + 1;
        }
 
        /* sort the records */
        }
 
        /* sort the records */
-       for (i = 0; i < nmx; i++) {
-               for (j = i + 1; j < nmx; j++) {
+       for (i = 0; i < nmx; i++)
+       {
+               for (j = i + 1; j < nmx; j++)
+               {
                        if (prefer[i] > prefer[j] ||
                        if (prefer[i] > prefer[j] ||
-                           (prefer[i] == prefer[j] && rand() % 1 == 0)) {
+                           (prefer[i] == prefer[j] && weight[i] > weight[j]))
+                       {
                                register int temp;
                                register char *temp1;
 
                                register int temp;
                                register char *temp1;
 
@@ -148,130 +278,522 @@ punt:           mxhosts[0] = strcpy(hostbuf, host);
                                temp1 = mxhosts[i];
                                mxhosts[i] = mxhosts[j];
                                mxhosts[j] = temp1;
                                temp1 = mxhosts[i];
                                mxhosts[i] = mxhosts[j];
                                mxhosts[j] = temp1;
+                               temp = weight[i];
+                               weight[i] = weight[j];
+                               weight[j] = temp;
                        }
                }
                        }
                }
-               if (seenlocal && prefer[i] >= localpref) {
+               if (seenlocal && prefer[i] >= localpref)
+               {
+                       /* truncate higher preference part of list */
+                       nmx = i;
+               }
+       }
+
+       if (nmx == 0)
+       {
+punt:
+               if (seenlocal &&
+                   (!TryNullMXList || sm_gethostbyname(host) == NULL))
+               {
                        /*
                        /*
-                        * truncate higher pref part of list; if we're
-                        * the best choice left, we should have realized
-                        * awhile ago that this was a local delivery.
-                        */
-                       if (i == 0) {
-                               *rcode = EX_CONFIG;
-                               return(-1);
+                       **  If we have deleted all MX entries, this is
+                       **  an error -- we should NEVER send to a host that
+                       **  has an MX, and this should have been caught
+                       **  earlier in the config file.
+                       **
+                       **  Some sites prefer to go ahead and try the
+                       **  A record anyway; that case is handled by
+                       **  setting TryNullMXList.  I believe this is a
+                       **  bad idea, but it's up to you....
+                       */
+
+                       *rcode = EX_CONFIG;
+                       syserr("MX list for %s points back to %s",
+                               host, MyHostName);
+                       return -1;
+               }
+               strcpy(MXHostBuf, host);
+               mxhosts[0] = MXHostBuf;
+               if (host[0] == '[')
+               {
+                       register char *p;
+
+                       /* this may be an MX suppression-style address */
+                       p = strchr(MXHostBuf, ']');
+                       if (p != NULL)
+                       {
+                               *p = '\0';
+                               if (inet_addr(&MXHostBuf[1]) != -1)
+                                       *p = ']';
+                               else
+                               {
+                                       trycanon = TRUE;
+                                       mxhosts[0]++;
+                               }
                        }
                        }
-                       nmx = i;
-                       break;
                }
                }
+               if (trycanon &&
+                   getcanonname(mxhosts[0], sizeof MXHostBuf - 2, FALSE))
+               {
+                       bp = &MXHostBuf[strlen(MXHostBuf)];
+                       if (bp[-1] != '.')
+                       {
+                               *bp++ = '.';
+                               *bp = '\0';
+                       }
+                       nmx = 1;
+               }
+       }
+
+       /* if we have a default lowest preference, include that */
+       if (fallbackMX != NULL && !seenlocal)
+               mxhosts[nmx++] = fallbackMX;
+
+       return (nmx);
+}
+\f/*
+**  MXRAND -- create a randomizer for equal MX preferences
+**
+**     If two MX hosts have equal preferences we want to randomize
+**     the selection.  But in order for signatures to be the same,
+**     we need to randomize the same way each time.  This function
+**     computes a pseudo-random hash function from the host name.
+**
+**     Parameters:
+**             host -- the name of the host.
+**
+**     Returns:
+**             A random but repeatable value based on the host name.
+**
+**     Side Effects:
+**             none.
+*/
+
+int
+mxrand(host)
+       register char *host;
+{
+       int hfunc;
+       static unsigned int seed;
+
+       if (seed == 0)
+       {
+               seed = (int) curtime() & 0xffff;
+               if (seed == 0)
+                       seed++;
        }
        }
-       return(nmx);
+
+       if (tTd(17, 9))
+               printf("mxrand(%s)", host);
+
+       hfunc = seed;
+       while (*host != '\0')
+       {
+               int c = *host++;
+
+               if (isascii(c) && isupper(c))
+                       c = tolower(c);
+               hfunc = ((hfunc << 1) ^ c) % 2003;
+       }
+
+       hfunc &= 0xff;
+
+       if (tTd(17, 9))
+               printf(" = %d\n", hfunc);
+       return hfunc;
 }
 }
+\f/*
+**  BESTMX -- find the best MX for a name
+**
+**     This is really a hack, but I don't see any obvious way
+**     to generalize it at the moment.
+*/
 
 
-getcanonname(host, hbsize)
+char *
+bestmx_map_lookup(map, name, av, statp)
+       MAP *map;
+       char *name;
+       char **av;
+       int *statp;
+{
+       int nmx;
+       auto int rcode;
+       int saveopts = _res.options;
+       char *mxhosts[MAXMXHOSTS + 1];
+
+       _res.options &= ~(RES_DNSRCH|RES_DEFNAMES);
+       nmx = getmxrr(name, mxhosts, FALSE, &rcode);
+       _res.options = saveopts;
+       if (nmx <= 0)
+               return NULL;
+       if (bitset(MF_MATCHONLY, map->map_mflags))
+               return map_rewrite(map, name, strlen(name), NULL);
+       else
+               return map_rewrite(map, mxhosts[0], strlen(mxhosts[0]), av);
+}
+\f/*
+**  DNS_GETCANONNAME -- get the canonical name for named host using DNS
+**
+**     This algorithm tries to be smart about wildcard MX records.
+**     This is hard to do because DNS doesn't tell is if we matched
+**     against a wildcard or a specific MX.
+**     
+**     We always prefer A & CNAME records, since these are presumed
+**     to be specific.
+**
+**     If we match an MX in one pass and lose it in the next, we use
+**     the old one.  For example, consider an MX matching *.FOO.BAR.COM.
+**     A hostname bletch.foo.bar.com will match against this MX, but
+**     will stop matching when we try bletch.bar.com -- so we know
+**     that bletch.foo.bar.com must have been right.  This fails if
+**     there was also an MX record matching *.BAR.COM, but there are
+**     some things that just can't be fixed.
+**
+**     Parameters:
+**             host -- a buffer containing the name of the host.
+**                     This is a value-result parameter.
+**             hbsize -- the size of the host buffer.
+**             trymx -- if set, try MX records as well as A and CNAME.
+**             statp -- pointer to place to store status.
+**
+**     Returns:
+**             TRUE -- if the host matched.
+**             FALSE -- otherwise.
+*/
+
+bool
+dns_getcanonname(host, hbsize, trymx, statp)
        char *host;
        int hbsize;
        char *host;
        int hbsize;
+       bool trymx;
+       int *statp;
 {
 {
-       extern int h_errno;
-       register u_char *eom, *cp;
+       register u_char *eom, *ap;
+       register char *cp;
        register int n; 
        HEADER *hp;
        querybuf answer;
        register int n; 
        HEADER *hp;
        querybuf answer;
-       u_short type;
-       int first, ancount, qdcount, loopcnt;
-       char nbuf[PACKETSZ];
+       int ancount, qdcount;
+       int ret;
+       char **domain;
+       int type;
+       char **dp;
+       char *mxmatch;
+       bool amatch;
+       bool gotmx = FALSE;
+       int qtype;
+       int loopcnt;
+       char *xp;
+       char nbuf[MAX(PACKETSZ, MAXDNAME*2+2)];
+       char *searchlist[MAXDNSRCH+2];
+       extern char *gethostalias();
 
 
-       loopcnt = 0;
-loop:
-       /*
-        * Use query type of ANY if possible (NO_WILDCARD_MX), which will
-        * find types CNAME, A, and MX, and will cause all existing records
-        * to be cached by our local server.  If there is (might be) a
-        * wildcard MX record in the local domain or its parents that are
-        * searched, we can't use ANY; it would cause fully-qualified names
-        * to match as names in a local domain.
-        */
-# ifdef NO_WILDCARD_MX
-       n = res_search(host, C_IN, T_ANY, (char *)&answer, sizeof(answer));
-# else
-       n = res_search(host, C_IN, T_CNAME, (char *)&answer, sizeof(answer));
-# endif
-       if (n < 0) {
-               if (tTd(8, 1))
-                       printf("getcanonname:  res_search failed (errno=%d, h_errno=%d)\n",
-                           errno, h_errno);
-               return;
+       if (tTd(8, 2))
+               printf("dns_getcanonname(%s, trymx=%d)\n", host, trymx);
+
+       if ((_res.options & RES_INIT) == 0 && res_init() == -1)
+       {
+               *statp = EX_UNAVAILABLE;
+               return FALSE;
        }
 
        }
 
-       /* find first satisfactory answer */
-       hp = (HEADER *)&answer;
-       ancount = ntohs(hp->ancount);
+       /*
+       **  Initialize domain search list.  If there is at least one
+       **  dot in the name, search the unmodified name first so we
+       **  find "vse.CS" in Czechoslovakia instead of in the local
+       **  domain (e.g., vse.CS.Berkeley.EDU).
+       **
+       **  Older versions of the resolver could create this
+       **  list by tearing apart the host name.
+       */
 
 
-       /* we don't care about errors here, only if we got an answer */
-       if (ancount == 0) {
-               if (tTd(8, 1))
-                       printf("rcode = %d, ancount=%d\n", hp->rcode, ancount);
-               return;
+       loopcnt = 0;
+cnameloop:
+       for (cp = host, n = 0; *cp != '\0'; cp++)
+               if (*cp == '.')
+                       n++;
+
+       if (n == 0 && (xp = gethostalias(host)) != NULL)
+       {
+               if (loopcnt++ > MAXCNAMEDEPTH)
+               {
+                       syserr("loop in ${HOSTALIASES} file");
+               }
+               else
+               {
+                       strncpy(host, xp, hbsize);
+                       host[hbsize - 1] = '\0';
+                       goto cnameloop;
+               }
        }
        }
-       cp = (u_char *)&answer + sizeof(HEADER);
-       eom = (u_char *)&answer + n;
-       for (qdcount = ntohs(hp->qdcount); qdcount--; cp += n + QFIXEDSZ)
-               if ((n = dn_skipname(cp, eom)) < 0)
-                       return;
+
+       dp = searchlist;
+       if (n > 0)
+               *dp++ = "";
+       if (n >= 0 && *--cp != '.' && bitset(RES_DNSRCH, _res.options))
+       {
+               for (domain = _res.dnsrch; *domain != NULL; )
+                       *dp++ = *domain++;
+       }
+       else if (n == 0 && bitset(RES_DEFNAMES, _res.options))
+       {
+               *dp++ = _res.defdname;
+       }
+       else if (*cp == '.')
+       {
+               *cp = '\0';
+       }
+       *dp = NULL;
+
+       /* if we have a wildcard MX and no dots, try MX anyhow */
+       if (n == 0)
+               trymx = TRUE;
 
        /*
 
        /*
-        * just in case someone puts a CNAME record after another record,
-        * check all records for CNAME; otherwise, just take the first
-        * name found.
-        */
-       for (first = 1; --ancount >= 0 && cp < eom; cp += n) {
-               if ((n = dn_expand((char *)&answer, eom, cp, nbuf,
-                   sizeof(nbuf))) < 0)
-                       break;
-               if (first) {                    /* XXX */
-                       (void)strncpy(host, nbuf, hbsize);
-                       host[hbsize - 1] = '\0';
-                       first = 0;
+       **  Now run through the search list for the name in question.
+       */
+
+       mxmatch = NULL;
+       qtype = T_ANY;
+
+       for (dp = searchlist; *dp != NULL; )
+       {
+               if (qtype == T_ANY)
+                       gotmx = FALSE;
+               if (tTd(8, 5))
+                       printf("dns_getcanonname: trying %s.%s (%s)\n",
+                               host, *dp,
+                               qtype == T_ANY ? "ANY" : qtype == T_A ? "A" :
+                               qtype == T_MX ? "MX" : "???");
+               ret = res_querydomain(host, *dp, C_IN, qtype,
+                                     answer.qb2, sizeof(answer.qb2));
+               if (ret <= 0)
+               {
+                       if (tTd(8, 7))
+                               printf("\tNO: errno=%d, h_errno=%d\n",
+                                       errno, h_errno);
+
+                       if (errno == ECONNREFUSED || h_errno == TRY_AGAIN)
+                       {
+                               /* the name server seems to be down */
+                               h_errno = TRY_AGAIN;
+                               *statp = EX_TEMPFAIL;
+                               return FALSE;
+                       }
+
+                       if (h_errno != HOST_NOT_FOUND)
+                       {
+                               /* might have another type of interest */
+                               if (qtype == T_ANY)
+                               {
+                                       qtype = T_A;
+                                       continue;
+                               }
+                               else if (qtype == T_A && !gotmx && trymx)
+                               {
+                                       qtype = T_MX;
+                                       continue;
+                               }
+                       }
+
+                       /* try the next name */
+                       dp++;
+                       qtype = T_ANY;
+                       continue;
                }
                }
-               cp += n;
-               GETSHORT(type, cp);
-               cp += sizeof(u_short) + sizeof(u_long);
-               GETSHORT(n, cp);
-               if (type == T_CNAME)  {
-                       /*
-                        * assume that only one cname will be found.  More
-                        * than one is undefined.  Copy so that if dn_expand
-                        * fails, `host' is still okay.
-                        */
-                       if ((n = dn_expand((char *)&answer, eom, cp, nbuf,
-                           sizeof(nbuf))) < 0)
+               else if (tTd(8, 7))
+                       printf("\tYES\n");
+
+               /*
+               **  This might be a bogus match.  Search for A or
+               **  CNAME records.  If we don't have a matching
+               **  wild card MX record, we will accept MX as well.
+               */
+
+               hp = (HEADER *) &answer;
+               ap = (u_char *) &answer + HFIXEDSZ;
+               eom = (u_char *) &answer + ret;
+
+               /* skip question part of response -- we know what we asked */
+               for (qdcount = ntohs(hp->qdcount); qdcount--; ap += ret + QFIXEDSZ)
+               {
+                       if ((ret = dn_skipname(ap, eom)) < 0)
+                       {
+                               if (tTd(8, 20))
+                                       printf("qdcount failure (%d)\n",
+                                               ntohs(hp->qdcount));
+                               *statp = EX_SOFTWARE;
+                               return FALSE;           /* ???XXX??? */
+                       }
+               }
+
+               amatch = FALSE;
+               for (ancount = ntohs(hp->ancount); --ancount >= 0 && ap < eom; ap += n)
+               {
+                       n = dn_expand((u_char *) &answer, eom, ap,
+                                     (RES_UNC_T) nbuf, sizeof nbuf);
+                       if (n < 0)
                                break;
                                break;
-                       (void)strncpy(host, nbuf, hbsize); /* XXX */
-                       host[hbsize - 1] = '\0';
-                       if (++loopcnt > 8)      /* never be more than 1 */
-                               return;
-                       goto loop;
+                       ap += n;
+                       GETSHORT(type, ap);
+                       ap += INT16SZ + INT32SZ;
+                       GETSHORT(n, ap);
+                       switch (type)
+                       {
+                         case T_MX:
+                               gotmx = TRUE;
+                               if (**dp != '\0' || !HasWildcardMX)
+                               {
+                                       /* got a match -- save that info */
+                                       if (trymx && mxmatch == NULL)
+                                               mxmatch = *dp;
+                                       continue;
+                               }
+
+                               /* exact MX matches are as good as an A match */
+                               /* fall through */
+
+                         case T_A:
+                               /* good show */
+                               amatch = TRUE;
+
+                               /* continue in case a CNAME also exists */
+                               continue;
+
+                         case T_CNAME:
+                               if (DontExpandCnames)
+                               {
+                                       /* got CNAME -- guaranteed canonical */
+                                       amatch = TRUE;
+                                       break;
+                               }
+
+                               if (loopcnt++ > MAXCNAMEDEPTH)
+                               {
+                                       /*XXX should notify postmaster XXX*/
+                                       message("DNS failure: CNAME loop for %s",
+                                               host);
+                                       if (CurEnv->e_message == NULL)
+                                       {
+                                               char ebuf[MAXLINE];
+
+                                               sprintf(ebuf, "Deferred: DNS failure: CNAME loop for %s",
+                                                       host);
+                                               CurEnv->e_message = newstr(ebuf);
+                                       }
+                                       h_errno = NO_RECOVERY;
+                                       *statp = EX_CONFIG;
+                                       return FALSE;
+                               }
+
+                               /* value points at name */
+                               if ((ret = dn_expand((u_char *)&answer,
+                                   eom, ap, (RES_UNC_T) nbuf, sizeof(nbuf))) < 0)
+                                       break;
+                               (void)strncpy(host, nbuf, hbsize); /* XXX */
+                               host[hbsize - 1] = '\0';
+
+                               /*
+                               **  RFC 1034 section 3.6 specifies that CNAME
+                               **  should point at the canonical name -- but
+                               **  urges software to try again anyway.
+                               */
+
+                               goto cnameloop;
+
+                         default:
+                               /* not a record of interest */
+                               continue;
+                       }
+               }
+
+               if (amatch)
+               {
+                       /* got an A record and no CNAME */
+                       mxmatch = *dp;
+                       break;
+               }
+
+               /*
+               **  If this was a T_ANY query, we may have the info but
+               **  need an explicit query.  Try T_A, then T_MX.
+               */
+
+               if (qtype == T_ANY)
+                       qtype = T_A;
+               else if (qtype == T_A && !gotmx && trymx)
+                       qtype = T_MX;
+               else
+               {
+                       /* really nothing in this domain; try the next */
+                       qtype = T_ANY;
+                       dp++;
                }
        }
                }
        }
-}
 
 
-#else /* not NAMED_BIND */
+       if (mxmatch == NULL)
+       {
+               *statp = EX_NOHOST;
+               return FALSE;
+       }
+
+       /* create matching name and return */
+       (void) sprintf(nbuf, "%.*s%s%.*s", MAXDNAME, host,
+                       *mxmatch == '\0' ? "" : ".",
+                       MAXDNAME, mxmatch);
+       strncpy(host, nbuf, hbsize);
+       host[hbsize - 1] = '\0';
+       *statp = EX_OK;
+       return TRUE;
+}
 
 
-#include <netdb.h>
 
 
-getcanonname(host, hbsize)
+char *
+gethostalias(host)
        char *host;
        char *host;
-       int hbsize;
 {
 {
-       struct hostent *hp;
+       char *fname;
+       FILE *fp;
+       register char *p = NULL;
+       char buf[MAXLINE];
+       static char hbuf[MAXDNAME];
 
 
-       hp = gethostbyname(host);
-       if (hp == NULL)
-               return;
+       fname = getenv("HOSTALIASES");
+       if (fname == NULL ||
+           (fp = safefopen(fname, O_RDONLY, 0, SFF_REGONLY)) == NULL)
+               return NULL;
+       while (fgets(buf, sizeof buf, fp) != NULL)
+       {
+               for (p = buf; p != '\0' && !(isascii(*p) && isspace(*p)); p++)
+                       continue;
+               if (*p == 0)
+               {
+                       /* syntax error */
+                       continue;
+               }
+               *p++ = '\0';
+               if (strcasecmp(buf, host) == 0)
+                       break;
+       }
 
 
-       if (strlen(hp->h_name) >= hbsize)
-               return;
+       if (feof(fp))
+       {
+               /* no match */
+               fclose(fp);
+               return NULL;
+       }
 
 
-       (void) strcpy(host, hp->h_name);
+       /* got a match; extract the equivalent name */
+       while (*p != '\0' && isascii(*p) && isspace(*p))
+               p++;
+       host = p;
+       while (*p != '\0' && !(isascii(*p) && isspace(*p)))
+               p++;
+       *p = '\0';
+       strncpy(hbuf, host, sizeof hbuf - 1);
+       hbuf[sizeof hbuf - 1] = '\0';
+       return hbuf;
 }
 
 }
 
-#endif /* not NAMED_BIND */
+#endif /* NAMED_BIND */