/* @(#)src/bindlib.c	1.16 9/5/92 22:16:42 */

/*
 *    Copyright (C) 1992  Ronald S. Karr
 *
 * See the file COPYING, distributed with smail, for restriction
 * and warranty information.
 */

/*
 * bindlib.c
 *	common code for the BIND router and the "tcpsmtp" transport,
 *	both of which can the Domain Name Service as implemented
 *	(typically) by the a Berkeley Internet Name Domain (BIND)
 *	server.
 *
 *	bind code converted to general library by Chip Salzenberg.
 */

/*
 * Specifications for the bind router and/or tcpsmtp transport:
 *
 *	associated transports:
 *	    Generally used with an smtp transport.
 *
 *	private attribute data:
 *	    ignore_domains: domains to ignore.  Names under any of these
 *		domains will never be matched.  This prevents expensive
 *		lookups for domains that are known not to be in the DNS.
 *
 *	private attribute flags:
 *	    defer_no_connect:  if set and we cannot connect to the
 *		name server, try again later.  This is set by default.
 *	    local_mx_okay: if not set, an MX record which points to the
 *		local host is considered to be an error which will
 *		will cause mail to be returned to the sender.
 *	    defnames:  append a default domain to an unqualified hostname,
 *		using the RES_DEFNAME flag to the resolver library.
 *		This is set by default.
 *	    domain_required:  at least two name components are required
 *		in the hostname.  Setting this prevents lookups of
 *		single-component names which are unlikely to be valid
 *		hosts in the DNS.
 *	    mx_only:  use only MX records in matching an address.  If a
 *		host doesn't have a MX records, then don't use the A
 *		or WKS records for routing.
 *
 *	algorithm:
 *	    For bind routing, given a target use the following
 *	    strategy:
 *
 *	    1.  Replace sequences of more than one dot in the target
 *		with a single dot.  Remove any dots from the beginning
 *		and end of the target.
 *
 *	    2.	See if there is an MX record for the target.  If so,
 *		skip to step 6.
 *
 *	    3.	If the MX record query returned a CNAME record, then
 *		treat the canonical name as the target and return to
 *		step 2.
 *
 *	    4.  Do not match the target.
 *
 *	    5.  If the primary name of the local host is listed in an
 *		MX record, all MX record's with equal or greater
 *		preference fields are discarded.
 *
 *	    6.  If the only MX record remaining references the local
 *		host, do not match the target.
 *
 *	    7.  Choose one of the MX records with the lowest
 *		preference fields.  Match the address with the
 *		next_host value set to the referenced host.
 *
 *          The algorithm is extended for the UK domain by (1)
 *          checking a list of top-level domains and converting the
 *          target to a gateway name if matched, (2) discarding any MX
 *          records which match the uk_ignore_gateways parameter, or
 *          whose preference value is greater than uk_max_precedence,
 *          (3) re-trying with the list of widening strings and then
 *          trying the address inverted before giving up, and (4)
 *          matching with the greybook transport if if MX records were
 *          found but were all discarded by the above rules.
 *
 *	    When a hostname is matched, the next_host field is set to
 *	    the referenced host, for MX records, or the matched host
 *	    for A records.  The route field is set to the canonical
 *	    name for the host, if that is different from the original
 *	    target host.  The match length is always set to the length
 *	    of the original target host.
 *
 * NOTE:  Use of WKS records is enabled if the USE_WKS_RECORDS macro
 *	  is defined.  This can be set from the EDITME file with the
 *	  MISC_H_DEFINES variable.  If this macro is not defined, then
 *	  WKS records are not retrieved and are assumed to exist and
 *	  to contain the SMTP service.
 */

#define NEED_SOCKETS
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include "defs.h"

/*
 * Compilation of this entire file depends on "HAVE_BIND".
 */

#ifdef HAVE_BIND

#include "smail.h"
#include "addr.h"
#include "route.h"
#include "transport.h"
#include "bindlib.h"
#include "bindsmtpth.h"
#include "lookup.h"
#include "dys.h"
#ifndef DEPEND
# include "extern.h"
# include "debug.h"
# include "error.h"
#endif

#if PACKETSZ > 1024
# define MAXPACKET	PACKETSZ
#else
# define MAXPACKET	1024
#endif

#ifndef GETSHORT
/*
 * earlier versions of bind don't seem to define these useful macros,
 * so roll our own.
 */
# define GETSHORT(i, p)	\
	((i)  = ((unsigned)(*(p)++ & 0xff) << 8),	\
	 (i) |= ((unsigned)(*(p)++ & 0xff)))
# define GETLONG(l, p)	\
	((l)  = ((unsigned long)(*(p)++ & 0xff) << 24),	\
	 (l) |= ((unsigned long)(*(p)++ & 0xff) << 16),	\
	 (l) |= ((unsigned long)(*(p)++ & 0xff) << 8),	\
	 (l) |= ((unsigned long)(*(p)++ & 0xff)))
# define PUTSHORT(i, p)	\
	((*(p)++ = (unsigned)(i) >> 8),			\
	 (*(p)++ = (unsigned)(i)))
# define PUTLONG(l, p)	\
	((*(p)++ = (unsigned long)(l) >> 24),		\
	 (*(p)++ = (unsigned long)(l) >> 16),		\
	 (*(p)++ = (unsigned long)(l) >> 8),		\
	 (*(p)++ = (unsigned long)(l)))
#endif

/*
 * The standard rrec structure doesn't have a space for the domain
 * name, so define our own.
 */
