/*
#ident	"@(#)smail/src:RELEASE-3_2_0_121:bindlib.c,v 1.126 2005/11/17 20:38:16 woods Exp"
 */

/*
 *    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, the "tcpsmtp" transport, and the SMTP
 *      client code, each of which can use 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.
 *      Rearranged, recoded and extended by Nigel Metheringham.
 *      One new feature added by Philip Hazel.
 *      Vastly improved and cleaned up by Greg A. Woods (including removal of
 *      the dead .uk greybook support).
 *      
 * Specifications for the bind router and/or tcpsmtp transport:
 *
 *      associated transports:
 *          Generally used with an smtp transport.
 *
 *      private attribute data:
 *	    match_domains: domains to match.  If this attribute is set, and
 *              if the target domain is fully qualified, then only names under
 *              one of these domains will be matched.  All other domains will
 *              never be matched.  This allows you to have a private DNS domain
 *              that'll be matched and handled by a bind router instance, but
 *              to still utilize a smart host for public DNS routing.
 *          ignore_domains: domains to ignore.  Names under any of these
 *              domains will never be matched.  This could be used to prevent
 *              expensive lookups for domains that are known not to be in the
 *              DNS.
 *          required: the domain(s) that the MX target host is required to be
 *              in for the to be matched.  This can be used for hosts behind
 *              firewalls but which still see the public DNS but which can only
 *              successfully connect to a select few MXers.
 *              (XXX this is borked -- required should do what match_domains
 *              does and this feature should be called valid_mx_host_domains or
 *              similar)
 *          widen_domains: colon separated list of domains to use to
 *              extend an unmatched bare name.  XXX deprecated!
 *          mx_domains: domains for which the effect of mx_only (see below)
 *              will always be applied.  Domain list separated by colons.
 *              Negative matches can be forced using a preceding ! - this
 *              is useful for excluding a subdomain of a wider domain.
 *              Ordering matters here....
 *          gateways: List of top domains and their relay gateways.  These
 *              are in the form <gateway>:<domain>...:+:
 *          rewrite_headers: Controls the rewriting of the header addresses.
 *              Integer, the higher the more likely it is to rewrite.  Possible
 *              values are 0 (never rewrite), 1 (rewrite if required -
 *              basically if the address has been widened) and 2 (always
 *              rewrite - tally ho etc...!).  (XXX this massive hack should be
 *              ripped out along with widen_domains, defnames, and dns_search.)
 *          cname_limit: Sets a limit of the number of CNAMEs you can
 *              encounter while looking up an address.  This stops someone
 *              making a loop out of a couple of CNAMEs.  Default value 1.
 *
 *      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.  This is normally a very
 *              desirable thing to have happen since it prevents idiots from
 *              pointing their MXes at your hosts, but if you've been burdened
 *              with the ordeal of dealing with a legacy wildcard MX then this
 *              may be your only way out until you can scrap the wildcard.
 *          defnames:  append a default domain to an unqualified hostname,
 *              using the RES_DEFNAME flag to the resolver library.  This is
 *              never set by default and it should be deprecated and removed.
 *          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 (except of course localhost, but it must
 *              be matched as a local host and directed locally and never
 *              ever routed to!).
 *          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 records
 *              for routing.  See also mx_domains above, which does this for
 *              named domains only.  This is almost always the right thing to
 *              do.  Normally a later instance of the gethostbyname router
 *              driver will pick up plain hostnames that have only A records.
 *          dns_search:  allow the resolver to search through its domain
 *              list for matches.  Deprecated -- not really a good thing!
 *
 * Algorithm
 *     1.   Check that the domain is not being ignored
 *     2.   Check that the domain is not gatewayed.
 *     3.   Look up domain in DNS - get MX records
 *     4.   Select out MX records
 *     5.   Find a transport
 *     6.   If there is no match, and we can flip, redo 1-5 with flipped name.
 *              
 *     DNS lookup:-
 *      A   Try bare name in DNS
 *      B   Try extending name with widen_domains
 *      C   If CNAME is found, redo lookup with CNAME
 *      D   If MX records, put them into mx_hints
 *      E   If no MX records make an implicit MX record (unless mx_only)
 *      F   Remove all MX records with lower precedence than local host
 *          
 *     MX record selection
 *          Remove all records with higher precedence than local host
 *          
 *     Transport Matching
 *          If there are valid MX record(s) left, then you're all done!
 *              
 */

#include "defs.h"

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

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <stdio.h>
#include <errno.h>

#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
#  include <stdlib.h>
# endif
#endif

#ifdef HAVE_STRING_H
# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H)
#  include <memory.h>
# endif
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif

#ifdef __STDC__
# include <stdarg.h>
#else
# include <varargs.h>
#endif

#if defined(HAVE_UNISTD_H)
# include <unistd.h>
#endif

#include <pcre.h>

#include "smail.h"
#include "alloc.h"
#include "list.h"
#include "smailstring.h"
#include "dys.h"
#include "smailsock.h"
#include "main.h"
#include "parse.h"
#include "addr.h"
#include "exitcodes.h"
#include "log.h"
#include "route.h"
#include "bindlib.h"
#include "bindsmtpth.h"
#include "lookup.h"
#include "match.h"
#include "transport.h"
#include "header.h"
#include "extern.h"
#include "debug.h"
#include "error.h"
#include "smailport.h"

/* Additional return code
 * I need a negative match, which is a stronger statement than DB_NOMATCH
 * so I have hacked one in here, with a remap where it is used so it
 * returns a DB_NOMATCH out to the caller
 */
#ifndef DB_NEGMATCH
# define DB_NEGMATCH	(-8)
#endif

#ifdef SMALL_MEMORY
# define MAXPACKET      4096		/* you takes your chances.... */
#else
# define MAXPACKET      65536		/* BIND-8.3.4 and 9.2.1 use this... */
#endif

/* missing in some old versions of <arpa/nameser.h> */
#ifndef T_TXT
# define T_TXT		16
#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 */
    unsigned 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 */
};

/* Workspace area - makes the thing more re-enterant than globals! */
struct mx_work_area {
    char * what;                        /* Name of this router */
    char * matched_target;              /* name that matched in DNS */
    char * full_target;                 /* name returned from DNS */
    int inverted;                       /* did we need to invert */
    HEADER * mx_rrs;                    /* MX RRs */
    int mx_size;                        /* Size of MX record */
    struct transport_hints * mx_hints;  /* MX hints for transport */
    struct bindlib_private * priv;      /* Pointer to router private attributes */
    struct error ** error_p;            /* Pointer to return error messages */
    unsigned long int flags;		/* Router flags */
    int local_precedence;               /* Precedence of local host */
    int * found_server;                 /* Have we used the server */
    int * no_server;                    /* Is the server dead? */
};

static struct bindlib_private generic_bindattr = BIND_TEMPLATE_ATTRIBUTES;


/* functions local to this file */

static int bind_addr_work __P((char *, unsigned long, struct bindlib_private *, char *, struct rt_info *, struct error **));
static int full_mx_lookup __P((char *, struct mx_work_area *, int,
                       struct bindlib_private *));
static int find_mx_records __P((char *, struct mx_work_area *, int));
static int decode_cnames __P((struct mx_work_area *));
static int handle_null_mxs __P((struct mx_work_area *, struct bindlib_private *));
static int make_mx_hints __P((char *, struct mx_work_area *));
static int filter_mx_hints __P((struct mx_work_area *));
static int get_addr_hints __P((struct mx_work_area *));
static void make_rt_entry __P((struct rt_info *, char *, char *, struct transport_hints *));
static int do_straight_mx_lookup __P((char *, struct mx_work_area *));
static struct mx_work_area *mx_work_area_constructor  __P((struct bindlib_private *, struct error **, char *, unsigned long, int *, int *));
static void mx_work_area_destructor __P((struct mx_work_area *, int));
static char * check_if_gatewayed __P((char *, char *));
static char *rewrite_header __P((char *, char *, char *));
static int get_records __P((char*,int,HEADER*,int*, const char **));
static RR *getnextrr __P((struct rr_iterator*,int));
static void rewindrr __P((struct rr_iterator*,HEADER*,int));
static void do_header_rewrites __P((char *, char *));
static int find_mx_target_a_records __P((struct mx_transport_hint *, char *, char *, const char **));
static struct error *not_a_canonical_host __P((char*));
static struct error *server_failure __P((char *,const char *));
static struct error *packet_error __P((char*,char*,char*));
static struct error *matched_local_host __P((char*,char*));
static struct error *cname_loop_error __P((char*,char*,int));
static struct error *res_init_error __P((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*));
static char * match_full_or_end_domain __P((char * domains, char * target));


/*
 * bind_compute_domain
 *
 * Return the domain that we're in, if we can find it...
 *
 */
