/*
#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