enum rr_sect { SECT_AN, SECT_NS, SECT_AR };
typedef struct rr {
    enum rr_sect rr_sect;		/* resource record section */
    char  *rr_dname;			/* domain name */
    short  rr_class;			/* class number */
    short  rr_type;			/* type number */
    int    rr_size;			/* size of data area */
    char  *rr_data;			/* pointer to data */
} RR;

/* structure for iterating over RR's with getnextrr() */
struct rr_iterator {
    RR rr;				/* space for storing RR */
    char dname[MAXDNAME];		/* space for storing domain name */
    char *dp;				/* pointer within packet */
    HEADER *hp;				/* saved header pointer */
    char *eom;				/* end of packet */
    int ancount;			/* count of answer records */
    int nscount;			/* count of ns records */
    int arcount;			/* count of additional records */
};

/*
 * import h_errno; many systems don't define it in <netdb.h>
 */

#ifndef OBSOLETE_RESOLVER
extern int h_errno;
#endif

/* functions local to this file */

#ifdef ANSI_C
# define P_(x) x
#else
# define P_(x) ()
#endif

static int bind_addr_work P_((char*,long,struct bindlib_private*,char*,
			      struct rt_info*,struct error **));
static char *strip_dots P_((char*));
static void flip P_((char *));   /* Flip address between UK/world order */
static char *rewrite_header P_((char *, char *, char *));
static int get_records P_((char*,int,HEADER*,int*,char **));
static RR *getnextrr P_((struct rr_iterator*,int));
static void rewindrr P_((struct rr_iterator*,HEADER*,int));
static int find_a_records P_((struct mx_transport_hint*,char*,char **));
static struct error *lost_server P_((char*));
static struct error *server_failure P_((char*,char*));
static struct error *packet_error P_((char*,char*,char*));
static struct error *no_valid_mx_records P_((char*,char*));
static struct error *matched_local_host P_((char*,char*));
static struct error *no_transport_error P_((char*,char*));
static int decode_mx_rr P_((RR*,struct rr_iterator*,char **,int*));
static struct transport_hints * new_mx_hint P_((int,char*,int));
static void add_mx_hint P_((struct transport_hints **,int,char*,int));
static void free_mx_hint P_((struct transport_hints **));
static void add_a_hint P_((struct mx_transport_hint*,char*,char*));

/*
 * bind_addr - lookup a host through the domain system
 *
 * Use the algorithm described at the top of this source file for
 * finding a match for a target.
 *
 * Return one of the following values:
 *
 * These return codes apply only to the specific address:
 *	DB_SUCCEED	Matched the target host.
 *	DB_NOMATCH	Did not match the target host.
 *	DB_FAIL		Fail the address with the given error.
 *	DB_AGAIN	Try to route with this address again at a
 *			later time.
 *
 * These return codes apply to this router in general:
 *	FILE_NOMATCH	There is no server running on this machine.
 *	FILE_AGAIN	Lost contact with server, or server is
 *			required to exist.  Try again later.
 *	FILE_FAIL	A major error has been caught in router,
 *			notify postmaster.
 */

int
bind_addr(raw_target, flags, priv, what, rt_info, error_p)
    char *raw_target;			/* raw target address */
    long flags;				/* bind-specific flags */
    struct bindlib_private *priv;	/* bind-specific data */
    char *what;				/* who called, for messages */
    struct rt_info *rt_info;		/* return route info here */
    struct error **error_p;		/* return lookup error here */
{
    long save_res_options;		/* like _res.options in <resolv.h> */
    int ret;

    save_res_options = _res.options;
    ret = bind_addr_work(raw_target, flags, priv, what, rt_info, error_p);
    _res.options = save_res_options;

    return ret;
}

static int
bind_addr_work(raw_target, flags, priv, what, rt_info, error_p)
    char *raw_target;			/* raw target address */
    long flags;				/* bind-specific flags */
    struct bindlib_private *priv;	/* bind-specific data */
    char *what;				/* who called, for messages */
    struct rt_info *rt_info;		/* return route info here */
    struct error **error_p;		/* return lookup error here */
{
    char *target;			/* raw_target stripped of extra dots */
    static char *orig_target = NULL;	/* original dot-stripped target */
    static char *full_target = NULL;	/* target sent by res_send */
    HEADER *mx_rrs = NULL;		/* response for MX rr's */
    int mx_size;			/* size of MX rr response packet */
    struct rr_iterator mx_it;		/* MX rr iterator */
    struct rr_iterator a_it;		/* A rr iterator */
    RR *mx_rr;				/* a single MX rr */
    RR *a_rr;				/* a single A rr */
    static int no_server = FALSE;	/* TRUE if no server process */
    static int found_server = FALSE;	/* TRUE if server process found */
    int success;			/* value to return */
    char *error;			/* error text from called function */
    int widencount = 0;                 /* number of times widened */
    int inverting = FALSE;              /* trying inverted address */
    int gated = FALSE;                  /* routed via a gateway */
    int UK_MX_rejected = FALSE;         /* at least one MX was rejected */
    int local_precedence = -1;		/* precedence of MX to local host */
    struct transport_hints *mx_hints = 0, **mx_a;
    struct transport_hints *hint;

    /*
     * memory management notes:
     *   target		points to static area returned by strip_dots().
     *			do not free.
     *   orig_target	static pointer of convenience.  do not free.
     *   full_target	static pointer of convenience.  do not free.
     */

    if (no_server) {
	return DB_NOMATCH;
    }