char *
bind_compute_domain ()
{
    /*  This initialises the resolver.  If the resolver is so old that RES_INIT
     *  isn't defined then the routines will initialise themselves - that is
     *  if the thing will compile at all!
     */
    if ((_res.options & RES_INIT)  == 0) {
	if (res_init() == EOF) {
	    DEBUG(DBG_DRIVER_LO, "ERROR: res_init() failed - unable to get domain\n");
	    return (NULL);
	}
    }

    return(COPY_STRING(_res.defdname));
}


/*
 * bind_check_if_canonical_host - check if domain is a canonical hostname and
 *				  its address matches inet
 *
 * Return one of the following values:
 *
 * These return codes apply only to the specific address:
 *      DB_SUCCEED	Yes, it is a canonical hostname.
 *      DB_NOMATCH	No, it is not a canonical hostname
 *      DB_FAIL		Negative response from server indicating some
 *			problem with the request.
 *      DB_AGAIN	Negative respone from server indicating a
 *			temporary problem.  Try again later.
 *
 * These return codes apply to this router in general:
 *      FILE_NOMATCH	Could not connect to the server.
 *      FILE_AGAIN	Lost contact with server, or other failure
 *			talking to server.  Try again later.
 *      FILE_FAIL	A major error has been caught in router,
 *			notify postmaster.
 */

#define CANONICAL_HOST_CHECK	"bind"	/* caller's are explicit enough */

int
bind_check_if_canonical_host(hostnm, inet, error_p)
    char *hostnm;			/* domain name of host */
    in_addr_t inet;			/* expected internet address for host */
    struct error **error_p;		/* pointer to error information */
{
    unsigned long save_res_options;	/* like _res.options in <resolv.h> */
    const char *error_msg = NULL; 	/* pointer for error string from get_records() */
    HEADER *a_rrs;
    int a_size;
    int a_count;
    struct rr_iterator a_it;
    RR *a_rr;
    int result;

    DEBUG2(DBG_DRIVER_MID, "bind_check_if_canonical_host(%s, %s) called.\n", hostnm, inet_ntoa(inet_makeaddr((in_addr_t) ntohl(inet), (in_addr_t) 0)));

#ifdef RES_INIT
    /*  This initialises the resolver.  If the resolver is so old that RES_INIT
     *  isn't defined then the routines will initialise themselves - that is
     *  if the thing will compile at all!
     */
    if ((_res.options & RES_INIT) == 0) {
	if (res_init() == EOF) {
	    DEBUG(DBG_DRIVER_LO, "ERROR: res_init() failed - deferring addresses\n");
	    *error_p = res_init_error(CANONICAL_HOST_CHECK);
	    return FILE_AGAIN;
	}
    }
#endif
    save_res_options = _res.options;

#ifdef RES_DEFNAMES
    /*
     * Don't allow the resolver to add the default domain.
     */
    _res.options &= ~(RES_DEFNAMES | RES_DNSRCH);
#else
    /*
     * XXX  we should append a dot to the name just to be sure!
     * 
     * alternately maybe get_records() should use res_query() and not
     * res_search()...  or at least offer the caller a way to do this if
     * RES_DEFNAMES isn't available.  (But then again why do we support such an
     * old and broken and remotely vulnerable resolver library anyway)?
     *
     * Who's idea was this stupid "defnames" B.S. anyway?
     */
# include "ERROR: old and broken resolver libraries are not supported!"
#endif
    a_rrs = (HEADER *) xmalloc((size_t) MAXPACKET);

    result = get_records(hostnm, T_A, a_rrs, &a_size, &error_msg);

    if (result != DB_SUCCEED) {
	xfree((char *) a_rrs);
	*error_p = server_failure(CANONICAL_HOST_CHECK, error_msg);
	return result;
    }

    result = DB_NOMATCH;		/* assume no data, or no A RRs, in response */
    *error_p = NULL;
    rewindrr(&a_it, a_rrs, a_size);
    /*
     * Note we go through all the RRs just in case there's a CNAME too, which
     * is a grave DNS error, but we'll just report it as not being a 'hostname'
     * as we won't bother to check if there are other RRs with it.
     */
    a_count = 0;
    while ((a_rr = getnextrr(&a_it, FALSE)) != NULL) {
	if (EQIC(a_it.dname, hostnm)) {
	    if (a_rr->rr_type == T_A) {
		struct in_addr inaddr;

		memcpy((char *) &(inaddr), a_rr->rr_data, sizeof(inaddr));
		DEBUG4(DBG_DRIVER_HI, "found a DNS A record for the hostname '%s' with the address [%s] (0x%lx vs. 0x%lx)\n",
		       hostnm,
		       inet_ntoa(inaddr),
		       ntohl(inaddr.s_addr),
		       ntohl(inet));
		a_count++;
		if ((in_addr_t) inaddr.s_addr == inet) {
		    result = DB_SUCCEED;	/* Oh!  I guess it was a matching A RR! */
		}
	    } else if (a_rr->rr_type == T_CNAME) {
		*error_p = not_a_canonical_host(xprintf("the domain name '%s' is a CNAME not a hostname (a hostname must resolve to an A record, see RFC 1123 Section 5.2.2 and 5.2.5, RFC 2821 Section 4.1.4; and RFC 2181 Section 10.2)", hostnm));
		result = DB_NOMATCH;
		break;				/* this is an immediate error */
	    }
#ifdef DIAGNOSTIC /* #ifndef NDEBUG */
	    else {
		/* we don't really care about any other kind of RRs */
		DEBUG1(DBG_DRIVER_MID, "bind_check_if_canonical_host: got undesired RR type %d\n", (int) (a_rr->rr_type));
	    }
#endif
	} else {
	    DEBUG2(DBG_DRIVER_MID, "bind_check_if_canonical_host: '%s' != '%s'...\n", a_it.dname, hostnm);
	}
    }
    if (a_count == 0 && !*error_p) {
	*error_p = not_a_canonical_host(xprintf("there are no DNS A records for the domain name '%s'", hostnm));
    } else if (result != DB_SUCCEED && !*error_p) {
	*error_p = not_a_canonical_host(xprintf("%sthe DNS A record%s for the hostname '%s' %smatch the address [%s]",
						a_count > 1 ? "none of " : "",
						a_count > 1 ? "s" : "",
						hostnm,
						a_count == 1 ? "does not " : "",
						inet_ntoa(inet_makeaddr((in_addr_t) ntohl(inet), (in_addr_t) 0))));
    }
    xfree((char *) a_rrs);
    _res.options = save_res_options;

    return result;
}


/*
 * bind_check_if_local_mxs - check if localhost is MXing for target
 * 
 * If DB_SUCCEED && (*local_precedence_p >= 0) then yes, we do MX for the
 * target.
 * 
 * Return one of the following values:
 *
 * These return codes apply only to the specific address:
 *	DB_SUCCEED	Lookup successful
 *      DB_NOMATCH	There is no such domain.
 *      DB_FAIL		Negative response from server indicating some
 *			problem with the request.
 *      DB_AGAIN	Negative respone from server indicating a
 *			temporary problem.  Try again later.
 *
 * These return codes apply to this router in general:
 *      FILE_NOMATCH	Could not connect to the server.
 *      FILE_AGAIN	Lost contact with server, or other failure
 *			talking to server.  Try again later.
 *      FILE_FAIL	A major error has been caught in router,
 *			notify postmaster.
 */

#define LOCALHOST_MX_CHECK	"localhost MX check"

int
bind_check_if_local_mxs(target, local_precedence_p, error_p)
    char *target;
    int *local_precedence_p;
    struct error **error_p;
{
    unsigned long save_res_options;	/* like _res.options in <resolv.h> */
    struct mx_work_area *mxdat;		/* Work area for MX operations */
    int no_server = FALSE;		/* TRUE if no server process */
    int found_server = FALSE;		/* TRUE if server process found */
    int ret;

    DEBUG1(DBG_DRIVER_MID, "bind_check_if_local_mxs(%s) called.\n", target);

#ifdef RES_INIT
    /*  This initialises the resolver.  If the resolver is so old that RES_INIT
     *  isn't defined then the routines will initialise themselves - that is
     *  if the thing will compile at all!
     */
    if ((_res.options & RES_INIT)  == 0) {
	if (res_init() == EOF) {
	    DEBUG(DBG_DRIVER_LO, "ERROR: res_init() failed - deferring addresses\n");
	    *error_p = res_init_error(LOCALHOST_MX_CHECK);
	    return FILE_AGAIN;
	}
    }
#endif
    save_res_options = _res.options;

#ifdef RES_DEFNAMES
    /*
     * Don't allow the resolver to add the default domain.
     */
    _res.options &= ~(RES_DEFNAMES | RES_DNSRCH);
#endif

    mxdat = mx_work_area_constructor(&generic_bindattr, error_p, LOCALHOST_MX_CHECK, 0L, &found_server, &no_server);

