ftp.nice.ch/pub/next/unix/mail/smail3.1.20.s.tar.gz#/smail3.1.20/src/routers/bind.c

This is bind.c in view mode; [Download] [Up]

/* @(#)src/routers/bind.c	1.3 22 Nov 1990 11:59:55 */

/*
 *    Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
 * 
 * See the file COPYING, distributed with smail, for restriction
 * and warranty information.
 */

/*
 * bind.c
 *	routing driver which connects to a Berkeley Internet Name
 *	Domain (BIND) server for routing mail.  At the present time
 *	this router has only been tested with BIND4.8, which was
 *	obtained from the archives on uunet.
 *
 * Specifications for the pathalias routing driver:
 *
 *	associated transports:
 *	    Generally used with an smtp transport.
 *
 *	private attribute data:
 *	    None.
 *
 *	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.
 *	    defname:  append a default domain to an unqualified hostname,
 *		using the RES_DEFNAME flag to the resolver library.
 *		This is set by default.
 *
 *	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.  See if there is an A record for the target.  If so,
 *		get the WKS record for the target.  If there is a WKS
 *		record which includes the SMTP service, then match
 *		the the target.  If the WKS record does not include
 *		the SMTP service, then reject the message as
 *		undeliverable.  If there is no WKS record, then match
 *		the target.
 *
 *	    5.  Do not match the target.
 *
 *	    6.	Issue a request for WKS records for each name
 *		specified by a returned MX record.  Discard all MX
 *		records for which no WKS record is found.
 *
 *	    7.  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.
 *
 *	    8.  If the only MX record remaining references the local
 *		host, do not match the target.
 *
 *	    9.  Choose one of the MX records with the lowest
 *		preference fields.  Match the address with the
 *		next_host value set to the referenced host.
 *
 *	    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.
 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <netdb.h>
#include <resolv.h>
#include "defs.h"
#include "../smail.h"
#include "../smailconf.h"
#include "../parse.h"
#include "../addr.h"
#include "../route.h"
#include "../transport.h"
#include "../lookup.h"
#include "../dys.h"
#include "rtlib.h"
#include "bind.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 */
};

/* functions local to this file */
static int bind_lookup();
static char *strip_dots();
static int get_records();
static RR *getnextrr();
static void rewindrr();
static int find_a_record();
#ifdef USE_WKS_RECORDS
static int find_smtp_service();
static int find_wks_service();
static int decode_mx_rr();
#endif
static struct error *lost_server();
static struct error *server_failure();
static struct error *packet_error();
#ifdef USE_WKS_RECORDS
static struct error *no_smtp_service();
static struct error *unknown_service();
#endif
static struct error *no_valid_mx_records();
static struct error *matched_local_host();


/*
 * rtd_bind - route using Berkeley Internet Name Domain server
 */
void
rtd_bind(rp, in, out, defer, fail)
    struct router *rp;			/* router table entry */
    struct addr *in;			/* input addr structures */
    struct addr **out;			/* non-failed addr structures */
    struct addr **defer;		/* addrs to defer to a later time */
    struct addr **fail;			/* unresolvable addrs */
{
    rtd_standard(rp, in, out, defer, fail, bind_lookup);
}

/*
 * rtv_bind - verify that a match exists for a list of addr structures
 */
/*ARGSUSED*/
void
rtv_bind(rp, in, retry, okay, defer, fail)
    struct router *rp;			/* router entry */
    struct addr *in;			/* input local-form addrs */
    struct addr **retry;		/* output list of unmatched addrs */
    struct addr **okay;			/* output list of verified addrs */
    struct addr **defer;		/* temporariliy unverifiable addrs */
    struct addr **fail;			/* unverified addrs */
{
    rtv_standard(rp, in, retry, okay, defer, fail, bind_lookup);
}

/*
 * rtb_bind - read the configuration file attributes
 */
char *
rtb_bind(rp, attrs)
    struct router *rp;			/* router entry being defined */
    struct attribute *attrs;		/* list of per-driver attributes */
{
    char *error;
    static struct attr_table bind_attributes[] = {
	{ "local_mx_okay", t_boolean, NULL, NULL, BIND_LOCAL_MX_OKAY },
	{ "defer_no_connect", t_boolean, NULL, NULL, BIND_DEFER_NO_CONN },
	{ "defnames", t_boolean, NULL, NULL, BIND_DEFNAMES },
    };
    static struct attr_table *end_bind_attributes = ENDTABLE(bind_attributes);
    static struct bind_private bind_template = {
	0,				/* dummy value */
    };
    struct bind_private *priv;		/* new bind_private structure */

    /* copy the template private data */
    priv = (struct bind_private *)xmalloc(sizeof(*priv));
    (void) memcpy((char *)priv, (char *)&bind_template, sizeof(*priv));

