1N/A/*
1N/A * Copyright (c) 1998-2004, 2006, 2010 Sendmail, Inc. and its suppliers.
1N/A * All rights reserved.
1N/A * Copyright (c) 1986, 1995-1997 Eric P. Allman. All rights reserved.
1N/A * Copyright (c) 1988, 1993
1N/A * The Regents of the University of California. All rights reserved.
1N/A *
1N/A * By using this file, you agree to the terms and conditions set
1N/A * forth in the LICENSE file which can be found at the top level of
1N/A * the sendmail distribution.
1N/A *
1N/A */
1N/A
1N/A#include <sendmail.h>
1N/A#include "map.h"
1N/A
1N/A#if NAMED_BIND
1N/ASM_RCSID("@(#)$Id: domain.c,v 8.204 2010/06/29 15:35:33 ca Exp $ (with name server)")
1N/A#else /* NAMED_BIND */
1N/ASM_RCSID("@(#)$Id: domain.c,v 8.204 2010/06/29 15:35:33 ca Exp $ (without name server)")
1N/A#endif /* NAMED_BIND */
1N/A
1N/A#if NAMED_BIND
1N/A
1N/A# include <arpa/inet.h>
1N/A
1N/A
1N/A# ifndef MXHOSTBUFSIZE
1N/A# define MXHOSTBUFSIZE (128 * MAXMXHOSTS)
1N/A# endif /* ! MXHOSTBUFSIZE */
1N/A
1N/Astatic char MXHostBuf[MXHOSTBUFSIZE];
1N/A#if (MXHOSTBUFSIZE < 2) || (MXHOSTBUFSIZE >= INT_MAX/2)
1N/A ERROR: _MXHOSTBUFSIZE is out of range
1N/A#endif /* (MXHOSTBUFSIZE < 2) || (MXHOSTBUFSIZE >= INT_MAX/2) */
1N/A
1N/A# ifndef MAXDNSRCH
1N/A# define MAXDNSRCH 6 /* number of possible domains to search */
1N/A# endif /* ! MAXDNSRCH */
1N/A
1N/A# ifndef RES_DNSRCH_VARIABLE
1N/A# define RES_DNSRCH_VARIABLE _res.dnsrch
1N/A# endif /* ! RES_DNSRCH_VARIABLE */
1N/A
1N/A# ifndef NO_DATA
1N/A# define NO_DATA NO_ADDRESS
1N/A# endif /* ! NO_DATA */
1N/A
1N/A# ifndef HFIXEDSZ
1N/A# define HFIXEDSZ 12 /* sizeof(HEADER) */
1N/A# endif /* ! HFIXEDSZ */
1N/A
1N/A# define MAXCNAMEDEPTH 10 /* maximum depth of CNAME recursion */
1N/A
1N/A# if defined(__RES) && (__RES >= 19940415)
1N/A# define RES_UNC_T char *
1N/A# else /* defined(__RES) && (__RES >= 19940415) */
1N/A# define RES_UNC_T unsigned char *
1N/A# endif /* defined(__RES) && (__RES >= 19940415) */
1N/A
1N/Astatic int mxrand __P((char *));
1N/Astatic int fallbackmxrr __P((int, unsigned short *, char **));
1N/A
1N/A/*
1N/A** GETFALLBACKMXRR -- get MX resource records for fallback MX host.
1N/A**
1N/A** We have to initialize this once before doing anything else.
1N/A** Moreover, we have to repeat this from time to time to avoid
1N/A** stale data, e.g., in persistent queue runners.
1N/A** This should be done in a parent process so the child
1N/A** processes have the right data.
1N/A**
1N/A** Parameters:
1N/A** host -- the name of the fallback MX host.
1N/A**
1N/A** Returns:
1N/A** number of MX records.
1N/A**
1N/A** Side Effects:
1N/A** Populates NumFallbackMXHosts and fbhosts.
1N/A** Sets renewal time (based on TTL).
1N/A*/
1N/A
1N/Aint NumFallbackMXHosts = 0; /* Number of fallback MX hosts (after MX expansion) */
1N/Astatic char *fbhosts[MAXMXHOSTS + 1];
1N/A
1N/Aint
1N/Agetfallbackmxrr(host)
1N/A char *host;
1N/A{
1N/A int i, rcode;
1N/A int ttl;
1N/A static time_t renew = 0;
1N/A
1N/A#if 0
1N/A /* This is currently done before this function is called. */
1N/A if (host == NULL || *host == '\0')
1N/A return 0;
1N/A#endif /* 0 */
1N/A if (NumFallbackMXHosts > 0 && renew > curtime())
1N/A return NumFallbackMXHosts;
1N/A if (host[0] == '[')
1N/A {
1N/A fbhosts[0] = host;
1N/A NumFallbackMXHosts = 1;
1N/A }
1N/A else
1N/A {
1N/A /* free old data */
1N/A for (i = 0; i < NumFallbackMXHosts; i++)
1N/A sm_free(fbhosts[i]);
1N/A
1N/A /* get new data */
1N/A NumFallbackMXHosts = getmxrr(host, fbhosts, NULL, false,
1N/A &rcode, false, &ttl);
1N/A renew = curtime() + ttl;
1N/A for (i = 0; i < NumFallbackMXHosts; i++)
1N/A fbhosts[i] = newstr(fbhosts[i]);
1N/A }
1N/A return NumFallbackMXHosts;
1N/A}
1N/A
1N/A/*
1N/A** FALLBACKMXRR -- add MX resource records for fallback MX host to list.
1N/A**
1N/A** Parameters:
1N/A** nmx -- current number of MX records.
1N/A** prefs -- array of preferences.
1N/A** mxhosts -- array of MX hosts (maximum size: MAXMXHOSTS)
1N/A**
1N/A** Returns:
1N/A** new number of MX records.
1N/A**
1N/A** Side Effects:
1N/A** If FallbackMX was set, it appends the MX records for
1N/A** that host to mxhosts (and modifies prefs accordingly).
1N/A*/
1N/A
1N/Astatic int
1N/Afallbackmxrr(nmx, prefs, mxhosts)
1N/A int nmx;
1N/A unsigned short *prefs;
1N/A char **mxhosts;
1N/A{
1N/A int i;
1N/A
1N/A for (i = 0; i < NumFallbackMXHosts && nmx < MAXMXHOSTS; i++)
1N/A {
1N/A if (nmx > 0)
1N/A prefs[nmx] = prefs[nmx - 1] + 1;
1N/A else
1N/A prefs[nmx] = 0;
1N/A mxhosts[nmx++] = fbhosts[i];
1N/A }
1N/A return nmx;
1N/A}
1N/A
1N/A/*
1N/A** GETMXRR -- get MX resource records for a domain
1N/A**
1N/A** Parameters:
1N/A** host -- the name of the host to MX.
1N/A** mxhosts -- a pointer to a return buffer of MX records.
1N/A** mxprefs -- a pointer to a return buffer of MX preferences.
1N/A** If NULL, don't try to populate.
1N/A** droplocalhost -- If true, all MX records less preferred
1N/A** than the local host (as determined by $=w) will
1N/A** be discarded.
1N/A** rcode -- a pointer to an EX_ status code.
1N/A** tryfallback -- add also fallback MX host?
1N/A** pttl -- pointer to return TTL (can be NULL).
1N/A**
1N/A** Returns:
1N/A** The number of MX records found.
1N/A** -1 if there is an internal failure.
1N/A** If no MX records are found, mxhosts[0] is set to host
1N/A** and 1 is returned.
1N/A**
1N/A** Side Effects:
1N/A** The entries made for mxhosts point to a static array
1N/A** MXHostBuf[MXHOSTBUFSIZE], so the data needs to be copied,
1N/A** if it must be preserved across calls to this function.
1N/A*/
1N/A
1N/Aint
1N/Agetmxrr(host, mxhosts, mxprefs, droplocalhost, rcode, tryfallback, pttl)
1N/A char *host;
1N/A char **mxhosts;
1N/A unsigned short *mxprefs;
1N/A bool droplocalhost;
1N/A int *rcode;
1N/A bool tryfallback;
1N/A int *pttl;
1N/A{
1N/A register unsigned char *eom, *cp;
1N/A register int i, j, n;
1N/A int nmx = 0;
1N/A register char *bp;
1N/A HEADER *hp;
1N/A querybuf answer;
1N/A int ancount, qdcount, buflen;
1N/A bool seenlocal = false;
1N/A unsigned short pref, type;
1N/A unsigned short localpref = 256;
1N/A char *fallbackMX = FallbackMX;
1N/A bool trycanon = false;
1N/A unsigned short *prefs;
1N/A int (*resfunc) __P((const char *, int, int, u_char *, int));
1N/A unsigned short prefer[MAXMXHOSTS];
1N/A int weight[MAXMXHOSTS];
1N/A int ttl = 0;
1N/A extern int res_query(), res_search();
1N/A
1N/A if (tTd(8, 2))
1N/A sm_dprintf("getmxrr(%s, droplocalhost=%d)\n",
1N/A host, droplocalhost);
1N/A *rcode = EX_OK;
1N/A if (pttl != NULL)
1N/A *pttl = SM_DEFAULT_TTL;
1N/A if (*host == '\0')
1N/A return 0;
1N/A
1N/A if ((fallbackMX != NULL && droplocalhost &&
1N/A wordinclass(fallbackMX, 'w')) || !tryfallback)
1N/A {
1N/A /* don't use fallback for this pass */
1N/A fallbackMX = NULL;
1N/A }
1N/A
1N/A if (mxprefs != NULL)
1N/A prefs = mxprefs;
1N/A else
1N/A prefs = prefer;
1N/A
1N/A /* efficiency hack -- numeric or non-MX lookups */
1N/A if (host[0] == '[')
1N/A goto punt;
1N/A
1N/A /*
1N/A ** If we don't have MX records in our host switch, don't
1N/A ** try for MX records. Note that this really isn't "right",
1N/A ** since we might be set up to try NIS first and then DNS;
1N/A ** if the host is found in NIS we really shouldn't be doing
1N/A ** MX lookups. However, that should be a degenerate case.
1N/A */
1N/A
1N/A if (!UseNameServer)
1N/A goto punt;
1N/A if (HasWildcardMX && ConfigLevel >= 6)
1N/A resfunc = res_query;
1N/A else
1N/A resfunc = res_search;
1N/A
1N/A errno = 0;
1N/A n = (*resfunc)(host, C_IN, T_MX, (unsigned char *) &answer,
1N/A sizeof(answer));
1N/A if (n < 0)
1N/A {
1N/A if (tTd(8, 1))
1N/A sm_dprintf("getmxrr: res_search(%s) failed (errno=%d, h_errno=%d)\n",
1N/A host, errno, h_errno);
1N/A switch (h_errno)
1N/A {
1N/A case NO_DATA:
1N/A trycanon = true;
1N/A /* FALLTHROUGH */
1N/A
1N/A case NO_RECOVERY:
1N/A /* no MX data on this host */
1N/A goto punt;
1N/A
1N/A case HOST_NOT_FOUND:
1N/A# if BROKEN_RES_SEARCH
1N/A case 0: /* Ultrix resolver retns failure w/ h_errno=0 */
1N/A# endif /* BROKEN_RES_SEARCH */
1N/A /* host doesn't exist in DNS; might be in /etc/hosts */
1N/A trycanon = true;
1N/A *rcode = EX_NOHOST;
1N/A goto punt;
1N/A
1N/A case TRY_AGAIN:
1N/A case -1:
1N/A /* couldn't connect to the name server */
1N/A if (fallbackMX != NULL)
1N/A {
1N/A /* name server is hosed -- push to fallback */
1N/A return fallbackmxrr(nmx, prefs, mxhosts);
1N/A }
1N/A /* it might come up later; better queue it up */
1N/A *rcode = EX_TEMPFAIL;
1N/A break;
1N/A
1N/A default:
1N/A syserr("getmxrr: res_search (%s) failed with impossible h_errno (%d)",
1N/A host, h_errno);
1N/A *rcode = EX_OSERR;
1N/A break;
1N/A }
1N/A
1N/A /* irreconcilable differences */
1N/A return -1;
1N/A }
1N/A
1N/A /* avoid problems after truncation in tcp packets */
1N/A if (n > sizeof(answer))
1N/A n = sizeof(answer);
1N/A
1N/A /* find first satisfactory answer */
1N/A hp = (HEADER *)&answer;
1N/A cp = (unsigned char *)&answer + HFIXEDSZ;
1N/A eom = (unsigned char *)&answer + n;
1N/A for (qdcount = ntohs((unsigned short) hp->qdcount);
1N/A qdcount--;
1N/A cp += n + QFIXEDSZ)
1N/A {
1N/A if ((n = dn_skipname(cp, eom)) < 0)
1N/A goto punt;
1N/A }
1N/A
1N/A /* NOTE: see definition of MXHostBuf! */
1N/A buflen = sizeof(MXHostBuf) - 1;
1N/A SM_ASSERT(buflen > 0);
1N/A bp = MXHostBuf;
1N/A ancount = ntohs((unsigned short) hp->ancount);
1N/A
1N/A /* See RFC 1035 for layout of RRs. */
1N/A /* XXX leave room for FallbackMX ? */
1N/A while (--ancount >= 0 && cp < eom && nmx < MAXMXHOSTS - 1)
1N/A {
1N/A if ((n = dn_expand((unsigned char *)&answer, eom, cp,
1N/A (RES_UNC_T) bp, buflen)) < 0)
1N/A break;
1N/A cp += n;
1N/A GETSHORT(type, cp);
1N/A cp += INT16SZ; /* skip over class */
1N/A GETLONG(ttl, cp);
1N/A GETSHORT(n, cp); /* rdlength */
1N/A if (type != T_MX)
1N/A {
1N/A if (tTd(8, 8) || _res.options & RES_DEBUG)
1N/A sm_dprintf("unexpected answer type %d, size %d\n",
1N/A type, n);
1N/A cp += n;
1N/A continue;
1N/A }
1N/A GETSHORT(pref, cp);
1N/A if ((n = dn_expand((unsigned char *)&answer, eom, cp,
1N/A (RES_UNC_T) bp, buflen)) < 0)
1N/A break;
1N/A cp += n;
1N/A n = strlen(bp);
1N/A# if 0
1N/A /* Can this happen? */
1N/A if (n == 0)
1N/A {
1N/A if (LogLevel > 4)
1N/A sm_syslog(LOG_ERR, NOQID,
1N/A "MX records for %s contain empty string",
1N/A host);
1N/A continue;
1N/A }
1N/A# endif /* 0 */
1N/A if (wordinclass(bp, 'w'))
1N/A {
1N/A if (tTd(8, 3))
1N/A sm_dprintf("found localhost (%s) in MX list, pref=%d\n",
1N/A bp, pref);
1N/A if (droplocalhost)
1N/A {
1N/A if (!seenlocal || pref < localpref)
1N/A localpref = pref;
1N/A seenlocal = true;
1N/A continue;
1N/A }
1N/A weight[nmx] = 0;
1N/A }
1N/A else
1N/A weight[nmx] = mxrand(bp);
1N/A prefs[nmx] = pref;
1N/A mxhosts[nmx++] = bp;
1N/A bp += n;
1N/A if (bp[-1] != '.')
1N/A {
1N/A *bp++ = '.';
1N/A n++;
1N/A }
1N/A *bp++ = '\0';
1N/A if (buflen < n + 1)
1N/A {
1N/A /* don't want to wrap buflen */
1N/A break;
1N/A }
1N/A buflen -= n + 1;
1N/A }
1N/A
1N/A /* return only one TTL entry, that should be sufficient */
1N/A if (ttl > 0 && pttl != NULL)
1N/A *pttl = ttl;
1N/A
1N/A /* sort the records */
1N/A for (i = 0; i < nmx; i++)
1N/A {
1N/A for (j = i + 1; j < nmx; j++)
1N/A {
1N/A if (prefs[i] > prefs[j] ||
1N/A (prefs[i] == prefs[j] && weight[i] > weight[j]))
1N/A {
1N/A register int temp;
1N/A register char *temp1;
1N/A
1N/A temp = prefs[i];
1N/A prefs[i] = prefs[j];
1N/A prefs[j] = temp;
1N/A temp1 = mxhosts[i];
1N/A mxhosts[i] = mxhosts[j];
1N/A mxhosts[j] = temp1;
1N/A temp = weight[i];
1N/A weight[i] = weight[j];
1N/A weight[j] = temp;
1N/A }
1N/A }
1N/A if (seenlocal && prefs[i] >= localpref)
1N/A {
1N/A /* truncate higher preference part of list */
1N/A nmx = i;
1N/A }
1N/A }
1N/A
1N/A /* delete duplicates from list (yes, some bozos have duplicates) */
1N/A for (i = 0; i < nmx - 1; )
1N/A {
1N/A if (sm_strcasecmp(mxhosts[i], mxhosts[i + 1]) != 0)
1N/A i++;
1N/A else
1N/A {
1N/A /* compress out duplicate */
1N/A for (j = i + 1; j < nmx; j++)
1N/A {
1N/A mxhosts[j] = mxhosts[j + 1];
1N/A prefs[j] = prefs[j + 1];
1N/A }
1N/A nmx--;
1N/A }
1N/A }
1N/A
1N/A if (nmx == 0)
1N/A {
1N/Apunt:
1N/A if (seenlocal)
1N/A {
1N/A struct hostent *h = NULL;
1N/A
1N/A /*
1N/A ** If we have deleted all MX entries, this is
1N/A ** an error -- we should NEVER send to a host that
1N/A ** has an MX, and this should have been caught
1N/A ** earlier in the config file.
1N/A **
1N/A ** Some sites prefer to go ahead and try the
1N/A ** A record anyway; that case is handled by
1N/A ** setting TryNullMXList. I believe this is a
1N/A ** bad idea, but it's up to you....
1N/A */
1N/A
1N/A if (TryNullMXList)
1N/A {
1N/A SM_SET_H_ERRNO(0);
1N/A errno = 0;
1N/A h = sm_gethostbyname(host, AF_INET);
1N/A if (h == NULL)
1N/A {
1N/A if (errno == ETIMEDOUT ||
1N/A h_errno == TRY_AGAIN ||
1N/A (errno == ECONNREFUSED &&
1N/A UseNameServer))
1N/A {
1N/A *rcode = EX_TEMPFAIL;
1N/A return -1;
1N/A }
1N/A# if NETINET6
1N/A SM_SET_H_ERRNO(0);
1N/A errno = 0;
1N/A h = sm_gethostbyname(host, AF_INET6);
1N/A if (h == NULL &&
1N/A (errno == ETIMEDOUT ||
1N/A h_errno == TRY_AGAIN ||
1N/A (errno == ECONNREFUSED &&
1N/A UseNameServer)))
1N/A {
1N/A *rcode = EX_TEMPFAIL;
1N/A return -1;
1N/A }
1N/A# endif /* NETINET6 */
1N/A }
1N/A }
1N/A
1N/A if (h == NULL)
1N/A {
1N/A *rcode = EX_CONFIG;
1N/A syserr("MX list for %s points back to %s",
1N/A host, MyHostName);
1N/A return -1;
1N/A }
1N/A# if NETINET6
1N/A freehostent(h);
1N/A h = NULL;
1N/A# endif /* NETINET6 */
1N/A }
1N/A if (strlen(host) >= sizeof(MXHostBuf))
1N/A {
1N/A *rcode = EX_CONFIG;
1N/A syserr("Host name %s too long",
1N/A shortenstring(host, MAXSHORTSTR));
1N/A return -1;
1N/A }
1N/A (void) sm_strlcpy(MXHostBuf, host, sizeof(MXHostBuf));
1N/A mxhosts[0] = MXHostBuf;
1N/A prefs[0] = 0;
1N/A if (host[0] == '[')
1N/A {
1N/A register char *p;
1N/A# if NETINET6
1N/A struct sockaddr_in6 tmp6;
1N/A# endif /* NETINET6 */
1N/A
1N/A /* this may be an MX suppression-style address */
1N/A p = strchr(MXHostBuf, ']');
1N/A if (p != NULL)
1N/A {
1N/A *p = '\0';
1N/A
1N/A if (inet_addr(&MXHostBuf[1]) != INADDR_NONE)
1N/A {
1N/A nmx++;
1N/A *p = ']';
1N/A }
1N/A# if NETINET6
1N/A else if (anynet_pton(AF_INET6, &MXHostBuf[1],
1N/A &tmp6.sin6_addr) == 1)
1N/A {
1N/A nmx++;
1N/A *p = ']';
1N/A }
1N/A# endif /* NETINET6 */
1N/A else
1N/A {
1N/A trycanon = true;
1N/A mxhosts[0]++;
1N/A }
1N/A }
1N/A }
1N/A if (trycanon &&
1N/A getcanonname(mxhosts[0], sizeof(MXHostBuf) - 2, false, pttl))
1N/A {
1N/A /* XXX MXHostBuf == "" ? is that possible? */
1N/A bp = &MXHostBuf[strlen(MXHostBuf)];
1N/A if (bp[-1] != '.')
1N/A {
1N/A *bp++ = '.';
1N/A *bp = '\0';
1N/A }
1N/A nmx = 1;
1N/A }
1N/A }
1N/A
1N/A /* if we have a default lowest preference, include that */
1N/A if (fallbackMX != NULL && !seenlocal)
1N/A {
1N/A nmx = fallbackmxrr(nmx, prefs, mxhosts);
1N/A }
1N/A return nmx;
1N/A}
1N/A/*
1N/A** MXRAND -- create a randomizer for equal MX preferences
1N/A**
1N/A** If two MX hosts have equal preferences we want to randomize
1N/A** the selection. But in order for signatures to be the same,
1N/A** we need to randomize the same way each time. This function
1N/A** computes a pseudo-random hash function from the host name.
1N/A**
1N/A** Parameters:
1N/A** host -- the name of the host.
1N/A**
1N/A** Returns:
1N/A** A random but repeatable value based on the host name.
1N/A*/
1N/A
1N/Astatic int
1N/Amxrand(host)
1N/A register char *host;
1N/A{
1N/A int hfunc;
1N/A static unsigned int seed;
1N/A
1N/A if (seed == 0)
1N/A {
1N/A seed = (int) curtime() & 0xffff;
1N/A if (seed == 0)
1N/A seed++;
1N/A }
1N/A
1N/A if (tTd(17, 9))
1N/A sm_dprintf("mxrand(%s)", host);
1N/A
1N/A hfunc = seed;
1N/A while (*host != '\0')
1N/A {
1N/A int c = *host++;
1N/A
1N/A if (isascii(c) && isupper(c))
1N/A c = tolower(c);
1N/A hfunc = ((hfunc << 1) ^ c) % 2003;
1N/A }
1N/A
1N/A hfunc &= 0xff;
1N/A hfunc++;
1N/A
1N/A if (tTd(17, 9))
1N/A sm_dprintf(" = %d\n", hfunc);
1N/A return hfunc;
1N/A}
1N/A/*
1N/A** BESTMX -- find the best MX for a name
1N/A**
1N/A** This is really a hack, but I don't see any obvious way
1N/A** to generalize it at the moment.
1N/A*/
1N/A
1N/A/* ARGSUSED3 */
1N/Achar *
1N/Abestmx_map_lookup(map, name, av, statp)
1N/A MAP *map;
1N/A char *name;
1N/A char **av;
1N/A int *statp;
1N/A{
1N/A int nmx;
1N/A int saveopts = _res.options;
1N/A int i;
1N/A ssize_t len = 0;
1N/A char *result;
1N/A char *mxhosts[MAXMXHOSTS + 1];
1N/A#if _FFR_BESTMX_BETTER_TRUNCATION
1N/A char *buf;
1N/A#else /* _FFR_BESTMX_BETTER_TRUNCATION */
1N/A char *p;
1N/A char buf[PSBUFSIZE / 2];
1N/A#endif /* _FFR_BESTMX_BETTER_TRUNCATION */
1N/A
1N/A _res.options &= ~(RES_DNSRCH|RES_DEFNAMES);
1N/A nmx = getmxrr(name, mxhosts, NULL, false, statp, false, NULL);
1N/A _res.options = saveopts;
1N/A if (nmx <= 0)
1N/A return NULL;
1N/A if (bitset(MF_MATCHONLY, map->map_mflags))
1N/A return map_rewrite(map, name, strlen(name), NULL);
1N/A if ((map->map_coldelim == '\0') || (nmx == 1))
1N/A return map_rewrite(map, mxhosts[0], strlen(mxhosts[0]), av);
1N/A
1N/A /*
1N/A ** We were given a -z flag (return all MXs) and there are multiple
1N/A ** ones. We need to build them all into a list.
1N/A */
1N/A
1N/A#if _FFR_BESTMX_BETTER_TRUNCATION
1N/A for (i = 0; i < nmx; i++)
1N/A {
1N/A if (strchr(mxhosts[i], map->map_coldelim) != NULL)
1N/A {
1N/A syserr("bestmx_map_lookup: MX host %.64s includes map delimiter character 0x%02X",
1N/A mxhosts[i], map->map_coldelim);
1N/A return NULL;
1N/A }
1N/A len += strlen(mxhosts[i]) + 1;
1N/A if (len < 0)
1N/A {
1N/A len -= strlen(mxhosts[i]) + 1;
1N/A break;
1N/A }
1N/A }
1N/A buf = (char *) sm_malloc(len);
1N/A if (buf == NULL)
1N/A {
1N/A *statp = EX_UNAVAILABLE;
1N/A return NULL;
1N/A }
1N/A *buf = '\0';
1N/A for (i = 0; i < nmx; i++)
1N/A {
1N/A int end;
1N/A
1N/A end = sm_strlcat(buf, mxhosts[i], len);
1N/A if (i != nmx && end + 1 < len)
1N/A {
1N/A buf[end] = map->map_coldelim;
1N/A buf[end + 1] = '\0';
1N/A }
1N/A }
1N/A
1N/A /* Cleanly truncate for rulesets */
1N/A truncate_at_delim(buf, PSBUFSIZE / 2, map->map_coldelim);
1N/A#else /* _FFR_BESTMX_BETTER_TRUNCATION */
1N/A p = buf;
1N/A for (i = 0; i < nmx; i++)
1N/A {
1N/A size_t slen;
1N/A
1N/A if (strchr(mxhosts[i], map->map_coldelim) != NULL)
1N/A {
1N/A syserr("bestmx_map_lookup: MX host %.64s includes map delimiter character 0x%02X",
1N/A mxhosts[i], map->map_coldelim);
1N/A return NULL;
1N/A }
1N/A slen = strlen(mxhosts[i]);
1N/A if (len + slen + 2 > sizeof(buf))
1N/A break;
1N/A if (i > 0)
1N/A {
1N/A *p++ = map->map_coldelim;
1N/A len++;
1N/A }
1N/A (void) sm_strlcpy(p, mxhosts[i], sizeof(buf) - len);
1N/A p += slen;
1N/A len += slen;
1N/A }
1N/A#endif /* _FFR_BESTMX_BETTER_TRUNCATION */
1N/A
1N/A result = map_rewrite(map, buf, len, av);
1N/A#if _FFR_BESTMX_BETTER_TRUNCATION
1N/A sm_free(buf);
1N/A#endif /* _FFR_BESTMX_BETTER_TRUNCATION */
1N/A return result;
1N/A}
1N/A/*
1N/A** DNS_GETCANONNAME -- get the canonical name for named host using DNS
1N/A**
1N/A** This algorithm tries to be smart about wildcard MX records.
1N/A** This is hard to do because DNS doesn't tell is if we matched
1N/A** against a wildcard or a specific MX.
1N/A**
1N/A** We always prefer A & CNAME records, since these are presumed
1N/A** to be specific.
1N/A**
1N/A** If we match an MX in one pass and lose it in the next, we use
1N/A** the old one. For example, consider an MX matching *.FOO.BAR.COM.
1N/A** A hostname bletch.foo.bar.com will match against this MX, but
1N/A** will stop matching when we try bletch.bar.com -- so we know
1N/A** that bletch.foo.bar.com must have been right. This fails if
1N/A** there was also an MX record matching *.BAR.COM, but there are
1N/A** some things that just can't be fixed.
1N/A**
1N/A** Parameters:
1N/A** host -- a buffer containing the name of the host.
1N/A** This is a value-result parameter.
1N/A** hbsize -- the size of the host buffer.
1N/A** trymx -- if set, try MX records as well as A and CNAME.
1N/A** statp -- pointer to place to store status.
1N/A** pttl -- pointer to return TTL (can be NULL).
1N/A**
1N/A** Returns:
1N/A** true -- if the host matched.
1N/A** false -- otherwise.
1N/A*/
1N/A
1N/Abool
1N/Adns_getcanonname(host, hbsize, trymx, statp, pttl)
1N/A char *host;
1N/A int hbsize;
1N/A bool trymx;
1N/A int *statp;
1N/A int *pttl;
1N/A{
1N/A register unsigned char *eom, *ap;
1N/A register char *cp;
1N/A register int n;
1N/A HEADER *hp;
1N/A querybuf answer;
1N/A int ancount, qdcount;
1N/A int ret;
1N/A char **domain;
1N/A int type;
1N/A int ttl = 0;
1N/A char **dp;
1N/A char *mxmatch;
1N/A bool amatch;
1N/A bool gotmx = false;
1N/A int qtype;
1N/A int initial;
1N/A int loopcnt;
1N/A char nbuf[SM_MAX(MAXPACKET, MAXDNAME*2+2)];
1N/A char *searchlist[MAXDNSRCH + 2];
1N/A
1N/A if (tTd(8, 2))
1N/A sm_dprintf("dns_getcanonname(%s, trymx=%d)\n", host, trymx);
1N/A
1N/A if ((_res.options & RES_INIT) == 0 && res_init() == -1)
1N/A {
1N/A *statp = EX_UNAVAILABLE;
1N/A return false;
1N/A }
1N/A
1N/A *statp = EX_OK;
1N/A
1N/A /*
1N/A ** Initialize domain search list. If there is at least one
1N/A ** dot in the name, search the unmodified name first so we
1N/A ** find "vse.CS" in Czechoslovakia instead of in the local
1N/A ** domain (e.g., vse.CS.Berkeley.EDU). Note that there is no
1N/A ** longer a country named Czechoslovakia but this type of problem
1N/A ** is still present.
1N/A **
1N/A ** Older versions of the resolver could create this
1N/A ** list by tearing apart the host name.
1N/A */
1N/A
1N/A loopcnt = 0;
1N/Acnameloop:
1N/A /* Check for dots in the name */
1N/A for (cp = host, n = 0; *cp != '\0'; cp++)
1N/A if (*cp == '.')
1N/A n++;
1N/A
1N/A /*
1N/A ** Build the search list.
1N/A ** If there is at least one dot in name, start with a null
1N/A ** domain to search the unmodified name first.
1N/A ** If name does not end with a dot and search up local domain
1N/A ** tree desired, append each local domain component to the
1N/A ** search list; if name contains no dots and default domain
1N/A ** name is desired, append default domain name to search list;
1N/A ** else if name ends in a dot, remove that dot.
1N/A */
1N/A
1N/A dp = searchlist;
1N/A if (n > 0)
1N/A *dp++ = "";
1N/A if (n >= 0 && *--cp != '.' && bitset(RES_DNSRCH, _res.options))
1N/A {
1N/A /* make sure there are less than MAXDNSRCH domains */
1N/A for (domain = RES_DNSRCH_VARIABLE, ret = 0;
1N/A *domain != NULL && ret < MAXDNSRCH;
1N/A ret++)
1N/A *dp++ = *domain++;
1N/A }
1N/A else if (n == 0 && bitset(RES_DEFNAMES, _res.options))
1N/A {
1N/A *dp++ = _res.defdname;
1N/A }
1N/A else if (*cp == '.')
1N/A {
1N/A *cp = '\0';
1N/A }
1N/A *dp = NULL;
1N/A
1N/A /*
1N/A ** Now loop through the search list, appending each domain in turn
1N/A ** name and searching for a match.
1N/A */
1N/A
1N/A mxmatch = NULL;
1N/A initial = T_A;
1N/A# if NETINET6
1N/A if (InetMode == AF_INET6)
1N/A initial = T_AAAA;
1N/A# endif /* NETINET6 */
1N/A qtype = initial;
1N/A
1N/A for (dp = searchlist; *dp != NULL; )
1N/A {
1N/A if (qtype == initial)
1N/A gotmx = false;
1N/A if (tTd(8, 5))
1N/A sm_dprintf("dns_getcanonname: trying %s.%s (%s)\n",
1N/A host, *dp,
1N/A# if NETINET6
1N/A qtype == T_AAAA ? "AAAA" :
1N/A# endif /* NETINET6 */
1N/A qtype == T_A ? "A" :
1N/A qtype == T_MX ? "MX" :
1N/A "???");
1N/A errno = 0;
1N/A ret = res_querydomain(host, *dp, C_IN, qtype,
1N/A answer.qb2, sizeof(answer.qb2));
1N/A if (ret <= 0)
1N/A {
1N/A int save_errno = errno;
1N/A
1N/A if (tTd(8, 7))
1N/A sm_dprintf("\tNO: errno=%d, h_errno=%d\n",
1N/A save_errno, h_errno);
1N/A
1N/A if (save_errno == ECONNREFUSED || h_errno == TRY_AGAIN)
1N/A {
1N/A /*
1N/A ** the name server seems to be down or broken.
1N/A */
1N/A
1N/A SM_SET_H_ERRNO(TRY_AGAIN);
1N/A if (**dp == '\0')
1N/A {
1N/A if (*statp == EX_OK)
1N/A *statp = EX_TEMPFAIL;
1N/A goto nexttype;
1N/A }
1N/A *statp = EX_TEMPFAIL;
1N/A
1N/A if (WorkAroundBrokenAAAA)
1N/A {
1N/A /*
1N/A ** Only return if not TRY_AGAIN as an
1N/A ** attempt with a different qtype may
1N/A ** succeed (res_querydomain() calls
1N/A ** res_query() calls res_send() which
1N/A ** sets errno to ETIMEDOUT if the
1N/A ** nameservers could be contacted but
1N/A ** didn't give an answer).
1N/A */
1N/A
1N/A if (save_errno != ETIMEDOUT)
1N/A return false;
1N/A }
1N/A else
1N/A return false;
1N/A }
1N/A
1N/Anexttype:
1N/A if (h_errno != HOST_NOT_FOUND)
1N/A {
1N/A /* might have another type of interest */
1N/A# if NETINET6
1N/A if (qtype == T_AAAA)
1N/A {
1N/A qtype = T_A;
1N/A continue;
1N/A }
1N/A else
1N/A# endif /* NETINET6 */
1N/A if (qtype == T_A && !gotmx &&
1N/A (trymx || **dp == '\0'))
1N/A {
1N/A qtype = T_MX;
1N/A continue;
1N/A }
1N/A }
1N/A
1N/A /* definite no -- try the next domain */
1N/A dp++;
1N/A qtype = initial;
1N/A continue;
1N/A }
1N/A else if (tTd(8, 7))
1N/A sm_dprintf("\tYES\n");
1N/A
1N/A /* avoid problems after truncation in tcp packets */
1N/A if (ret > sizeof(answer))
1N/A ret = sizeof(answer);
1N/A SM_ASSERT(ret >= 0);
1N/A
1N/A /*
1N/A ** Appear to have a match. Confirm it by searching for A or
1N/A ** CNAME records. If we don't have a local domain
1N/A ** wild card MX record, we will accept MX as well.
1N/A */
1N/A
1N/A hp = (HEADER *) &answer;
1N/A ap = (unsigned char *) &answer + HFIXEDSZ;
1N/A eom = (unsigned char *) &answer + ret;
1N/A
1N/A /* skip question part of response -- we know what we asked */
1N/A for (qdcount = ntohs((unsigned short) hp->qdcount);
1N/A qdcount--;
1N/A ap += ret + QFIXEDSZ)
1N/A {
1N/A if ((ret = dn_skipname(ap, eom)) < 0)
1N/A {
1N/A if (tTd(8, 20))
1N/A sm_dprintf("qdcount failure (%d)\n",
1N/A ntohs((unsigned short) hp->qdcount));
1N/A *statp = EX_SOFTWARE;
1N/A return false; /* ???XXX??? */
1N/A }
1N/A }
1N/A
1N/A amatch = false;
1N/A for (ancount = ntohs((unsigned short) hp->ancount);
1N/A --ancount >= 0 && ap < eom;
1N/A ap += n)
1N/A {
1N/A n = dn_expand((unsigned char *) &answer, eom, ap,
1N/A (RES_UNC_T) nbuf, sizeof(nbuf));
1N/A if (n < 0)
1N/A break;
1N/A ap += n;
1N/A GETSHORT(type, ap);
1N/A ap += INT16SZ; /* skip over class */
1N/A GETLONG(ttl, ap);
1N/A GETSHORT(n, ap); /* rdlength */
1N/A switch (type)
1N/A {
1N/A case T_MX:
1N/A gotmx = true;
1N/A if (**dp != '\0' && HasWildcardMX)
1N/A {
1N/A /*
1N/A ** If we are using MX matches and have
1N/A ** not yet gotten one, save this one
1N/A ** but keep searching for an A or
1N/A ** CNAME match.
1N/A */
1N/A
1N/A if (trymx && mxmatch == NULL)
1N/A mxmatch = *dp;
1N/A continue;
1N/A }
1N/A
1N/A /*
1N/A ** If we did not append a domain name, this
1N/A ** must have been a canonical name to start
1N/A ** with. Even if we did append a domain name,
1N/A ** in the absence of a wildcard MX this must
1N/A ** still be a real MX match.
1N/A ** Such MX matches are as good as an A match,
1N/A ** fall through.
1N/A */
1N/A /* FALLTHROUGH */
1N/A
1N/A# if NETINET6
1N/A case T_AAAA:
1N/A# endif /* NETINET6 */
1N/A case T_A:
1N/A /* Flag that a good match was found */
1N/A amatch = true;
1N/A
1N/A /* continue in case a CNAME also exists */
1N/A continue;
1N/A
1N/A case T_CNAME:
1N/A if (DontExpandCnames)
1N/A {
1N/A /* got CNAME -- guaranteed canonical */
1N/A amatch = true;
1N/A break;
1N/A }
1N/A
1N/A if (loopcnt++ > MAXCNAMEDEPTH)
1N/A {
1N/A /*XXX should notify postmaster XXX*/
1N/A message("DNS failure: CNAME loop for %s",
1N/A host);
1N/A if (CurEnv->e_message == NULL)
1N/A {
1N/A char ebuf[MAXLINE];
1N/A
1N/A (void) sm_snprintf(ebuf,
1N/A sizeof(ebuf),
1N/A "Deferred: DNS failure: CNAME loop for %.100s",
1N/A host);
1N/A CurEnv->e_message =
1N/A sm_rpool_strdup_x(
1N/A CurEnv->e_rpool, ebuf);
1N/A }
1N/A SM_SET_H_ERRNO(NO_RECOVERY);
1N/A *statp = EX_CONFIG;
1N/A return false;
1N/A }
1N/A
1N/A /* value points at name */
1N/A if ((ret = dn_expand((unsigned char *)&answer,
1N/A eom, ap, (RES_UNC_T) nbuf,
1N/A sizeof(nbuf))) < 0)
1N/A break;
1N/A (void) sm_strlcpy(host, nbuf, hbsize);
1N/A
1N/A /*
1N/A ** RFC 1034 section 3.6 specifies that CNAME
1N/A ** should point at the canonical name -- but
1N/A ** urges software to try again anyway.
1N/A */
1N/A
1N/A goto cnameloop;
1N/A
1N/A default:
1N/A /* not a record of interest */
1N/A continue;
1N/A }
1N/A }
1N/A
1N/A if (amatch)
1N/A {
1N/A /*
1N/A ** Got a good match -- either an A, CNAME, or an
1N/A ** exact MX record. Save it and get out of here.
1N/A */
1N/A
1N/A mxmatch = *dp;
1N/A break;
1N/A }
1N/A
1N/A /*
1N/A ** Nothing definitive yet.
1N/A ** If this was a T_A query and we haven't yet found a MX
1N/A ** match, try T_MX if allowed to do so.
1N/A ** Otherwise, try the next domain.
1N/A */
1N/A
1N/A# if NETINET6
1N/A if (qtype == T_AAAA)
1N/A qtype = T_A;
1N/A else
1N/A# endif /* NETINET6 */
1N/A if (qtype == T_A && !gotmx && (trymx || **dp == '\0'))
1N/A qtype = T_MX;
1N/A else
1N/A {
1N/A qtype = initial;
1N/A dp++;
1N/A }
1N/A }
1N/A
1N/A /* if nothing was found, we are done */
1N/A if (mxmatch == NULL)
1N/A {
1N/A if (*statp == EX_OK)
1N/A *statp = EX_NOHOST;
1N/A return false;
1N/A }
1N/A
1N/A /*
1N/A ** Create canonical name and return.
1N/A ** If saved domain name is null, name was already canonical.
1N/A ** Otherwise append the saved domain name.
1N/A */
1N/A
1N/A (void) sm_snprintf(nbuf, sizeof(nbuf), "%.*s%s%.*s", MAXDNAME, host,
1N/A *mxmatch == '\0' ? "" : ".",
1N/A MAXDNAME, mxmatch);
1N/A (void) sm_strlcpy(host, nbuf, hbsize);
1N/A if (tTd(8, 5))
1N/A sm_dprintf("dns_getcanonname: %s\n", host);
1N/A *statp = EX_OK;
1N/A
1N/A /* return only one TTL entry, that should be sufficient */
1N/A if (ttl > 0 && pttl != NULL)
1N/A *pttl = ttl;
1N/A return true;
1N/A}
1N/A#endif /* NAMED_BIND */