    if ((ret = find_mx_records(target, mxdat, FALSE)) != DB_SUCCEED) {
	DEBUG1(DBG_DRIVER_LO, "no MXs for %s.\n", target);
	mx_work_area_destructor(mxdat, FALSE);
	_res.options = save_res_options;

	return ret;
    }
    if ((ret = decode_cnames(mxdat)) != DB_SUCCEED) {
	DEBUG1(DBG_DRIVER_LO, "problem decoding cnames for %s.\n", target);
	mx_work_area_destructor(mxdat, FALSE);
	_res.options = save_res_options;

	return ret;
    }
    if ((ret = make_mx_hints(target, mxdat)) != DB_SUCCEED) {
	DEBUG1(DBG_DRIVER_LO, "problem finding MX hints for %s.\n", target);
	mx_work_area_destructor(mxdat, FALSE);
	_res.options = save_res_options;

	return ret;
    }
    if ((ret = get_addr_hints(mxdat)) != DB_SUCCEED) {
	DEBUG1(DBG_DRIVER_LO, "problem filling in address hints for %s.\n", target);
	mx_work_area_destructor(mxdat, FALSE);
	_res.options = save_res_options;

	return ret;
    }
    ret = DB_SUCCEED;
    *local_precedence_p = mxdat->local_precedence;

    mx_work_area_destructor(mxdat, FALSE);
    _res.options = save_res_options;

    DEBUG2(DBG_DRIVER_MID, "bind_check_if_local_mxs(%s) says %s.\n",
	   target, (mxdat->local_precedence >= 0) ? "TRUE" : "FALSE");

    return ret;
}


/*
 * gather up the TXT records for a domain
 */
char *
bind_lookup_txt_rr(target, error_p)
    char *target;
    struct error **error_p;
{
    const char *error_msg = NULL;	/* pointer for error string from get_records() */
    HEADER *txt_rrs;
    int txt_size;
    struct rr_iterator txt_it;
    RR *txt_rr;
    int result;
    char *text = NULL;

    DEBUG1(DBG_DRIVER_HI, "bind_lookup_txt_rr(%s) called.\n", target);

    if (error_p) {
	*error_p = (struct error *) NULL;
    }

#ifdef RES_INIT
    /*  This initialises the resolver.  If the resolver is so old that RES_INIT
     *  isn't defined then the routines will initialise themselves - that is
     *  if the thing will compile at all!
     */
    if ((_res.options & RES_INIT)  == 0) {
	if (res_init() == EOF) {
	    DEBUG(DBG_DRIVER_LO, "ERROR: res_init() failed - cannot do TXT lookup\n");
	    if (error_p) {
		*error_p = res_init_error(LOCALHOST_MX_CHECK);
	    }
	    return (char *) NULL;
	}
    }
#endif
    txt_rrs = (HEADER *) xmalloc((size_t) MAXPACKET);
    result = get_records(target, T_TXT, txt_rrs, &txt_size, &error_msg);
    if (result != DB_SUCCEED) {
	xfree((char *) txt_rrs);
	if (error_p) {
	    *error_p = server_failure(CANONICAL_HOST_CHECK, error_msg);
	}
	return (char *) NULL;
    }
    rewindrr(&txt_it, txt_rrs, txt_size);
    while ((txt_rr = getnextrr(&txt_it, TRUE)) != NULL) {
	if (txt_rr->rr_type == T_TXT) {
	    char *tp, *cp, *cp2;

	    /* XXX maybe we should use dynamic strings and STR_CAT[N]()? */
	    if (!text) {
		text = xmalloc((size_t) (txt_rr->rr_size * 2) + 1);
		tp = text;
	    } else {
		int curlen = strlen(text);

		text = xrealloc(text, (size_t) (curlen + (txt_rr->rr_size * 2) + 2));
		tp = text + curlen + 1;
		text[curlen] = '\n';		
	    }
	    cp = txt_rr->rr_data;
	    cp2 = txt_rr->rr_data + txt_rr->rr_size;
	    while (cp < cp2) {
		int n;

		if ((n = (unsigned char) *cp++)) {
		    for (; n > 0 && cp < cp2; n--) {
			*tp++ = *cp++;
		    }
		}
		if (cp < cp2) {
		    *tp++ = '\n';		/* ???? */
		}
	    }
	    *tp = '\0';
	}
    }
    xfree((char *) txt_rrs);

    return text;
}


/*
 * 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 */
    unsigned 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 */
{
    unsigned long save_res_options;	/* like _res.options in <resolv.h> */
    int ret;

    DEBUG2(DBG_DRIVER_HI, "bind_addr(%s) called from %s.\n", raw_target, what);

#ifdef RES_INIT
    /*  This initialises the resolver.  If the resolver is so old that RES_INIT
     *  isn't defined then the routines will initialise themselves - that is
     *  if the thing will compile at all!
     */
    if ((_res.options & RES_INIT)  == 0) {
	if (res_init() == EOF) {
	    DEBUG(DBG_DRIVER_LO, "ERROR: res_init() failed - defering addresses\n");
	    *error_p = res_init_error(what);
	    return (FILE_FAIL);
	}
    }
#endif
    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(target, flags, priv, what, rt_info, error_p)
    char *target;			/* raw target address */
    unsigned 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 *orig_target = NULL;		/* set to target if target set to gateway */
    static int no_server = FALSE;	/* TRUE if no server process (not used) */
    static int found_server = FALSE;	/* TRUE if server process found (not used) */
    int result;				/* value to return */
    struct mx_work_area * mxdat;	/* Work area for MX operations */
    char *gateway = NULL;		/* gateway if target in gateways */

    if (no_server) {
	/* XXX should we modify error_p? */
        return FILE_AGAIN;		/* FILE_AGAIN more appropriate than DB_NOMATCH */
    }

#ifdef RES_DEFNAMES
    _res.options &= ~(RES_DEFNAMES | RES_DNSRCH);
    if (flags & BIND_DEFNAMES) {
	_res.options |= RES_DEFNAMES;	/* almost always a bad idea */
    }
    if (flags & BIND_DNS_SEARCH) {
	_res.options |= RES_DNSRCH;	/* XXX DANGER!  Experimental! */
    }
#endif

    /*
     * If the target is not in one of the "match" domains, then don't
     * match it.  This prevents lookups of domains that are known not 
     * to exist in the DNS.
     *
     * Note: If the target isn't a fully qualified domain name 
     *       (i.e. doesn't contain a '.'), then assume it to be
     *       inside the default domain.
     */

    if (priv->match_domains) {
	if (strchr(target, '.') != NULL) {
	    if (match_end_domain(priv->match_domains, target) == NULL) {
		DEBUG1(DBG_DRIVER_LO, "Target %s not in match_domains\n", target);
		/* XXX should we modify error_p? */

		return DB_NOMATCH;
	    } else {
		DEBUG1(DBG_DRIVER_LO, "Target %s in match_domains\n", target);
	    }
	} else {
	    DEBUG1(DBG_DRIVER_LO, "Target %s assumed to be in the default domain\n", target);
	}
    }

    /*
     * 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_full_or_end_domain(priv->ignore_domains, target) != NULL) {
	    /* XXX should we modify error_p? */
	    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
     * orig_target to the original string so it will be passed
     * on to the gateway.
     */
    if ((gateway = check_if_gatewayed(target, priv->gateways)) != NULL) {
	orig_target = target;
	target = COPY_STRING(gateway);
    }

    /*
     * the bind driver always effectively matches the whole target name if
     * it matches anything....
     */
    rt_info->matchlen = strlen(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) {
	DEBUG1(DBG_DRIVER_MID, "target '%s' is not a domain.\n", target);
	/* XXX should we modify error_p? */
	if (orig_target) {
	    xfree(target);
	}

	return DB_NOMATCH;
    }

    mxdat = mx_work_area_constructor(priv, error_p, what, flags, &found_server, &no_server);
    result = full_mx_lookup(target, mxdat, (gateway == NULL), priv);

    if (result == DB_SUCCEED) {
	/*
	 * if the target is not in one of the required domains, then don't
	 * match it. For systems behind firewalls who "see" systems on the
	 * Internet in DNS, but which can't actually reach them.  If a host
	 * is outside a local domain, this will fail and presumably fall
	 * through to smart_path.
	 */
	if (priv->required) {
	    if (match_full_or_end_domain(priv->required, mxdat->full_target) == NULL) {
		mx_work_area_destructor(mxdat, FALSE);
		DEBUG1(DBG_DRIVER_HI, "MX target '%s' is not in one of the required domains.\n", target);
		/* XXX should we modify error_p? */
		if (orig_target) {
		    xfree(target);
		}

		return DB_NOMATCH;
	    }
	}

	if (gateway) {
	    if (priv->rewrite_headers >= BIND_REWRITE_IFREQUIRED) {
		do_header_rewrites(orig_target, mxdat->full_target); /* XXX ??? is this right?, or to gateway? */
	    }
	    make_rt_entry(rt_info, mxdat->full_target, target, mxdat->mx_hints);
	} else {
	    if (priv->rewrite_headers >= BIND_REWRITE_IFREQUIRED) {
		do_header_rewrites(target, mxdat->full_target);
	    }
	    make_rt_entry(rt_info, mxdat->full_target, (char *) NULL, mxdat->mx_hints);
	}
	mx_work_area_destructor(mxdat, TRUE);
	if (orig_target) {
	    xfree(target);
	}
	
	return DB_SUCCEED;
    }
    mx_work_area_destructor(mxdat, FALSE);
    if (orig_target) {
	xfree(target);
    }

    return ((result == DB_NEGMATCH) ? DB_NOMATCH : result);
}