    rp->private = (char *)priv;
    rp->flags |= (BIND_DEFNAMES|BIND_DEFER_NO_CONN);
    /* fill in the attributes of the private data */
    error = fill_attributes((char *)priv,
			    attrs,
			    &rp->flags,
			    bind_attributes,
			    end_bind_attributes);

    if (error) {
	return error;
    } else {
	return NULL;
    }
}


/*
 * bind_lookup - lookup a host in 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.
 */
/*ARGSUSED*/
static int
bind_lookup(rp, addr, fl, rt_info, error_p)
    struct router *rp;			/* router table entry */
    struct addr *addr;			/* addr structure */
    int fl;				/* flags from rt[dv]_standard */
    struct rt_info *rt_info;		/* return route info here */
    struct error **error_p;		/* return lookup error here */
{
    char *target;			/* target stripped of extra dots */
    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 server = FALSE;		/* TRUE if server process found */
    int success;			/* value to return */
    char *error;			/* error text from called function */
    int low_precedence;			/* lowest precedence MX record */
    int local_precedence;		/* precedence of MX to local host */

    if (no_server) {
	return DB_NOMATCH;
    }

#ifdef RES_DEFNAMES
    if (rp->flags & BIND_DEFNAMES) {
	_res.options |= RES_DEFNAMES;
    } else {
	_res.options &= ~RES_DEFNAMES;
    }
#endif

    /* Step 1:  Strip extra dots */
    /* store return values for common case */
    rt_info->next_host = target = strip_dots(addr->target);
    rt_info->route = NULL;
    rt_info->matchlen = strlen(addr->target);

get_target_mx_records:
    /* 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_SUCCEED) {
	switch (success) {

	case FILE_NOMATCH:
	    if ((rp->flags & BIND_DEFER_NO_CONN) && ! server) {
		no_server = TRUE;
		success = DB_NOMATCH;
		break;
	    }
	    if (server) {
		*error_p = lost_server(rp);
	    } else {
		*error_p = server_failure(rp, error);
	    }
	    success = FILE_AGAIN;
	    break;

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

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

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

    /* See if there are any MX records */
    rewindrr(&mx_it, mx_rrs, mx_size);
    while ((mx_rr = getnextrr(&mx_it)) && 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)) && mx_rr->rr_type != T_CNAME) ;
	if (mx_rr) {
	    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(rp, "CNAME", target);

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

	    /* Go back and look for MX records again */
	    goto get_target_mx_records;
	}

	/* Step 4:  Look for an A record */
	/* First look through the previously returned packet */
	rewindrr(&a_it, mx_rrs, mx_size);
	while ((a_rr = getnextrr(&a_it)) && a_rr->rr_type != T_A) ;

	/* we don't need the MX record response anymore */
	xfree((char *)mx_rrs);
	if (a_rr == NULL) {

	    /* Go out and get an A record */
	    success = find_a_record(target, &error);
	    switch (success) {

	    case DB_NOMATCH:
		return DB_NOMATCH;

	    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(rp, error);
		return success;
	    }
	    /* found an A record */
	}

#ifdef USE_WKS_RECORDS
	/* Step 4 continued:  look for WKS records */
	success = find_smtp_service(target, &error);
	switch(success) {

	case DB_NOMATCH:
	    *error_p = no_smtp_service(rp, target);
	    break;

	case DB_FAIL:
	    success = DB_SUCCEED;
	    break;

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

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

	case FILE_FAIL:
	    *error_p = unknown_service(rp, error);
	    break;
	}
	return success;
#else	/* not USE_WKS_RECORDS */
	return DB_SUCCEED;