    /*
     * strip extra dots from the target to ensure sane lookups.
     * If the target contained any dots before being stripped,
     * then don't allow the resolver to add the default domain.
     * This allows mail to high-level domains (e.g., "com.")
     * while also allowing use of default domain suffixes.
     */

#ifdef RES_DEFNAMES
# ifndef RES_DNSRCH
# define RES_DNSRCH	0
# endif
    if (flags & BIND_DEFNAMES && strchr(raw_target, '.') == NULL) {
	_res.options |= RES_DEFNAMES|RES_DNSRCH;
    } else {
	_res.options &= ~(RES_DEFNAMES|RES_DNSRCH);
    }
#endif

    /*
     * Step 1: Omit needless dots!
     */

    target = strip_dots(raw_target);

    if (orig_target) {
	xfree(orig_target);
    }
    orig_target = COPY_STRING(target);

    if (full_target) {
	xfree(full_target);
    }
    full_target = NULL;

    /*
     * if the target is in one of the "ignore" domains, then don't
     * match it.  This prevents expensive lookups of domains that are
     * known not to exist in the DNS, such as .uucp or .bitnet.
     */

    if (priv->ignore_domains) {
	if (match_end_domain(priv->ignore_domains, target) != NULL) {
	    return DB_NOMATCH;
	}
    }

    if (debug >= DBG_DRIVER_HI) {
	_res.options |= RES_DEBUG;	/* turn on resolver debugging */
    }

    /*
     * first see if the address matches one of the list of special
     * gateways.  if so, change the target to the gateway, and set
     * the full_target to the original string so it will be passed
     * on to the gateway.  the flag prevents subsequent updating of
     * full_target from the DNS records.
     */

    if (priv->gateways) {
	char *cur;
	char *gateway = NULL;

	for (cur = strcolon(priv->gateways);
	     cur && !gated;
	     cur = strcolon((char *)NULL))
	{
	    if (!gateway) {
		gateway = COPY_STRING(cur);
	    }
	    else if (EQ(cur, "+")) {
		if (gateway) {
		    xfree(gateway);
		}
		gateway = NULL;
	    }
	    else if (is_suffix(cur, target, FALSE)) {
		full_target = COPY_STRING(target);
		target = strip_dots(gateway);
		gated = TRUE;
	    }
	}

	if (gateway) {
	    xfree(gateway);
	}
    }

    /* store return values for common case */
    rt_info->next_host = COPY_STRING(target);
    rt_info->route = NULL;
    rt_info->matchlen = strlen(raw_target);

    /*
     * if the domain_required flag is set, then the target hostname
     * is required to have at least one "."
     */

    if (flags & BIND_DOMAIN_REQUIRED && strchr(target, '.') == NULL) {
	return DB_NOMATCH;
    }

get_target_mx_records:

    DEBUG1(DBG_ROUTE_LO, "Trying %s\n", target);

    /* Step 2:  See if there are any MX records */
    if (mx_rrs == NULL) {
	mx_rrs = (HEADER *)xmalloc(MAXPACKET);
    }

    success = get_records(target, T_MX, mx_rrs, &mx_size, &error);
    if ((success == DB_NOMATCH) && !(flags & BIND_MX_ONLY))
	success = get_records(target, T_ANY, mx_rrs, &mx_size, &error);

    if (success != DB_SUCCEED) {
	switch (success) {

	case FILE_NOMATCH:
	    if (! (flags & BIND_DEFER_NO_CONN) && ! found_server) {
		no_server = TRUE;
		success = DB_NOMATCH;
		break;
	    }
	    if (found_server) {
		*error_p = lost_server(what);
	    } else {
		*error_p =
		    server_failure(what, "Connection to BIND server failed");
	    }
	    success = FILE_AGAIN;
	    break;

	case DB_AGAIN:
	case DB_FAIL:
	case FILE_AGAIN:
	    *error_p = server_failure(what, error);
	    break;

	case DB_NOMATCH:

	    /*
	     * Before giving up, we try widening the name according to a
	     * configured list. Because the strcolon routine re-uses its
	     * store, we can't remember the position globally. We just
	     * remember the count of widenings. As there will typically be
	     * only two or three, don't worry about the inefficiency.
	     */

	    /* Don't do it if gated.  (Shouldn't get here, anyway.) */
	    if (!gated) {
		char *w;
		int i;

		w = strcolon(priv->widen_domains ? priv->widen_domains : "");
		for (i = 0; i < widencount; i++)
		    w = strcolon((char *)NULL);
		if (w && *w)
		{
		    struct str new;	/* region for building new string */

		    STR_INIT(&new);
		    STR_CAT(&new, orig_target);
		    STR_NEXT(&new, '.');
		    STR_CAT(&new, w);
		    STR_NEXT(&new, '\0');
		    STR_DONE(&new);

		    target = strip_dots(new.p);
		    xfree(rt_info->next_host);
		    rt_info->next_host = new.p;

		    widencount++;
		    goto get_target_mx_records;
		}

		/*
		 * Deal with setting up for flipping addresses; don't do it
		 * the address is already in the "uk" domain.
		 */

		else if ((flags & BIND_UK_TRY_INVERT) && !inverting &&
			 !is_suffix("uk", orig_target, TRUE))
		{
		    inverting = TRUE;
		    flip(orig_target);     /* does it in situ */

		    target = strip_dots(orig_target);
		    xfree(rt_info->next_host);
		    rt_info->next_host = COPY_STRING(target);

		    widencount = 0;
		    goto get_target_mx_records;
		}
	    }
	    break;
	}

	xfree((char *)mx_rrs);
	return success;
    }

    /* We have connected to the server at least once */
    found_server = TRUE;

    /* See if there are any MX records */
    rewindrr(&mx_it, mx_rrs, mx_size);
    while ((mx_rr = getnextrr(&mx_it, FALSE)) && mx_rr->rr_type != T_MX)
	;