static void
make_rt_entry(rt_info, next_host, route, mx_hints)
     struct rt_info * rt_info;
     char * next_host;
     char * route;
     struct transport_hints * mx_hints;
{
    rt_info->next_host = COPY_STRING(next_host);
    if (route != NULL) {
        rt_info->route = COPY_STRING(route);
    } else {
        rt_info->route = NULL;
    }
    rt_info->tphint_list = mx_hints;
}


static void
do_header_rewrites(from, to)
    char *from;
    char *to;
{
    charplist_t *q = header;

    if EQIC(from, to) {
	return;
    }

    DEBUG2(DBG_ROUTE_MID, "Header rewrite \"%s\" -> \"%s\"\n", from, to);
    while (q != NULL) {
	/* XXX should do resent handling properly */
        if (strncmpic(q->text, "to:", (size_t) 3) == 0 ||
            strncmpic(q->text, "cc:", (size_t) 3) == 0) {
	    DEBUG1(DBG_ROUTE_HI, "Rewriting header -- %s", q->text);
	    q->text = rewrite_header(q->text, from, to);
            DEBUG1(DBG_ROUTE_HI,  "              to -- %s", q->text);
	}
        q = q->succ;
    }
}


/*
 * full_mx_lookup       - Performs all the sections of the MX lookup
 *                        including filtering and IP address lookup
 *
 * This co-ordinates all the subsections of an MX lookup.
 */
static int
full_mx_lookup (target, mxdat, allow_mangling, priv)
     char * target;
     struct mx_work_area * mxdat;
     int allow_mangling;
     struct bindlib_private *priv; 
{
    int result;                        /* how have we done? */

    result = find_mx_records(target, mxdat, allow_mangling);
    if (result != DB_SUCCEED) {
        return result;
    }

    result = decode_cnames(mxdat);
    if (result != DB_SUCCEED) {
        return result;
    }

    result = make_mx_hints(target, mxdat);
    if (result != DB_SUCCEED) {
        return result;
    }

    if (mxdat->mx_hints == NULL) {
	DEBUG1(DBG_DRIVER_MID, "full_mx_lookup: target '%s' needs null MX.\n", target);
        result = handle_null_mxs(mxdat, priv);
        if (result != DB_SUCCEED) {
            return result;
        }
        if (mxdat->priv->fallback_gateway) {
            add_mx_hint(&(mxdat->mx_hints), BIND_FALLBACK_PREFERENCE, 
                        mxdat->priv->fallback_gateway, TRUE);
	}
    } else {
        if (mxdat->priv->fallback_gateway) {
            add_mx_hint(&(mxdat->mx_hints), BIND_FALLBACK_PREFERENCE, 
                        mxdat->priv->fallback_gateway, TRUE);
	}
	if (! (mxdat->flags & BIND_DONT_FILTER)) { /* special for verify_sender() */
	    /* Can only filter MX records if there are some! */
	    result = filter_mx_hints(mxdat);
	}
        if (result != DB_SUCCEED) {
            return result;
        }
	if (mxdat->mx_hints == NULL) {
	    DEBUG1(DBG_DRIVER_MID, "full_mx_lookup: target '%s' had no MXs after filtering.\n", target);
	    mxdat->flags &= ~BIND_MX_ONLY; /* no other way to add ourselves! */

	    return handle_null_mxs(mxdat, priv);
	}
    }

    result = get_addr_hints(mxdat);
    if (result != DB_SUCCEED) {
        return result;
    }

    if (mxdat->mx_hints == NULL && !(mxdat->flags & BIND_LOCAL_MX_OKAY)) {
	DEBUG1(DBG_DRIVER_MID, "full_mx_lookup: target '%s' has no MX hints.\n", target);
        return DB_NOMATCH;              /* Not sure if you really can get here! */
    } else {
        return DB_SUCCEED;
    }
}



/*
 * find_mx_records      - look up the target in the DNS
 *
 * High level query function - looks up name in DNS.  Also performs
 * various name extensions as requested.  Returns a stack of
 * MX records in the mxdat struct as well as the name that actually
 * matched in the DNS.
 * The return value is the standard DB lookup status.
 */
static int
find_mx_records (target, mxdat, allow_mangling)
     char * target;
     struct mx_work_area * mxdat;
     int allow_mangling;
{
    char * cur_target = target;
    int result;

    result = do_straight_mx_lookup(cur_target, mxdat);

    if ((result == DB_NOMATCH) && allow_mangling) {
        char * w;                       /* Working string */
        static struct str p;            /* region for building new string */
        static int initialised = FALSE; /* Have I initialised p yet? */

	if (!initialised) {
            STR_INIT(&p);
            initialised++;
        }
        if (mxdat->priv->widen_domains != NULL) {
            for (w = strcolon(mxdat->priv->widen_domains); (w != NULL); (w = strcolon((char *) NULL))) {
		STR_LEN(&p) = 0;	/* Zero string length */
                str_cat(&p, target);
                str_cat(&p, ".");       /* Add a dot */
                str_cat(&p, w);         /* Append domain */
                cur_target = STR(&p);
                result = do_straight_mx_lookup(cur_target, mxdat);
                if (result != DB_NOMATCH) {
		    break;			/* Fall out end if not not found! */
		}
            }
        }
    }
    if (mxdat->matched_target) {
        xfree(mxdat->matched_target);
        mxdat->matched_target = NULL;
    }
    if (result == DB_SUCCEED) {
	if (target != cur_target) {
	    DEBUG2(DBG_DRIVER_HI, "find_mx_records: target '%s' matched something as '%s'\n", target, cur_target);
	}
        mxdat->matched_target = COPY_STRING(cur_target);
    } else {
	DEBUG2(DBG_DRIVER_MID, "find_mx_records: target '%s' not matched, returning %d.\n", target, result);
    }
    return result;
}


/*
 * do_straight_mx_lookup   - relatively simple straight lookup routine!
 *
 */
static int
do_straight_mx_lookup(target, mxdat)
     char * target;
     struct mx_work_area * mxdat;
{
    int result;
    const char *error_msg = NULL;

    DEBUG1(DBG_DRIVER_HI, "bindlib: MX lookup for %s\n", target);
    result = get_records(target, 
			 T_MX, 
			 mxdat->mx_rrs, 
			 &(mxdat->mx_size), 
			 &error_msg);
    switch (result) {

    case DB_SUCCEED:
        *mxdat->found_server = TRUE;
	if (mxdat->mx_rrs->ancount == 0) {
	    /* no valid answer records so let's remember the error just in case.... */
	    *mxdat->error_p = server_failure(mxdat->what, error_msg);
	}
        break;

    case DB_NOMATCH:
	*mxdat->error_p = server_failure(mxdat->what, error_msg);
	break;

    case DB_FAIL:
    case FILE_NOMATCH:
	*mxdat->error_p = server_failure(mxdat->what, error_msg);
	if (mxdat->flags & BIND_DEFER_NO_CONN) {
	    /* We should defer all addresses... hopefully the server will return! */
	    /* NB If this error is address related - ie it always happens to one
	     *    address we are in real trouble here - nothing will get done by
	     *    the router 
	     */
	    DEBUG1(DBG_DRIVER_MID, "bindlib: resolver returned %d, deferring with DB_AGAIN.\n", result);
	    result = DB_AGAIN;
	}
	break;

    case DB_AGAIN:
    case FILE_AGAIN:
        *mxdat->error_p = server_failure(mxdat->what, error_msg);
        break;
  
    }
    DEBUG2(DBG_DRIVER_HI, "bindlib: MX lookup for '%s' done, returning %d.\n", target, result);

    return result;
}



/*
 * decode_cnames        - Decode any cnames returned by earlier
 *                        lookups.
 *
 * If there are CNAME records in the set returned, then we look up on the
 * domain name pointed to by those records.  We iterate this, with a maximum of
 * cname_limit iterations (normally 1 -- the variable is a hack to assist
 * broken DNS configurations), until we get a proper match.
 */
static int decode_cnames(mxdat)
     struct mx_work_area * mxdat;
{
    struct rr_iterator mx_it;
    RR *mx_rr;
    int result;
    int num_iterations = 0;