#endif	/* not USE_WKS_RECORDS */
    }

    /*
     * Steps 6-9:  We combine these four steps by going through the MX
     * records and keeping track of the lowest precedence number.
     */
    rewindrr(&mx_it, mx_rrs, mx_size);
    low_precedence = -1;
    local_precedence = -1;

    /* grab the domain returned in the packet as the real target name */
    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);
    }
    while (mx_rr = getnextrr(&mx_it)) {
	static char *return_name = NULL;
	char *name;
	int precedence;

	if (mx_rr->rr_type != T_MX) {
	    continue;
	}

	success = decode_mx_rr(mx_rr, &mx_it, &name, &precedence);
	if (success != DB_SUCCEED) {
	    *error_p = packet_error(rp, "MX", target);
	    xfree((char *)mx_rrs);
	    return DB_AGAIN;
	}

#ifdef USE_WKS_RECORDS
	success = find_smtp_service(name, &error);
	switch (success) {

	case DB_FAIL:
	case DB_NOMATCH:
	    /* no SMTP service, ignore entry */
	    continue;

	case FILE_FAIL:
	    /* Could not determine smtp or tcp protocol */
	    *error_p = unknown_service(rp, error);
	    xfree((char *)mx_rrs);
	    return FILE_FAIL;

	case FILE_AGAIN:
	case FILE_NOMATCH:
	    *error_p = server_failure(rp, error);
	    xfree((char *)mx_rrs);
	    return FILE_AGAIN;
	}
#endif	/* USE_WKS_RECORDS */

	if (islocalhost(name)) {
	    if (local_precedence < 0 || precedence < local_precedence) {
		local_precedence = precedence;
	    }
	}

	if (low_precedence < 0 || precedence < low_precedence) {
	    low_precedence = precedence;

	    if (return_name) {
		xfree(return_name);
	    }
	    rt_info->next_host = return_name = COPY_STRING(name);
	}
    }
    xfree((char *)mx_rrs);

    if (low_precedence < 0) {
	/* There were no valid MX records, this is an error */
	*error_p = no_valid_mx_records(rp, target);
	return DB_FAIL;
    }
    if (local_precedence == low_precedence) {
	if ((rp->flags & BIND_LOCAL_MX_OKAY) == 0) {
	    *error_p = matched_local_host(rp, target);
	    return DB_FAIL;
	}
	return DB_NOMATCH;
    }
    /*
     * if the MX record points to a different host than the
     * target, make sure the target hostname is passed to that
     * host.
     */
    if (! EQIC(full_target, rt_info->next_host)) {
	rt_info->route = full_target;
    }
    return DB_SUCCEED;
}

/*
 * 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;
}


/*
 * 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 */
{
    char msgbuf[MAXPACKET];
    int msglen;
    int anslen;

    msglen = res_mkquery(QUERY, qname, C_ANY, qtype, (char *)NULL, 0,
			 (struct rrec *)NULL, msgbuf, MAXPACKET);

    anslen = res_send(msgbuf, msglen,  (char *)answer, MAXPACKET);
    if (anslen < 0) {
	return FILE_NOMATCH;
    }
    *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;

    case FORMERR:
	*error = "Nameserver: Format error in packet";
	return DB_FAIL;

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

    case NXDOMAIN:
	return DB_NOMATCH;

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

    case REFUSED:
	*error = "Nameserver: Query refused";
	return FILE_AGAIN;

    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)
    register struct rr_iterator *it;	/* iteration variables */
{
    int dnamelen;
    register char *dp;

    dp = it->dp;

    /* return NULL if no rr's remain */
    if (it->ancount != 0) {
	--it->ancount;
	it->rr.rr_sect = SECT_AN;
    } else if (it->nscount != 0) {
	--it->nscount;
	it->rr.rr_sect = SECT_NS;
    } else if (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 = dp;		/* there is the data */
    it->dp = 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_record - 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_record(target, error)
    char *target;
    char **error;
{
    int success;
    HEADER *a_rrs;
    int a_size;
    struct rr_iterator a_it;
    RR *a_rr;

    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)) &&a_rr->rr_type != T_A) ;

    xfree((char *)a_rrs);
    if (a_rr) {
	return DB_SUCCEED;
    }
    return DB_NOMATCH;
}

#ifdef USE_WKS_RECORDS
/*
 * find_smtp_service - look for the SMTP service in WKS records for target
 *
 * get the WKS records for the specific target and search those WKS
 * records for the SMTP service.  Return the following:
 *
 *	DB_SUCCEED	The SMTP service was found.
 *	DB_FAIL		There were no WKS records.
 *	DB_NOMATCH	The SMTP service was not found in the WKS
 *			records.
 *	DB_AGAIN	There was a server error for this query, try
 *			again later.
 *	FILE_AGAIN	Server error, try again later.
 *	FILE_NOMATCH	We could not connect to the server.
 *	FILE_FAIL	Could not determine smtp or tcp protocol
 *			numbers.
 *
 * For response codes other than DB_SUCCEED, DB_FAIL and DB_NOMATCH,
 * store an error message.
 */