    if (mx_rr == NULL) {

	/* Step 3:  Look for a CNAME record */
	rewindrr(&mx_it, mx_rrs, mx_size);
	while ((mx_rr = getnextrr(&mx_it, FALSE)) && mx_rr->rr_type != T_CNAME)
	    ;
	if (mx_rr) {
	    static char nambuf[MAXDNAME];
	    int dlen;

	    dlen = dn_expand((char *)mx_it.hp, mx_it.eom, mx_rr->rr_data,
			     nambuf, MAXDNAME);
	    if (dlen < 0) {
		/* format error in response packet */
		*error_p = packet_error(what, "CNAME", target);

		xfree((char *)mx_rrs);
		return DB_AGAIN;
	    }
	    target = nambuf;
	    xfree(rt_info->next_host);
	    rt_info->next_host = COPY_STRING(target);

	    /*
	     * RFC 974:
	     * There is one other special case.  If the response contains
	     * an answer which is a CNAME RR, it indicates that REMOTE is
	     * actually an alias for some other domain name. The query
	     * should be repeated with the canonical domain name.
	     */
	    /*
	     * The next query should really be done with the DEFNAMES flag
	     * cleared !!
	     */
	    goto get_target_mx_records;
	}

	if (! (flags & BIND_MX_ONLY)) {
	    /*
	     * from RFC 974:
	     * It is possible that the list of MXs in the response to
	     * the query will be empty.  This is a special case.  If the
	     * list is empty, mailers should treat it as if it contained
	     * one RR, an MX RR with a preference value of 0, and a host
	     * name of REMOTE.  (I.e., REMOTE is its only MX).  In
	     * addition, the mailer should do no further processing on
	     * the list, but should attempt to deliver the message to
	     * REMOTE.  The idea here is that if a domain fails to
	     * advertise any information about a particular name we will
	     * give it the benefit of the doubt and attempt delivery.
	     */
	    char *fullname;

	    if (islocalhost(target))
		local_precedence = 0;
	    if (_res.options & RES_DEFNAMES &&
		strchr(target, '.') == NULL &&
		_res.defdname[0] != '\0')
	    {
		add_mx_hint(&mx_hints, 0,
			    xprintf("%s.%s", target, _res.defdname),
			    TRUE);
		fullname = xprintf("%s.%s", target, _res.defdname);
	    } else {
		add_mx_hint(&mx_hints, 0, target, TRUE);
		fullname = COPY_STRING(target);
	    }

	    /*
	     * Deal with widening of the target name. This may have been
	     * done explicitly above, or it may have been done by the DNS
	     * resolver for an unqualified name. We cautiously do this only
	     * for UK addresses, but it is probably OK in general, except
	     * that it wastes time. Note: do *not* do this for explicitly
	     * gatewayed addresses. We have to do this separately for the
	     * MX and non-MX cases, as the data comes from different places,
	     * in the MX case, aliases may have been followed, etc.
	     */

	    if (!gated && is_suffix("uk", fullname, TRUE)) {
		struct list *q;

		for (q = header; q; q = q->succ) {
		    if (strncmpic(q->text, "to:", 3) == 0 ||
			strncmpic(q->text, "cc:", 3) == 0)
		    {
			q->text = rewrite_header(q->text, raw_target,
						 fullname);
		    }
		}
	    }

	    xfree(fullname);
	}
    }

    /* mx records received: scan them */

    else {
	rewindrr(&mx_it, mx_rrs, mx_size);
	while (mx_rr = getnextrr(&mx_it, FALSE)) {
	    if (mx_rr->rr_type == T_MX) {
		char *name;
		int precedence;

		success = decode_mx_rr(mx_rr, &mx_it, &name, &precedence);
		if (success != DB_SUCCEED) {
		    *error_p = packet_error(what, "MX", target);
		    xfree((char *)mx_rrs);
		    return DB_AGAIN;
		}
		/*
		 * grab the domain returned in the packet as
		 * the real target name, unless gatewayed
		 */
                if (!gated) {
		    if (full_target) {
			xfree(full_target);
		    }
		    if (! EQIC(target, mx_it.dname)) {
			full_target = COPY_STRING(mx_it.dname);
		    } else {
			full_target = COPY_STRING(target);
		    }
                }

		/*
                 * Pick up the local host's precedence.
		 */
		if (islocalhost(name)
		    && (local_precedence < 0 || precedence < local_precedence))
		{
		    local_precedence = precedence;
		}

                /*
		 * If the address is in the UK, and is not gated, we must
		 * modify the To: and Cc: fields with the widened address.
		 */

                if (!gated && is_suffix("uk", mx_it.dname, TRUE))
		{
		    struct list *q;

		    for (q = header; q; q = q->succ) {
			if (strncmpic(q->text, "to:", 3) == 0 ||
			    strncmpic(q->text, "cc:", 3) == 0)
			{
			    q->text = rewrite_header(q->text, raw_target,
						     mx_it.dname);
			}
                    }
		}

		/*
		 * Use an MX if precedence is within bounds and if
		 * it doesn't specify a gateway to be ignored.
		 */

                if (is_suffix("uk", mx_it.dname, TRUE) &&
		    ((priv->uk_max_precedence > 0 &&
		      precedence > priv->uk_max_precedence) ||
		     (priv->uk_ignore_gateways &&
		      is_string_in_list(name, priv->uk_ignore_gateways))))
		{
		    UK_MX_rejected = TRUE;
		}
                else
		{
		    /*
		     * This test sometimes avoids adding exchangers that must
		     * not be used.
		     */
		    if (local_precedence < 0 || precedence < local_precedence)
			add_mx_hint(&mx_hints, precedence, name, FALSE);
		}
	    }
	}
    }