    do {                                /* Iterate through any CNAMEs */
        rewindrr(&mx_it, mxdat->mx_rrs, mxdat->mx_size);
        while ((mx_rr = getnextrr(&mx_it, FALSE)) && mx_rr->rr_type != T_CNAME) {
            ;
	}
        if (mx_rr != NULL) {
            static char nambuf[MAXDNAME];
            int dlen;

            if ((mxdat->priv->cname_limit > 0) && (num_iterations++ >= mxdat->priv->cname_limit)) {
                *(mxdat->error_p) = cname_loop_error(mxdat->what, mxdat->matched_target, mxdat->priv->cname_limit);
                return DB_FAIL;
            }
            dlen = dn_expand((unsigned char *)mx_it.hp, (unsigned char *)mx_it.eom, 
                             (unsigned char *)mx_rr->rr_data, (char *)nambuf, 
                             MAXDNAME);
            if (dlen < 0) {
                /* format error in response packet */
                *(mxdat->error_p) = packet_error(mxdat->what, "CNAME", mxdat->matched_target);
                return DB_AGAIN;
            }
            /*
             * OK, we have a CNAME - lets do another lookup as told:-
             * 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.
             */
	    DEBUG1(DBG_DRIVER_MID, "decode_cnames() found CNAME pointing at %s.\n", nambuf);
            result =  find_mx_records(nambuf, mxdat, FALSE);
            if (result != DB_SUCCEED) {
                /* XXX Cleanup code goes here - deallocate storage */
                return result;
            }
        }
    } while (mx_rr != NULL);
    return DB_SUCCEED;
}


/*
 * handle_null_mxs      - Handle situation where no MX records are found
 *
 * This generally means putting in a zero precendence MX for the
 * host itself.  However a config option is to insist that MX
 * records exist - in this case we bomb out! Another config option
 * insists on MX records for specific domains. 
 */
static int
handle_null_mxs(mxdat, priv)
     struct mx_work_area * mxdat;
     struct bindlib_private *priv;  
{
    int result;

    if (mxdat->flags & BIND_MX_ONLY) {
        /* We need MX records.  We have none - hence failed! */
	DEBUG1(DBG_DRIVER_MID, "handle_null_mxs: no faking for %s with BIND_MX_ONLY!.\n", mxdat->matched_target);
        return DB_NEGMATCH;
    } else if (match_full_or_end_domain(priv->mx_domains, mxdat->matched_target) != NULL) {
	DEBUG1(DBG_DRIVER_LO, "handle_null_mxs: mx_domains says no faking for %s!.\n", mxdat->matched_target);
        return DB_NEGMATCH;
    } else {
        /*
         * 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.
         */
	/*
	 * As long as we're allowed to filter, check if the target is a local
	 * host because if it is then we won't need any address hints, and we
	 * can save another DNS lookup.
	 */
	/*
	 * XXX WARNING: things like wild-card MXs will break the islocalhost()
	 * check!!!!
	 */
	if (!(mxdat->flags & BIND_DONT_FILTER) && (mxdat->flags & BIND_LOCAL_MX_OKAY) && islocalhost(mxdat->matched_target)) {
	    DEBUG1(DBG_DRIVER_HI, "handle_null_mxs: islocalhost(%s).\n", mxdat->matched_target);
	    return DB_SUCCEED;
	}
	/*
	 * XXX it would be nice if we could re-add the lowest precedence MX
	 * that had been filtered previously since this would be known to have
	 * a valid hint (assuming the DNS was correct).  Of course if there
	 * were no MXs filtered then we still have to fake up an MX for the
	 * target domain and fill in a hint for it.
	 */
        add_mx_hint(&(mxdat->mx_hints), 0,
                    mxdat->matched_target,
                    TRUE);
        result = get_addr_hints(mxdat);
        if (result == DB_SUCCEED) {
            /* OK we have an implicit MX with address records (we hope!)
             * but we may not have the FQDN as yet.  So lets get the
             * FQDN out of the address hint.  Just remember here that
             * there is *only* one MX record - I put it in just above!
             */
#define mx_hint ((struct mx_transport_hint *)(mxdat->mx_hints->private))
            xfree(mx_hint->exchanger);
            mx_hint->exchanger = COPY_STRING(mx_hint->ipaddrs->hostname);
            if (mxdat->full_target == NULL) {
                mxdat->full_target = COPY_STRING(mx_hint->exchanger);
            }

            /* Check if local host */
            if (islocalhost(mxdat->full_target)) {
                mxdat->local_precedence = 0;
                if ((mxdat->flags & BIND_LOCAL_MX_OKAY) == 0) {
                    *(mxdat->error_p) = matched_local_host(mxdat->what, mxdat->matched_target);
                    return DB_FAIL;
                }
            }
        } else {
	    DEBUG2(DBG_DRIVER_LO, "handle_null_mxs: no address hints for %s [%d].\n", mxdat->matched_target, result);
	    /* XXX it's possible this is a config error and should go to paniclog */
	}

        return result;                 /* A touch optomistic! */
#undef mx_hint
    }
}



/*
 * make_mx_hints        - Build MX hints from the received
 *                        MX RRs
 */
static int
make_mx_hints(target, mxdat)
    char *target;
    struct mx_work_area * mxdat;
{
    struct rr_iterator mx_it;
    RR * mx_rr;
    int result;
    int found_mx = FALSE;

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

	    found_mx = TRUE;
            result = decode_mx_rr(mx_rr, &mx_it, &name, &precedence);
            if (result != DB_SUCCEED) {
                *(mxdat->error_p) = packet_error(mxdat->what, "MX", mxdat->matched_target);
                return DB_AGAIN;
            }
	    if (!EQIC(mx_it.dname, target)) {
		DEBUG3(DBG_DRIVER_MID, "make_mx_hints: found MX for '%s' when looking for '%s'!  MX target host '%s'\n",
		       mx_it.dname, target, name);
#if 0 /* XXX this is likely too deep inside to do this test reliably... */
		if ((BIND_DEFNAMES || BIND_DNS_SEARCH) && !BIND_DOMAIN_REQUIRED) {
		    errno = 0;
		    *(mxdat->error_p) = server_failure(mxdat->what,
						       xprintf("found bogus MX RR for '%s' when looking for '%s'.",
							       mx_it.dname, target));
		    return DB_FAIL;
		}
#endif
	    } else {
		DEBUG2(DBG_DRIVER_MID, "make_mx_hints: found MX for '%s': MX target host '%s'\n",
		       mx_it.dname, name);
	    }

            add_mx_hint(&(mxdat->mx_hints), precedence, name, FALSE);

            /* Extract the full name of the target if possible */
            if (mxdat->full_target == NULL) {
                mxdat->full_target = COPY_STRING(mx_it.dname);
            }

            /* Pick up the local host's precedence */
            if (islocalhost(name) && (mxdat->local_precedence < 0 || precedence < mxdat->local_precedence)) {
		DEBUG3(DBG_DRIVER_HI, "make_mx_hints: adjusting precedence for %s from %d to %d\n",
		       name, mxdat->local_precedence, precedence);
                mxdat->local_precedence = precedence;
	    }
        } else if (mx_rr->rr_type != T_NS && mx_rr->rr_type != T_A) {
	    /* we don't really care what else is in the answer, but... */
	    DEBUG2(DBG_DRIVER_HI, "make_mx_hints: found non-MX/non-A/non-NS RR type %d for %s\n",
		   (int) mx_rr->rr_type, mx_it.dname);
	}
    }

    /*
     * From RFC 2821 §5
     *
     *	If one or more MX RRs are found for a given name, SMTP systems MUST NOT
     *	utilize any A RRs associated with that name unless they are located
     *	using the MX RRs; the "implicit MX" rule above applies only if there
     *	are no MX records present.  If MX records are present, but none of them
     *	are usable, this situation MUST be reported as an error.
     */
    if (found_mx && !mxdat->mx_hints) {
	DEBUG2(DBG_DRIVER_HI, "make_mx_hints: found an MX RR type %d for %s\n", (int) mx_rr->rr_type, mx_it.dname);
	return DB_FAIL;
    }

    return DB_SUCCEED;
}


/*
 * filter_mx_hints      - Apply the various filtering operations to
 *                        the received MX records
 *                        Basically this means making sure that there
 *                        no MX records with precedence higher than that
 *                        of the local host.
 */
static int filter_mx_hints(mxdat)
     struct mx_work_area * mxdat;
{
    struct transport_hints ** mx_a;
    int low_precedence = -1;


#define mx_hint ((struct mx_transport_hint *)((*mx_a)->private))
    for (mx_a = &(mxdat->mx_hints); (*mx_a);) {
        char *name = mx_hint->exchanger;
        int precedence = mx_hint->preference;

        if (mxdat->local_precedence >= 0 && precedence >= mxdat->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.
             */
            DEBUG3(DBG_DRIVER_HI, "Removed MX hint %s preference %d [Local precedence %d]\n", 
                   name, precedence, mxdat->local_precedence);
            free_mx_hint(mx_a);
            continue;
        } else if (low_precedence < 0 || precedence < low_precedence) {
            low_precedence = precedence;
        }
#undef mx_hint
        mx_a = &(*mx_a)->succ;
    }