static int
find_smtp_service(target, error)
    char *target;
    char **error;
{
    int success;
    HEADER *wks_rrs;
    int wks_size;
    struct rr_iterator wks_it;
    RR *wks_rr;
    int found_wks = FALSE;
    int found_smtp = FALSE;

    wks_rrs = (HEADER *)xmalloc(MAXPACKET);
    success = get_records(target, T_WKS, wks_rrs, &wks_size, error);

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

    rewindrr(&wks_it, wks_rrs, wks_size);
    while (wks_rr = getnextrr(&wks_it)) {
	int proto;
	static int tcp_proto;
	static int smtp_service = -1;
	struct servent *smtp_servent;
	struct protoent *tcp_protoent;

	if (smtp_service < 0) {
	    smtp_servent = getservbyname("smtp", "tcp");
	    if (smtp_servent) {
		smtp_service = smtp_servent->s_port;
	    } else {
		*error = "smtp/tcp: Unknown service";
		xfree((char *)wks_rrs);
		return FILE_FAIL;
	    }
	    tcp_protoent = getprotobyname("tcp");
	    if (tcp_protoent) {
		tcp_proto = tcp_protoent->p_proto;
	    } else {
		*error = "tcp: Unknown protocol";
		xfree((char *)wks_rrs);
		return FILE_FAIL;
	    }
	}

	if (wks_rr->rr_type != T_WKS) {
	    continue;
	}
	/* proto is in fifth byte of record data */
	proto = 0xff & wks_rr->rr_data[4];
	if (proto != tcp_proto) {
	    continue;
	}
	found_wks = TRUE;
	if (find_wks_service(wks_rr, smtp_service)) {
	    found_smtp = TRUE;
	    break;
	}
    }
    if (found_smtp) {
	success = DB_SUCCEED;
    } else if (found_wks) {
	success = DB_NOMATCH;
    } else {
	success = DB_FAIL;
    }
    xfree((char *)wks_rrs);

    return success;
}

/*
 * find_wks_service - look for a service in a WKS record
 *
 * Return TRUE if the service is listed in the record, FALSE
 * otherwise.
 */
static int
find_wks_service(rr, service)
    RR *rr;
    int service;
{
    int i = 0;
    char *s = rr->rr_data + 5;		/* skip over inet address and proto */

    while (s < rr->rr_data + rr->rr_size) {
	register unsigned int bits = 0xff & *s++;
	int bit;

	for (bit = 0; bit < 8; bit++) {
	    if (i == service) {
		return (bits & 0x80)? TRUE: FALSE;
	    }
	    bits = bits << 1;
	    i++;
	}
    }

    return FALSE;
}
#endif	/* USE_WKS_RECORDS */

static int
decode_mx_rr(rr, it, name, precedence)
    RR *rr;
    struct rr_iterator *it;
    char **name;
    int *precedence;
{
    static char nambuf[MAXDNAME];
    char *s = 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(rp)
    struct router *rp;
{
    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("router %s: Lost connection to BIND server: %s",
			 rp->name, strerrno());
    DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);

    return note_error(ERR_163, error_text);
}

static struct error *
server_failure(rp, error)
    struct router *rp;
    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("router %s: BIND server failure: %s: %s",
			 rp->name, error, strerrno());
    DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);

    return note_error(ERR_NPOSTMAST | ERR_164, error_text);
}

static struct error *
packet_error(rp, type, host)
    struct router *rp;
    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("router %s: BIND server format error in %s packet for %s",
			 rp->name, type, host);
    DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);

    return note_error(ERR_165, error_text);
}

#ifdef USE_WKS_RECORDS
static struct error *
no_smtp_service(rp, host)
    struct router *rp;
    char *host;
{
    char *error_text;

    /*
     * ERR_166 - no SMTP service for host
     *
     * DESCRIPTION
     *	    There were WKS records for the target host, though none of
     *	    them listed the SMTP service.  This means that the target
     *	    does not accept SMTP connections, and is not likely to be
     *	    something that wants to receive mail.  This error is only
     *	    returned for targets that had no MX records.
     *
     * ACTIONS
     *	    Fail the message.
     *
     * RESOLUTION
     *	    Use a different target.
     */
    error_text = xprintf("router %s: no SMTP service for: %s", rp->name, host);
    DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);

    return note_error(ERR_NSOWNER | ERR_166, error_text);
}

static struct error *
unknown_service(rp, error)
    struct router *rp;
    char *error;			/* additional error text */
{
    char *error_text;

    /*
     * ERR_167 - unknown service or protocol
     *
     * DESCRIPTION
     *	    The SMTP service or the TCP protocol numbers could not be
     *	    found.  This is a configuration error.
     *
     * ACTIONS
     *	    Defer the message as a configuration error.
     *
     * RESOLUTION
     *	    The site administrator should check to make sure that the
     *	    getservbyname() and getprotobyname() functions can find
     *	    the SMTP service and TCP protocol numbers.
     */
    error_text = xprintf("router %s: %s", rp->name, error);
    DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);

    return note_error(ERR_CONFERR | ERR_167, error_text);
}
#endif	/* USE_WKS_RECORDS */

static struct error *
no_valid_mx_records(rp, host)
    struct router *rp;
    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("router %s: no valid MX records for %s",
			 rp->name, host);
    DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);

    return note_error(ERR_NSOWNER | ERR_168, error_text);
}

static struct error *
matched_local_host(rp, host)
    struct router *rp;
    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("router %s: MX record for %s points to local host",
			 rp->name, host);
    DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);

    return note_error(ERR_NSOWNER | ERR_169, error_text);
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.