    mx_a = &mx_hints;
    while (*mx_a) {
#define mx_hint ((struct mx_transport_hint *)((*mx_a)->private))
	int precedence = mx_hint->preference;

	if (local_precedence >= 0 && precedence >= local_precedence) {
	    /*
	     * RFC 974:
	     * If the domain name LOCAL is listed as an MX RR, all MX
	     * RRs with a preference value greater than or equal to that
	     * of LOCAL's must be discarded.
	     */
	    free_mx_hint(mx_a);
	    continue;
	}
	mx_a = &(*mx_a)->succ;
#undef mx_hint
    }

    /* if there were no valid MX records... */
    if (! mx_hints) {
	xfree((char *)mx_rrs);

	if (local_precedence >= 0) {
	    if ((flags & BIND_LOCAL_MX_OKAY) == 0) {
		*error_p = matched_local_host(what, target);
		return DB_FAIL;
	    }
	    return DB_NOMATCH;
	}

	/*
	 * This is not an error in the UK if MX records were rejected
	 * for "UK" reasons (i.e. via the preference value or
	 * uk_ignore_gateways.)  In this case, we assume that JANET mail
	 * can reach the address, since all sites with this kind of
	 * gateway MX record should be accessible via GreyBook mail.
	 *
	 * A configuration option specifies whether this system has a
	 * greybook transport or not. If not, just yield NOMATCH,
	 * assuming a smarthost driver will pick up the address.
	 */

        if (UK_MX_rejected)
	{
	    struct transport *tp;

	    if (priv->uk_greybook_transport == NULL)
		return DB_NOMATCH;

	    rt_info->transport = find_transport(priv->uk_greybook_transport);
	    rt_info->route = full_target;
	    full_target = NULL;

	    if (!rt_info->transport) {
		*error_p = no_transport_error(what,
					      priv->uk_greybook_transport);
		return DB_NOMATCH;
	    }

	    /*
	     * Ensure that the target and the route are in UK order,
	     * unless the option is set to suppress this (future hope!)
	     */
	    if (!(flags & BIND_UK_GREY_WORLD))
	    {
		if (is_suffix("uk", rt_info->route, TRUE))
		    flip(rt_info->route);
		if (is_suffix("uk", rt_info->next_host, TRUE))
		    flip(rt_info->next_host);
	    }
	    return DB_SUCCEED;
	}

	/* it was an error, after all */
	if (!(flags & BIND_MX_ONLY)) {
	    *error_p = no_valid_mx_records(what, target);
	}
	return DB_NOMATCH;
    }

    /*
     * if any MX record points to a different host than the target,
     * make sure the target hostname is passed to that host.
     */
    if (full_target) {
	for (hint = mx_hints; hint; hint = hint->succ) {
	    if (EQIC(hint->hint_name, "mx")) {
#define mx_hint ((struct mx_transport_hint *)(hint->private))
		if (! EQIC(mx_hint->exchanger, full_target)) {
		    rt_info->route = full_target;
		    full_target = NULL;
		    break;
		}
	    }
	}
    }

    /* look for relevant A records in the additional section of the MX answer */
    rewindrr(&a_it, mx_rrs, mx_size);
    while ((a_rr = getnextrr(&a_it, TRUE))!=0) {
	if (a_rr->rr_type==T_A) {
	    for (hint = mx_hints; hint; hint = hint->succ) {
#define mx_hint ((struct mx_transport_hint *)(hint->private))
		if (EQIC(mx_hint->exchanger, a_it.dname))
		    add_a_hint(mx_hint, a_it.dname, a_rr->rr_data);
#undef mx_hint
	    }
	}
    }

    xfree((char *)mx_rrs);
    mx_a = &mx_hints;
    while (*mx_a) {
#define mx_hint ((struct mx_transport_hint *)((*mx_a)->private))
	if (! mx_hint->ipaddrs) {
	    /* Go out and get an A record */
	    success = find_a_records(mx_hint, mx_hint->exchanger, &error);
	    switch (success) {

	    case DB_SUCCEED:
		mx_a = &(*mx_a)->succ;
		break;

	    case DB_NOMATCH:
		if (! mx_hint->implicit)
		    /* This is an error! */;
		free_mx_hint(mx_a);
		break;

	    case FILE_NOMATCH:
		success = FILE_AGAIN;
		/* FALL THROUGH */

	    case DB_FAIL:
	    case DB_AGAIN:
	    case FILE_AGAIN:
		/* Step 5:  Do not match the target */
		*error_p = server_failure(what, error);
		return success;
	    }
	} else {
	    mx_a = &(*mx_a)->succ;
	}
#undef mx_hint
    }

    if (mx_hints) {
	rt_info->tphint_list = mx_hints;
	return DB_SUCCEED;
    } else {
	return DB_NOMATCH;
    }
}

/*
 * strip_dots - remove extra dots from a hostname string
 *
 * Remove all dots from the beginning and end of a string.  Also, any
 * sequence of more than one dot is replaced by a single dot.  For
 * example, the string:
 *
 *	.att..com.
 *
 * will result in the string:
 *
 *	att.com
 *
 * The operation is non-destructive on the passed string.  The
 * resulting value points to a region which may be reused on
 * subsequent calls to strip_dots().
 */
static char *
strip_dots(s)
    register char *s;
{
    static struct str new;		/* region for building new string */
    static int inited = FALSE;		/* true if target initialized */

    /* initialize or clear the new string */
    if (! inited) {
	STR_INIT(&new);
    } else {
	new.i = 0;
    }

    /*
     * copy target, removing extra dots.
     */
    while (*s == '.') s++;
    do {
	if (*s == '.') {
	    while (*s == '.') s++;
	    if (*s) --s;
	}
	STR_NEXT(&new, *s);
    } while (*s++);

    return new.p;
}