    /* if there are no hints left then we may still be able to match.... */
    if (! mxdat->mx_hints) {
        if (mxdat->local_precedence >= 0) {
            /*
	     * This means we rejected them all because the local host is the
	     * prefered MX, so IFF that's allowed then we give the go-ahead
	     * anyway, otherwise we trigger an immediate hard failure due to no
	     * local support for this domain.
	     */
            if (mxdat->flags & BIND_LOCAL_MX_OKAY) {
		DEBUG(DBG_DRIVER_HI, "filter_mx_hints: returning DB_SUCCEED because BIND_LOCAL_MX_OKAY.\n");
                return DB_SUCCEED;
            } else {
                *(mxdat->error_p) = matched_local_host(mxdat->what, mxdat->matched_target);
                return DB_FAIL;
            }
        }

	/* otherwise we cannot match this domain */
        return DB_NOMATCH;
    }

    /*
     * From RFC 2821 §5
     *
     *     ... If there are multiple destinations with the same preference
     *  and there is no clear reason to favor one (e.g., by recognition of an
     *  easily-reached address), then the sender-SMTP MUST randomize them to
     *  spread the load across multiple mail exchangers for a specific
     *  organization.
     *
     * XXX currently we do not bother.  ;-)
     *
     */

    return DB_SUCCEED;
}


/*
 * get_addr_hints       - Find all the address hints for the MX records
 *
 * This both picks out the A records in the additional section of the
 * MX records, and also goes off and finds A records for any hosts
 * for which they have not been supplied.
 * As a hack, you may not have got the full_target name for a host
 * which was implicity MXed, so we cheat and pull it out here, if
 * needed!
 *
 * Note also from RFC 2821 §5
 *
 *  The destination host (perhaps taken from the preferred MX record) may
 *  be multihomed, in which case the domain name resolver will return a
 *  list of alternative IP addresses.  It is the responsibility of the
 *  domain name resolver interface to have ordered this list by
 *  decreasing preference if necessary, and SMTP MUST try them in the
 *  order presented.
 *
 * I.e. we must not re-order address hints for any given MX host.
 */
static int
get_addr_hints(mxdat)
    struct mx_work_area *mxdat;
{
    struct rr_iterator a_it;
    RR *a_rr;
    struct transport_hints **mx_a;
    int result;
    const char *error_msg = NULL;
    int dns_timeout = 0;

    /* look for relevant A records in the additional section of the MX answer */
    rewindrr(&a_it, mxdat->mx_rrs, mxdat->mx_size);
    while ((a_rr = getnextrr(&a_it, TRUE)) != NULL) {
        struct transport_hints *hint;

#define mx_hint	((struct mx_transport_hint *) (hint->private))

        if (a_rr->rr_type == T_A) {
            for ((hint = mxdat->mx_hints); (hint != NULL); (hint = hint->succ)) {
                if (EQIC(mx_hint->exchanger, a_it.dname)) {
		    char *reject_reason = NULL;
		    struct in_addr inaddr;
		    char *ina;

		    memcpy((char *) &(inaddr), a_rr->rr_data, sizeof(inaddr));
		    ina = inet_ntoa(inaddr);
		    if ((match_hostname(mxdat->matched_target, smtp_bad_mx_except, (char **) NULL) != MATCH_MATCHED) &&
			match_ip(ina, smtp_bad_mx_targets, ':', ';', FALSE, &reject_reason)) {

			error_msg = xprintf("The target hostname '%s' (of MX domain '%s') has an unauthorised address of [%s]%s%s",
					    a_it.dname,
					    mxdat->matched_target,
					    ina,
					    reject_reason ? ": " : "",
					    reject_reason ? reject_reason : "");
			DEBUG1(DBG_DRIVER_MID, "get_addr_hints: %s.\n", error_msg);
			*(mxdat->error_p) = server_failure(mxdat->what, error_msg);
			return DB_FAIL;
		    } else {
			add_a_hint(mx_hint, a_it.dname, a_rr->rr_data);
		    }
                }
#if 0 /* #ifdef SERIOUS_DEBUGGING */
		else {
		    /*
		     * this is very noisy -- there will amost always be other
		     * non-matching A RRs in the aditional section of an MX
		     * lookup reply.
		     */
		    DEBUG2(DBG_DRIVER_HI, "get_addr_hints: found additional section A RR for %s when looking for %s (likely for an NS, or for another MX target)\n", a_it.dname, mx_hint->exchanger);
		}
#endif
            }
        }
#if 0 /* #ifndef SERIOUS_DEBUGGING */
	else if (a_rr->rr_type != T_NS && a_rr->rr_type != T_MX) {
	    /*
	     * XXX I'm not sure why but on some systems the MX RRs appear to be
	     * in here too.
	     */
	    /* we don't really care what other crap is in the additional section */
	    DEBUG2(DBG_DRIVER_HI, "get_addr_hints: found additional section non-A/non-NS RR type %d for %s\n", (int) a_rr->rr_type, a_it.dname);
	}
#endif

#undef mx_hint
    }

    mx_a = &(mxdat->mx_hints);
    while (*mx_a) {

#define mx_hint	((struct mx_transport_hint *)((*mx_a)->private))

        if (! mx_hint->ipaddrs) {
	    DEBUG1(DBG_DRIVER_MID, "get_addr_hints: no A RR(s) found for '%s' in additional section...\n", mx_hint->exchanger);

            /* Go out and get an A record */
            result = find_mx_target_a_records(mx_hint, mxdat->matched_target, mx_hint->exchanger, &error_msg);

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

	    case DB_NOMATCH:
		*(mxdat->error_p) = server_failure(mxdat->what, xprintf("MX target host '%s' does not exist: %s", mx_hint->exchanger, error_msg));
                free_mx_hint(mx_a);
                break;

	    case FILE_NOMATCH:
                result = FILE_AGAIN;
                /* FALLTHRU */

	    case DB_AGAIN:
                *(mxdat->error_p) = server_failure(mxdat->what, xprintf("Temporary error getting address of MX target host '%s': %s", mx_hint->exchanger, error_msg));
                free_mx_hint(mx_a);
                dns_timeout++;
                break;

	    case DB_FAIL:
	    case FILE_AGAIN:
                *(mxdat->error_p) = server_failure(mxdat->what, xprintf("Server error getting address of MX target host '%s': %s", mx_hint->exchanger, error_msg));
                free_mx_hint(mx_a);
                break;
            }
        } else {
            mx_a = &(*mx_a)->succ;
        }
#undef mx_hint
    }

    if (mxdat->mx_hints) {
        return DB_SUCCEED;
    }

    DEBUG1(DBG_DRIVER_HI, "get_addr_hints: %s.\n", dns_timeout ? "dns timeout" : "no hints");
    if (dns_timeout) {
	return DB_AGAIN;
    }

    return DB_FAIL;
}


/*
 * mx_work_area_constructor     - A constructor for mx_work_area
 *
 * Vaguely C++, but done so that I was getting the intialisation
 * and allocation correct.  The destructor also means I can make
 * sure the memory is deallocated correctly!
 */
static struct mx_work_area *
mx_work_area_constructor(private, error_p, what, flags, found_server, no_server)
    struct bindlib_private *private;	/* Link to the router private info */
    struct error **error_p;		/* Pointer to return error messages */
    char *what;				/* Name of this router */
    unsigned long flags;		/* Router specific flags */
    int *found_server;			/* Pointer to server status */
    int *no_server;			/* Pointer to server down status */
{
    struct mx_work_area * mxdat;

    /* Allocate storage */
    mxdat = (struct mx_work_area *) xmalloc(sizeof(struct mx_work_area));

    /* Initialise data structure */
    mxdat->what = what;                 /* Set name of router */
    mxdat->matched_target = NULL;       /* Not yet set */
    mxdat->full_target = NULL;          /* Not yet set */
    mxdat->mx_rrs = (HEADER *) xmalloc((size_t) MAXPACKET);
    /* Make buffer for MX replies */
    mxdat->mx_size = 0;                 /* More for completeness than correctness */
    mxdat->mx_hints = NULL;             /* No MX hints yet */
    mxdat->flags = flags;               /* Copy flag values */
    mxdat->priv = private;              /* Set pointer to private attributes */
    mxdat->local_precedence = -1;       /* Mark local_precedence as not seen */
    mxdat->error_p = error_p;
    mxdat->found_server = found_server;	/* never used? */
    mxdat->no_server = no_server;	/* never used? */

    return mxdat;
}