/*
 * flip - flip address between UK/world order
 *
 * operates on the string in situ
 */
static void
flip(s)
    char *s;
{
    static struct str new;		/* region for building new string */
    static int inited = FALSE;		/* true if target initialized */
    char *p;

    /* initialize or clear the new string */
    if (! inited) {
	STR_INIT(&new);
    } else {
	new.i = 0;
    }

    p = s + strlen(s);
    while (p > s) {
	if (*--p == '.') {
	    *p = '\0';
	    if (*(p + 1)) {
		STR_CAT(&new, p + 1);
		if (p > s) {
		    STR_NEXT(&new, '.');
		}
	    }
	}
    }
    STR_CAT(&new, s);
    strcpy(s, new.p);
}

/*
 * rewrite-header - re-write an address in a header to be the
 * correct UK address, possibly widened and/or re-ordered from
 * what the user typed. Used for To: and Cc: fields.
 *
 * Under some circumstances smail seems to go round the loop
 * twice, so be careful not to do the job twice.
 */

static char *
rewrite_header(text, oldtarget, newtarget)
    char *text;
    char *oldtarget;
    char *newtarget;
{
    char *p = text;
    int oldlen = strlen(oldtarget);
    int newlen = strlen(newtarget);

    while (*p)
    {
	while (*p != 0 && *p != '@')
	    p++;
	if (*p == 0)
	    break;

	if (strncmpic(++p, oldtarget, oldlen) == 0)
	{
	    int c = p[oldlen];
	    if (c == 0 || c == ',' || c == '>' ||
		c == ' ' || c == '\n' || c == '\t')
	    {
		if (oldlen == newlen)
		    memcpy(p, newtarget, newlen);
		else
		{
		    int baselen = p - text;
		    int oldtextlen = strlen(text);
		    char *newtext = xmalloc(oldtextlen+1+(newlen-oldlen));
		    memcpy(newtext, text, baselen);
		    memcpy(newtext+baselen, newtarget, newlen);
		    memcpy(newtext+baselen+newlen, p+oldlen,
			   oldtextlen-baselen-oldlen+1);
		    xfree(text);
		    text = newtext;
		    p = text + baselen + newlen;
		}
	    }
	}
    }

    return text;
}


/*
 * get_records - query the domain system for resource records of the given
 *		 name.
 *
 * Send out a query and return the response packet in a passed buffer.
 * Resource records are in the bytes following the header for that
 * packet.  The actual size of the response packet is stored in pack_size.
 *
 * The passed answer buffer must have space for at least MAXPACKET
 * bytes of data.
 *
 * Return one of the following response codes:
 *
 *	DB_SUCCEED	We received an affirmative packet from the
 *			server.
 *	DB_NOMATCH	We received a negative response from the server
 *			indicating that the name does not exist.
 *	DB_FAIL		We received a negative response from the
 *			server indicating some problem with the
 *			packet.
 *	DB_AGAIN	We received a negative response from the
 *			server indicating a temporary failure while
 *			processing the name.
 *	FILE_NOMATCH	We could not connect to the server.
 *	FILE_AGAIN	There was a failure in the server, try again
 *			later.
 */
static int
get_records(qname, qtype, answer, pack_size, error)
    char *qname;			/* search for this name */
    int qtype;				/* and for records of this type */
    register HEADER *answer;		/* buffer for storing answer */
    int *pack_size;			/* store answer packet size here */
    char **error;			/* store error message here */
{
#ifdef OBSOLETE_RESOLVER
    char msgbuf[MAXPACKET];
#endif
    int msglen;
    int anslen;

#ifdef OBSOLETE_RESOLVER
    msglen = res_mkquery(QUERY, qname, C_IN, qtype, (char *)NULL, 0,
			 (struct rrec *)NULL, msgbuf, MAXPACKET);

    anslen = res_send(msgbuf, msglen,  (char *)answer, MAXPACKET);
    if (anslen < 0) {
	return FILE_NOMATCH;
    }
#else /* not OBSOLETE_RESOLVER */
    anslen = res_search(qname, C_IN, qtype, answer, MAXPACKET);
    if (anslen < 0) {
	switch (h_errno) {
	case NO_DATA:
	    *pack_size = 0;
	    return DB_SUCCEED;

	case HOST_NOT_FOUND:
	    return DB_NOMATCH;

	case TRY_AGAIN:
	    *error = "Nameserver: Server failure";
	    return DB_AGAIN;

	case NO_RECOVERY:
	    *error = "Irrecoverable nameserver error";
	    return FILE_NOMATCH;

	default:
	    *error = "Unknown nameserver error";
	    return FILE_NOMATCH;
	}
    }
#endif /* not OBSOLETE_RESOLVER */
    *pack_size = anslen;

    answer->qdcount = ntohs(answer->qdcount);
    answer->ancount = ntohs(answer->ancount);
    answer->nscount = ntohs(answer->nscount);
    answer->arcount = ntohs(answer->arcount);

    switch (answer->rcode) {

    case NOERROR:
	return DB_SUCCEED;
#ifdef OBSOLETE_RESOLVER
    case FORMERR:
	*error = "Nameserver: Format error in packet";
	return DB_FAIL;

    case SERVFAIL:
	*error = "Nameserver: Server failure";
	return DB_AGAIN;

    case NXDOMAIN:
	return DB_NOMATCH;

    case NOTIMP:
	*error = "Nameserver: Unimplemented request";
	return DB_FAIL;

    case REFUSED:
	*error = "Nameserver: Query refused";
	return FILE_AGAIN;
#endif /* OBSOLETE_RESOLVER */
    default:
	*error = "Nameserver: Unknown response code";
	return DB_FAIL;
    }
}

/*
 * getnextrr - get a sequence of resource records from a name server
 *	       response packet.
 *
 * The first time getnextrr() is called to process a packet, pass it
 * the header address of the packet.  For subsequent calls pass NULL.
 * When no more records remain, getnexrr() returns NULL.
 *
 * To process a specific response section, pass the section in sect.
 */
static RR *
getnextrr(it, additional)
    register struct rr_iterator *it;	/* iteration variables */
    int additional;		/* interested in additional section RRs */
{
    int dnamelen;
    register unsigned char *dp;

    dp = (unsigned char *)it->dp;

    /* return NULL if no rr's remain */
    if (it->ancount != 0) {
	--it->ancount;
	it->rr.rr_sect = SECT_AN;
    } else if (additional && it->nscount != 0) {
	--it->nscount;
	it->rr.rr_sect = SECT_NS;
    } else if (additional && it->arcount != 0) {
	--it->arcount;
	it->rr.rr_sect = SECT_AR;
    } else {
	return NULL;
    }

    dnamelen = dn_expand((char *)it->hp, it->eom, dp, it->dname, MAXDNAME);
    if (dnamelen < 0) {
	return NULL;
    }
    dp += dnamelen;
    GETSHORT(it->rr.rr_type, dp);	/* extract type from record */
    GETSHORT(it->rr.rr_class, dp);	/* extract class */
    dp += 4;				/* skip time to live */
    GETSHORT(it->rr.rr_size, dp);	/* extract length of data */
    it->rr.rr_data = (char *)dp;	/* there is the data */
    it->dp = (char *)(dp + it->rr.rr_size); /* skip to next resource record */

    return &it->rr;
}

static void
rewindrr(it, hp, packsize)
    register struct rr_iterator *it;
    HEADER *hp;
    int packsize;
{
    int dnamelen;
    int qdcount;

    it->dp = (char *)(hp + 1);
    it->hp = hp;
    it->eom = (char *)hp + packsize;
    it->rr.rr_dname = it->dname;
    it->ancount = it->hp->ancount;
    it->nscount = it->hp->nscount;
    it->arcount = it->hp->arcount;
    qdcount = it->hp->qdcount;
    /* skip over questions */
    while (qdcount > 0) {
	dnamelen = dn_expand((char *)it->hp, it->eom, it->dp,
			     it->dname, MAXDNAME);
	if (dnamelen < 0) {
	    it->ancount = it->nscount = it->arcount = 0;
	}
	it->dp += dnamelen;
	it->dp += 4;			/* skip over class and type */
	--qdcount;
    }
}


/*
 * find_a_records - look for an A record for the target
 *
 * Look for an A record for the target, and return an appropriate
 * response code:
 *
 *	DB_SUCCEED	An A record was found for the target.
 *	DB_NOMATCH	There was not an A record.
 *	DB_FAIL		There was a server error for this query.
 *	DB_AGAIN	There was a server error for this query, try
 *			again later.
 *	FILE_AGAIN	Server error, try again later.
 *	FILE_NOMATCH	Could not connect to server.
 *
 * For response codes other than DB_SUCCEED and DB_NOMATCH, store an
 * error message.
 */
static int
find_a_records(mx_hint, target, error)
    struct mx_transport_hint * mx_hint;
    char *target;
    char **error;
{
    int success;
    HEADER *a_rrs;
    int a_size;
    struct rr_iterator a_it;
    RR *a_rr;
    int result = DB_NOMATCH;

    a_rrs = (HEADER *)xmalloc(MAXPACKET);
    success = get_records(target, T_A, a_rrs, &a_size, error);

    if (success != DB_SUCCEED) {
	xfree((char *)a_rrs);
	return success;
    }

    rewindrr(&a_it, a_rrs, a_size);
    while ((a_rr = getnextrr(&a_it, FALSE))!=0) {
	if (a_rr->rr_type == T_A) {
	    result = DB_SUCCEED;
	    add_a_hint(mx_hint, a_it.dname, a_rr->rr_data);
	}
    }
    xfree((char *)a_rrs);
    return result;
}

static int
decode_mx_rr(rr, it, name, precedence)
    RR *rr;
    struct rr_iterator *it;
    char **name;
    int *precedence;
{
    static char nambuf[MAXDNAME];
    unsigned char *s = (unsigned char *)rr->rr_data;
    int dlen;

    GETSHORT(*precedence, s);
    dlen = dn_expand((char *)it->hp, it->eom, s, nambuf, MAXDNAME);
    if (dlen < 0) {
	return DB_FAIL;
    }
    *name = nambuf;
    return DB_SUCCEED;
}


/*
 * Create error structures for various errors.
 */

static struct error *
lost_server(what)
    char *what;
{
    char *error_text;

    /*
     * ERR_163 - lost connection to BIND server
     *
     * DESCRIPTION
     *	    Lost connection to the nameserver.
     *
     * ACTIONS
     *      Try again later.
     *
     * RESOLUTION
     *	    Hopefully, a later retry will reconnect.
     */
    error_text = xprintf("%s: Lost connection to BIND server: %s",
			 what, strerrno());
    DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);

    return note_error(ERR_163, error_text);
}