/*
 * mx_work_area_destructor      - A destructor for mx_work_area
 *
 * Vaguely C++, but done so that I was getting the intialisation
 * and allocation correct.  The destructor also means I can make
 * sure the memory is deallocated correctly!
 */
static void
mx_work_area_destructor(mxdat, do_not_deallocate_mxhints)
    struct mx_work_area * mxdat;
    int do_not_deallocate_mxhints;
{
    /* Deallocate storage */
    if (mxdat->matched_target != NULL) {
	xfree(mxdat->matched_target);
    }
    if (mxdat->full_target != NULL) {
        xfree(mxdat->full_target);
    }
    if (mxdat->mx_rrs != NULL) {
	xfree((char *) mxdat->mx_rrs);
    }

    if (!do_not_deallocate_mxhints) {
	while (mxdat->mx_hints) {
	    free_mx_hint(&(mxdat->mx_hints));
	}
    }
    xfree((char *) mxdat);
}


/* check if the address given by target matches one of the domains given for a
 * list of special gateways.  Gateways is a list of gateways and corresponding
 * domains for explicit routing using the bind processing logic for the gateway
 * address.  The string consists of a sequence of lists.  Each list consists of
 * strings separated by colons, and the lists are separated by a string
 * consisting of a single `+' character.  Each list consists of the name of a
 * gateway, followed by a list of domains that should be routed to that
 * gateway.  The gatewayed domains will match either against a complete
 * address, or against the terminating domain of an address.
 */
static char * check_if_gatewayed (target, gateways)
     char * target;
     char * gateways;
{
    register char * cur;
    static char gatebuf[MAXHOSTNAMELEN];
    int len = strlen(target);           /* length of target */
    int gated = FALSE;

    if (gateways == NULL)
        return NULL;

    cur = strcolon(gateways);
    while (cur && !gated) {
        strncpy(gatebuf, cur, sizeof(gatebuf));
	gatebuf[sizeof(gatebuf) - 1] = '\0';
        cur = strcolon((char *)NULL);
        while (cur) {
            register int domlen = strlen(cur);
      
            if (strcmp(cur, "+") == 0) {
                cur = strcolon((char *)NULL);
                break;
            } else {
                if ((len > domlen && target[len - domlen - 1] == '.' &&
                     EQIC(target + len - domlen, cur)) ||
                    (len == domlen && EQIC(target, cur))) {
                    gated = TRUE; 
                    break;
                } else { 
                    cur = strcolon((char *)NULL);
                }
            }
        }
    }
    return gated ? gatebuf : NULL;
}


/*
 * XXX -- this hack should be ripped out.
 *
 * rewrite-header - re-write an address in a header to be the corrected
 * address, possibly widened 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;
    size_t oldlen = strlen(oldtarget);
    size_t 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, (size_t) newlen);
		} else {
		    int baselen = p - text;
		    size_t oldtextlen = strlen(text);
		    char *newtext = xmalloc(oldtextlen+1+(newlen-oldlen));

		    memcpy(newtext, text, (size_t) baselen);
		    memcpy(newtext+baselen, newtarget, (size_t) newlen);
		    memcpy(newtext+baselen+newlen, p + oldlen,
			   (size_t) (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 */
    const char **error;			/* store error message here */
{
    int anslen;

    *pack_size = 0;                     /* Ensure packet size is zero unless set */
    *error = NULL;
    anslen = res_search(qname, C_IN, qtype, (u_char *) answer, MAXPACKET);
    if (anslen < 0) {
	/*
	 * NOTE: if you get a compiler warning about hstrerror() not being
	 * declared, or one warning about "illegal combination of pointer and
	 * integer", then you are not compiling with the headers from a modern
	 * enough resolver library!  (regardless of what you think you're doing ;-)
	 */
	*error = hstrerror(h_errno);
        switch (h_errno) {
        case NO_DATA:
	    DEBUG2(DBG_DRIVER_MID, "bindlib: DNS lookup for '%s' returned NO_DATA: %s.\n", qname, *error);
	    /*
	     * This means the domain name exists in the DNS but there were no
	     * records of the desired type.
	     *
	     * The rest of bindlib.c assumes that we have a valid packet
	     * when we return DB_SUCCEED, so dummy one up that shows no
	     * data was retrieved
	     */
	    answer->qdcount = 0;
	    answer->ancount = 0;
	    answer->nscount = 0;
	    answer->arcount = 0;
	    *pack_size = sizeof(HEADER);
	    return DB_SUCCEED;

        case HOST_NOT_FOUND:
	    DEBUG2(DBG_DRIVER_HI, "bindlib: DNS lookup for '%s' returned HOST_NOT_FOUND: %s.\n", qname, *error);
            return DB_NOMATCH;

        case TRY_AGAIN:
	    DEBUG2(DBG_DRIVER_MID, "bindlib: DNS lookup for '%s' returned TRY_AGAIN: %s.\n", qname, *error);
            return DB_AGAIN;

        case NO_RECOVERY:
	    DEBUG2(DBG_DRIVER_MID, "bindlib: DNS lookup for '%s' returned NO_RECOVERY: %s.\n", qname, *error);
            return FILE_NOMATCH;

        default:
	    DEBUG3(DBG_DRIVER_MID, "bindlib: DNS lookup for '%s' returned %d: %s.\n", qname, anslen, *error);
            return FILE_AGAIN;
        }
    }
    /*
     * This is broken if anslen is bigger than we allowed for then we should
     * re-allocate to fit anslen and try the query again....
     */
    if (anslen > MAXPACKET) {
	    DEBUG3(DBG_DRIVER_MID, "bindlib: DNS lookup for '%s' returned truncated response, need %d bytes, have only %d.\n", qname, anslen, MAXPACKET);
	    *error = "DNS response was truncated due to lack of buffer space.";
            return FILE_AGAIN;
    }
    *pack_size = anslen;

    /* XXX note that usually (sizeof(in_port_t) < sizeof(int)) */
    answer->qdcount = ntohs((in_port_t) answer->qdcount);
    answer->ancount = ntohs((in_port_t) answer->ancount);
    answer->nscount = ntohs((in_port_t) answer->nscount);
    answer->arcount = ntohs((in_port_t) answer->arcount);

    switch (answer->rcode) {
    case NOERROR:
        return DB_SUCCEED;
    default:
        *error = "DNS resolver: 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((unsigned char *)it->hp, (unsigned char *)it->eom, 
                         (unsigned char *)dp, (char *)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((unsigned char *)it->hp, (unsigned char *)it->eom, 
                             (unsigned char *)it->dp, (char *)it->dname, 
                             MAXDNAME);
        if (dnamelen < 0) {
            it->ancount = it->nscount = it->arcount = 0;
	    return;
        }
        it->dp += dnamelen;
        it->dp += 4;                    /* skip over class and type */
        --qdcount;
    }
}


/*
 * find_mx_target_a_records - look carefully only 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, or
 *                      invalid results were returned.
 *      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_mx_target_a_records(mx_hint, mx_domain, target, error)
    struct mx_transport_hint * mx_hint;
    char *mx_domain;
    char *target;
    const char **error;
{
    HEADER *a_rrs;
    int a_size;
    struct rr_iterator a_it;
    RR *a_rr;
    int result;
    char the_stupid_cname[MAXDNAME];
    char the_stupid_cname_target[MAXDNAME];

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

    if (result != DB_SUCCEED) {
        xfree((char *) a_rrs);

        return result;
    }

    result = DB_NOMATCH;
    *error = xprintf("MX domain '%s' has target host '%s' with no valid address (A) records", mx_domain, target);
    memset(the_stupid_cname, '\0', sizeof(the_stupid_cname));
    memset(the_stupid_cname_target, '\0', sizeof(the_stupid_cname_target));
    rewindrr(&a_it, a_rrs, a_size);
    while ((a_rr = getnextrr(&a_it, FALSE)) != NULL) {
        if (a_rr->rr_type == T_A) {
	    if (EQIC(target, a_it.dname) ||
		(allow_one_mx_target_cname_hack &&
		 *the_stupid_cname && EQIC(target, the_stupid_cname) &&
		 EQIC(a_it.dname, the_stupid_cname_target))) {
		char *reject_reason = NULL;
		struct in_addr inaddr;
		char *ina;
		
		if (!EQIC(target, a_it.dname)) {
		    /* i.e. it's the A RR for the one stupid CNAME we allow, so log it. */
		    write_log(WRITE_LOG_SYS, "DNS MX error: MX domain '%s' has a target host '%s' which is an invalid CNAME (for '%s')",
			      mx_domain, target, a_it.dname);
		}
		memcpy((char *) &(inaddr), a_rr->rr_data, sizeof(inaddr));
		ina = inet_ntoa(inaddr);
		if ((match_hostname(mx_domain, smtp_bad_mx_except, (char **) NULL) != MATCH_MATCHED) &&
		    match_ip(ina, smtp_bad_mx_targets, ':', ';', FALSE, &reject_reason)) {

		    *error = xprintf("The target hostname '%s' (for MX domain '%s') has an unauthorised address of [%s]%s%s",
				     a_it.dname, mx_domain, ina,
				     reject_reason ? ": " : "",
				     reject_reason ? reject_reason : "");
		    xfree((char *) a_rrs);

		    /*
		     * XXX what we really want to do is return DB_SUCCEED with
		     * an empty set so that the "bind" driver will match when
		     * it has the "always" attribute set and thus it will still
		     * fail and also prevent going on to try any further
		     * routers.
		     */
		    return DB_FAIL;		/* immediate fatal error */
		} else {
		    /*
		     * Clear the initial or any previous error message and
		     * prepare to succeed as we've found at least one of what
		     * we're looking for!
		     */
		    *error = NULL;
		    result = DB_SUCCEED;
		    add_a_hint(mx_hint, a_it.dname, a_rr->rr_data);
		}
	    } else {
		DEBUG3(DBG_DRIVER_MID, "find_mx_target_a_records: found A RR for '%s' when looking for '%s'%s\n",
		       a_it.dname, target,
		       allow_one_mx_target_cname_hack ? "" : " (likely via an invalid CNAME)");
		if (result != DB_SUCCEED) {
		    result = DB_FAIL;
		}
	    }
	} else if (a_rr->rr_type == T_CNAME) {
	    if (EQIC(target, a_it.dname)) {
		int dlen;

		dlen = dn_expand((unsigned char *) a_it.hp, (unsigned char *) a_it.eom, 
				 (unsigned char *) a_rr->rr_data, the_stupid_cname_target, MAXDNAME);
		if (dlen < 0) {
			*error = xprintf("MX domain '%s' has a target host '%s' which is invalid.", mx_domain, target);
			xfree((char *) a_rrs);

			return DB_FAIL;			/* immediate fatal error! */
		} else {
		    *error = xprintf("MX domain '%s' has a target host '%s' which is an invalid CNAME (for '%s'), see RFC 2181 Section 10.3.",
				     mx_domain, target, the_stupid_cname_target);
		    if (allow_one_mx_target_cname_hack) {
			strncpy(the_stupid_cname, a_it.dname, (size_t) MAXDNAME);
			the_stupid_cname[MAXDNAME - 1] = '\0';
			/* this will be logged when we see the 'A' record above */
			DEBUG1(DBG_DRIVER_MID, "find_mx_target_a_records: %s\n", *error);
		    } else {
			write_log(WRITE_LOG_SYS, "DNS MX error: %s", *error);
			xfree((char *) a_rrs);

			return DB_FAIL;			/* immediate fatal error! */
		    }
		}
	    } else {
		DEBUG2(DBG_DRIVER_HI, "find_mx_target_a_records: found CNAME RR for '%s' when looking for '%s'\n", a_it.dname, target);
	    }
	} else {
	    if (EQIC(target, a_it.dname)) {
		/* XXX this should be impossible -- we only asked for A RRs... */
		DEBUG2(DBG_DRIVER_MID, "find_mx_target_a_records: found non-A RR type %d for '%s'\n", (int) (a_rr->rr_type), target);
	    } else {
		DEBUG3(DBG_DRIVER_HI, "find_mx_target_a_records: found non-A RR type %d for '%s' when looking for '%s'\n", (int) (a_rr->rr_type), a_it.dname, target);
	    }
	}
    }
    xfree((char *) a_rrs);
    if (!*error) {
	/* set default error -- ignored if addresses were found */
	*error = xprintf("No address for MX (%s) target host '%s'", mx_domain, target);
    }

    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((unsigned char *)it->hp, (unsigned char *)it->eom, 
                     (unsigned char *)s, (char *)nambuf, MAXDNAME);
    if (dlen < 0) {
        return DB_FAIL;
    }
    *name = nambuf;
    return DB_SUCCEED;
}


static struct error *
not_a_canonical_host(error)
    char *error;                        /* additional error text */
{
    /*
     * ERR_186 - canonical host not found
     *
     * DESCRIPTION
     *      No A RR was returned for the domain queried.
     *
     * ACTIONS
     *      Actions depend upon the specific error.
     *
     * RESOLUTION
     *      Resolution depends upon the specific error.
     */

    return note_error(ERR_186, error);
}


static struct error *
server_failure(what, error)
    char *what;				/* name of function or service involved */
    const char *error;			/* additional error text */
{
    char *error_text;

    /*
     * ERR_164 - failure talking to DNS server
     *
     * DESCRIPTION
     *      An error occured when sending or receiving packets from
     *      the BIND server, or the server registered an error in the
     *      response packet, or no valid answer was returned.
     *
     * ACTIONS
     *      Actions depend upon the specific error.  Usually, a retry
     *      is attempted later for any temporary error.
     *
     * RESOLUTION
     *      Resolution depends upon the specific error.
     */
    error_text = xprintf("%s: DNS error: %s%s%s",
			 what,
			 error,
			 (errno != 0) ? ": " : "",
			 (errno != 0) ? strerror(errno) : "");

    return note_error(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, or some as yet
     *      unhandled boundary condition in the way we parse the response
     *      packet.  If the latter then it's a bug in smail.
     */
    error_text =
        xprintf("%s: BIND server format error in %s packet for %s",
                what, type, host);

    return note_error(ERR_165, 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, but 
     */
    error_text = xprintf("%s: MX record for %s points to a local host but it is not configured to be handled locally",
                         what, host);

    return note_error(ERR_NPOSTMAST | ERR_NSOWNER | ERR_169, error_text);
}


static struct error *
cname_loop_error(what, host, cname_limit)
    char *what;
    char *host;                         /* target hostname */
    int cname_limit;			/* limit in current driver */
{
    char *error_text;

    /*
     * ERR_177 - CNAME loop detected
     *
     * DESCRIPTION
     *      One CNAME points to another etc up to the maximum limit
     *      that this system is allowed to take (configurable).
     *      
     * ACTIONS
     *      The domain database should probably be looked at.
     *
     * RESOLUTION
     *      The postmaster should probably look into the problem.
     */
    error_text = xprintf("%s: too many CNAMEs encountered in DNS for %s (maximum permitted = %d).",
                         what, host, cname_limit);

    return note_error(ERR_NSOWNER | ERR_177, error_text);
}

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

    /*
     * ERR_180 - DNS/BIND res_init() failed
     *
     * DESCRIPTION
     *      An attempt to use res_init() to setup the BIND resolver
     *      failed.
     *      
     * ACTIONS
     *      There is something badly wrong with the resolver
     *
     * RESOLUTION
     *      The postmaster should probably look into the problem.
     */
    error_text = xprintf("%s: DNS/BIND res_init() failed - unable to use resolver",
                         what);

    return note_error(ERR_NPOSTMAST | ERR_NSOWNER | ERR_180, 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;

    DEBUG3(DBG_DRIVER_HI, "add_mx_hint: %s MX %d hint for %s.\n",
	   implicit ? "implicit" : "non-implicit", precedence, name)
    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((char *) &(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 mx_hint ((struct mx_transport_hint *)((*hint_p)->private))
    for (addr = mx_hint->ipaddrs; addr; addr = next_addr) {
        next_addr = addr->succ;
        xfree(addr->hostname);
        xfree((char *)addr);
    }
    xfree(mx_hint->exchanger);
    xfree((char *)mx_hint);
#undef mx_hint
    xfree((char *)*hint_p);
    *hint_p = next;
}


/*
 * match_full_or_end_domain - try to match one of a list of domains against a target
 *
 * given a list of domains separated by colons, determine if the given
 * target ends in, or is equal to one of those domains.  If so, return a 
 * pointer to the first character of the matched domain, else return NULL.
 * The list is scanned from left to right, with the first match returned.
 ****
 * Added functionality.  A domain spec *may* start with a ! character, in
 * which case NULL is returned if it *does* match.  This allows a specific
 * subdomain to be excluded from a more general match
 */
static char *
match_full_or_end_domain(domains, target)
    char *domains;                      /* colon separated list of domains */
    char *target;                       /* target to test against */
{
    register char *cur;                 /* current domain being checked */
    register int negmatch;	        /* are we doing negative matching */
    char * match;                       /* the actual match */

    if (!domains)
        return NULL;

    for (cur = strcolon(domains); cur; cur = strcolon((char *)NULL)) {
	negmatch = (*cur == '!') && cur++;
        match = is_suffix(cur, target, FALSE);
        if (match != NULL) {
            return (negmatch ? NULL : match);
        }
    }

    /* did not end in one of the domains */
    return NULL;
}

#endif /* HAVE_BIND */

/* 
 * Local Variables:
 * c-file-style: "smail"
 * End:
 */


syntax highlighted by Code2HTML, v. 0.9.1