static struct error *
server_failure(what, error)
    char *what;
    char *error;			/* additional error text */
{
    char *error_text;

    /*
     * ERR_164 - failure talking to BIND server
     *
     * DESCRIPTION
     *      An error occured when sending or receiving packets from
     *	    the BIND server, or the server registered an error in the
     *	    response packet.
     *
     * ACTIONS
     *	    Actions depend upon the specific error.  Usually, a retry
     *	    is attempted later.
     *
     * RESOLUTION
     *	    Resolution depends upon the specific error.
     */
    error_text = xprintf("%s: BIND server failure: %s: %s",
			 what, error, strerrno());
    DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);

    return note_error(ERR_NPOSTMAST | ERR_164, error_text);
}

static struct error *
packet_error(what, type, host)
    char *what;
    char *type;				/* type name of packet */
    char *host;				/* target host */
{
    char *error_text;

    /*
     * ERR_165 - response packet format error
     *
     * DESCRIPTION
     *	    A format error was found in a packet returned by the BIND
     *	    name server.
     *
     * ACTIONS
     *	    Retry again later, in the hope that the problem will go
     *	    away.
     *
     * RESOLUTION
     *	    This is probably a bug in the nameserver.
     */
    error_text =
	xprintf("%s: BIND server format error in %s packet for %s",
		what, type, host);
    DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);

    return note_error(ERR_165, error_text);
}

static struct error *
no_valid_mx_records(what, host)
    char *what;
    char *host;				/* target hostname */
{
    char *error_text;

    /*
     * ERR_168 - no valid MX records for host
     *
     * DESCRIPTION
     *	    There were MX records for the target host, though all of
     *	    them were rejected.
     *
     * ACTIONS
     *	    Delivery to the target fails.
     *
     * RESOLUTION
     *	    The postmaster should look into the problem.
     */
    error_text = xprintf("%s: no valid MX records for %s",
			 what, host);
    DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);

    return note_error(ERR_NSOWNER | ERR_168, error_text);
}

static struct error *
matched_local_host(what, host)
    char *what;
    char *host;				/* target hostname */
{
    char *error_text;

    /*
     * ERR_169 - MX record points to local host
     *
     * DESCRIPTION
     *	    The MX record for the target host points to the local
     *	    host, but the local host is not prepared to handle this
     *	    case.
     *
     * ACTIONS
     *	    The domain database should probably be looked at.
     *
     * RESOLUTION
     *	    The postmaster should probably look into the problem.
     */
    error_text = xprintf("%s: MX record for %s points to local host",
			 what, host);
    DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);

    return note_error(ERR_NSOWNER | ERR_169, error_text);
}

static struct error *
no_transport_error(what, transport)
    char *what;
    char *transport;			/* name of missing transport */
{
    char *error_text;

    /*
     * ERR_110 - no transport for router
     *
     * DESCRIPTION
     *	    A format error was found in a packet returned by the BIND
     *	    name server.
     *
     * ACTIONS
     *	    Defer delivery.
     *
     * RESOLUTION
     *	    This is a configuration bug.
     */
    error_text =
	xprintf("%s: no such transport %s", what, transport);
    DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);

    return note_error(ERR_CONFERR | ERR_110, error_text);
}

static struct transport_hints *
new_mx_hint(pref, dname, implicit)
    int pref;
    char * dname;
    int implicit;
{
    struct transport_hints * new_hint;
    struct mx_transport_hint * mx_hint;

    new_hint = (struct transport_hints *) xmalloc(sizeof(*new_hint));
    mx_hint = (struct mx_transport_hint *) xmalloc(sizeof(*mx_hint));
    new_hint->succ = (struct transport_hints *) 0;
    new_hint->hint_name = "mx";
    new_hint->private = (char *) mx_hint;
    mx_hint->preference = pref;
    mx_hint->exchanger = dname;
    mx_hint->implicit = implicit;
    mx_hint->ipaddrs = (struct ipaddr_hint *) 0;
    return new_hint;
}

static void
add_mx_hint(addr, precedence, name, implicit)
    struct transport_hints ** addr;
    int precedence;
    char * name;
    int implicit;
{
    struct transport_hints * new_hint;

    new_hint = new_mx_hint(precedence, COPY_STRING(name), implicit);
#define mxhint(hint) ((struct mx_transport_hint *)((hint)->private))
    while (*addr && (! EQ("mx",(*addr)->hint_name)
		     || mxhint(*addr)->preference < precedence))
    {
	addr = &(*addr)->succ;
    }
#undef mxhint
    new_hint->succ = *addr;
    *addr = new_hint;
}

static void
add_a_hint(mx_hint, hostname, address)
    struct mx_transport_hint *mx_hint;
    char *hostname;
    char *address;
{
    struct ipaddr_hint * a_hint, ** ah;

    a_hint = (struct ipaddr_hint *) xmalloc(sizeof(*a_hint));
    a_hint->succ = NULL;
    a_hint->hostname = COPY_STRING(hostname);
    memcpy(&a_hint->addr, address, sizeof(a_hint->addr));

    ah = &mx_hint->ipaddrs;
    while (*ah) {
	ah = &(*ah)->succ;
    }
    (*ah) = a_hint;
}

static void
free_mx_hint(hint_p)
    struct transport_hints ** hint_p;
{
    struct transport_hints * next = (*hint_p)->succ;
    struct ipaddr_hint * addr, * next_addr;

#define mxhint ((struct mx_transport_hint *)((*hint_p)->private))
    for (addr = mxhint->ipaddrs; addr; addr = next_addr) {
	next_addr = addr->succ;
	xfree(addr->hostname);
	xfree((char *)addr);
    }
    xfree(mxhint->exchanger);
    xfree((char *)mxhint);
#undef mxhint
    xfree((char *)*hint_p);
    *hint_p = next;
}

#endif /* HAVE_BIND */
