/* @(#) $Id: reroute.c,v 1.3 1992/09/20 14:16:32 tron Exp $ */

/*
 *    Copyright (C) 1992 Uwe Doering
 *
 * The rerouting algorithm was taken from the VHS-Reroute sources
 * written by Heiko Schlichting (heiko@methan.chemie.fu-berlin.de).
 * 
 * See the file COPYING, distributed with smail, for restriction
 * and warranty information.
 *
 * Uwe Doering can be contacted at gemini@geminix.in-berlin.de.
 */

/*
 * reroute.c
 *	rerouting driver that matches hosts in a bang path address
 *	against a database and gives back a new address in remainder
 *	that is subsequently reparsed.
 *
 * Specifications for the reroute driver:
 *
 *	associated transports:
 *	    No specific transport is set.
 *
 *	private attribute data:
 *	    file (string):  the name of the file which contains the
 *		host to reroute association database.  In the case of
 *		the dbm protocol this will actually be the basename
 *		for the two associated DBM files, ending in .pag and
 *		.dir.
 *
 *	    proto (name):  specifies the protocol used in accessing
 *		the database.  Can be one of:
 *
 *		lsearch - performs a linear search (default).
 *		bsearch - performs a straightforward binary search
 *		    on a sorted file.
 *		dbm - uses the Berkeley DBM routines.  If NDBM is
 *		    available, it is used instead.  If only the
 *		    older routines are available, exactly one DBM
 *		    database can be opened by smail.
 *
 *	    domain (string):  specifies the default domain for hosts
 *		in the database.  Targets ending in this domain will
 *		have the domain stripped (including a preceding dot)
 *		before the database is searched.  A target containing
 *		only the domain (e.g., .uucp) will remain .uucp.
 *
 *	private attribute flags:
 *	    reopen:  if set, then reopen the database for each call
 *		to the routing driver, and close before each return.
 *		This is necessary for systems that would not otherwise
 *		have a sufficient number of available file descriptors
 *		for all of their routing and directing needs.
 *	    optional:  if set, then if the open fails, assume an empty
 *		reroute file.
 *	    tryagain:  if set, then if the open fails, try again on a
 *		later spool directory queue run.
 *	    matchall:  if set, reroute all bang path addresses.  The
 *		rerouting database isn't used at all in this mode, so
 *		even hosts that have an exclusion flag (`-') in the
 *		database will be rerouted.
 *		USE THIS ONLY FOR DEBUGGING PURPOSES.  Unconditional
 *		rerouting is an offense against the net.community!
 *	    matchlocal: if set, before the database lookup is done the
 *		target is checked against the local host names, and if
 *		a match is found, rerouting is done for the previous
 *		target.  Note that we're scanning the path backwards,
 *		so "previous" means on the right side.
 *	    matchdb: if set, a database lookup is done for the target.
 *		Otherwise, the reroute database isn't used.
 *	    bounceonly: if set together with the matchlocal flag, local
 *		host name matching is done only for bounce mails. This
 *		is useful for hosts that in general don't want to reroute
 *		mails, but nevertheless want to collaps bounce mail bang
 *		paths that have been generated by a mail looping between
 *		two host, so that only the actual address remains that is
 *		necessary to deliver the bounce mail to the target host.
 *
 *	algorithm:
 *	    The given address is checked on whether it is a pure bang
 *	    path address.  Only these are candidates for rerouting.
 *	    Then the path is scanned backwards for known hosts.  If
 *	    a host is known and the reroute flag field is a `+', the path
 *	    components preceding the known host are dropped and the
 *	    remainder is given back for reparsing.  If the reroute flag
 *	    field is a `-', no rerouting is done for this host, and the
 *	    search is continued with the next host.
 *
 *	    To determine whether a host should be classified as
 *	    known the following strategy is used to look up the
 *	    host in the reroute database:
 *
 *	    1.	look for an exact match of the domain.  If not
 *		found, and the domain begins with a dot, look for
 *		a match without the dot.
 *	    2.	if previous attempts failed, strip first component
 *		of the domain, leaving the initial dot on
 *		the second component.  Look for a match.
 *	    3.	if this fails, strip the next component and repeat
 *		from step 2 until no more components remain.
 *
 *	    If a match is found, the remaining path components are returned
 *	    as the remainder for reparsing.
 *
 *	    NOTE:  this router will, if a new address has been generated,
 *		   return DB_SUCCEED.  However, it doesn't return any
 *		   routing informations but instead forces a reparsing of
 *		   the returned address.
 *
 *	file format:
 *	    The reroute database has the following file format:
 *
 *	    .do.main	+
 *	    .bar	+
 *	    .foo.bar	-
 *	    host1.foo.bar	+
 *	    host2	+
 *	    host2.abc	+
 *	    host3	+
 *	    host4	+
 *
 *	    The first field is the key that is used for the lookup, and
 *	    the second field is the data section.  It can contain either
 *	    a plus sign (`+'), which tells the reroute driver to do
 *	    rerouting for the matching host, or a minus sign (`-'), which
 *	    tells the driver not to reroute the host.  This allows to
 *	    make exclusions for hosts with a domain for which a catch-all
 *	    entry exists in the reroute database.  The key and data fields
 *	    are separated by exactly _one_ whitespace.
 *
 *	    In the exclusion example above hosts with a domain `.bar' are
 *	    generally rerouted, with the exception of hosts that are
 *	    in the domain `.foo.bar'.  To this exception there is also
 *	    an exception, that is, the backbone itself for the `.foo.bar'
 *	    domain (`host1.foo.bar') _is_ rerouted.
 *
 *	    The reason for such a construction is that although you
 *	    do rerouting for `.bar', for technical or political
 *	    reasons you don't want to interfere with the `.foo.bar'-
 *	    internal routing generated by other sites.  So you are
 *	    rerouting hosts preceding, and including, the backbone
 *	    `host1.foo.bar', but you don't touch the domain-internal
 *	    routing.
 *
 *	THE DOWNSIDE OF REROUTING:
 *	    While rerouting can be a great service to the net, it can
 *	    be disastrous if done wrong.  Here are two situations where
 *	    it can be useful to reroute mail:
 *
 *	    1. If your site is on the Internet, and it is a backbone
 *	       for a domain of UUCP sites that are connected to your site,
 *	       your site might have access to routing informations that
 *	       aren't available to the UUCP sites (MX-records etc.).  Using
 *	       shortcuts over the internet instead of sending the mail
 *	       along the original slow and maybe expensive (telephone
 *	       charges) path is an advantage that justifies rerouting.
 *
 *	    2. If one or more links that are in your direct neighbourhood
 *	       (one or two hops away) have changed and the new UUCP maps
 *	       haven't yet propagated wide enough to make other sites
 *	       aware of these changes, you could use rerouting to
 *	       _temporarely_ correct the incoming bang paths that
 *	       were generated from the old maps.
 *
 *	    However, before you enable rerouting you should make sure
 *	    that your pathalias routing database is really accurate.
 *	    It's not enough that you install the posted maps automatically.
 *	    These maps are written by lots of people who might very well
 *	    don't have enough knowledge to do it right.
 *
 *	    Errors and unrealistic link costs (often a result of the
 *	    author's need of an ego boost) in maps are responsible for more
 *	    distorted or bogus paths than one would think.  Therefore,
 *	    it is necessary to check this database line by line whenever
 *	    it is rebuild from the maps.  If you can't or don't want to
 *	    do this, don't use rerouting! Or at least only for sites in
 *	    your direct neighbourhood.  Rerouting with a poorly maintained
 *	    routing database is a disservice to the net!
 *
 *	    Also, if you think that you have the resources for rerouting,
 *	    do it only when you are willing to keep in touch with the
 *	    maintainers of other rerouting sites in your area.  Mail loops
 *	    between two rerouting sites are a common problem that can be
 *	    reduced only by discussing changes you want to make to your
 *	    routing in _advance_.
 *
 *	    So in short, folks, don't use rerouting if it isn't absolutely
 *	    necessary.  Please don't use it as a means to inflate your ego.
 *	    Instead, maintaining a rerouting host is a responsible task that
 *	    needs lots of time and determination.  Please restrain yourself,
 *	    for the sake of fast and flawless mail delivery.
 */
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.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 "reroute.h"
#ifndef DEPEND
# include "../extern.h"
# include "../debug.h"
# include "../error.h"
#endif

/* functions local to this file */
static int reroute_lookup();
static void close_if_reopen();
static int find_domain();
static struct error *bad_entry();
static struct error *open_failed();
static struct error *lookup_error();


/*
 * rtd_reroute - reroute using a reroute database
 */
void
rtd_reroute(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, reroute_lookup);
    close_if_reopen(rp);
}

/*
 * rtv_reroute - verify that a match exists in a reroute database
 */
void
rtv_reroute(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, reroute_lookup);
    close_if_reopen(rp);
}

#ifdef notyet
/*
 * rtc_reroute - open a reroute database in the server, if so requested
 */
void
rtc_reroute(rp)
    struct router *rp;			/* router entry */
{
    struct reroute_private *priv;
    int success;
    char *error_text;

    priv = (struct reroute_private *)rp->private;
    if (rp->flags & RR_CACHEOPEN) {
	rp->flags &= ~(RR_OPENFAIL | RR_OPENAGAIN | RR_OPENNOMATCH);
	if (priv->error_text) {
	    xfree(priv->error_text);
	}
	success = cache_database(priv->file, priv->proto,
				 priv->retries, priv->interval,
				 (struct stat *)NULL,
				 &priv->database, &error_text);
	switch (success) {
	case FILE_FAIL:
	    rp->flags |= RR_OPENFAIL;
	    error_text = COPY_STRING(error_text);
	    break;

	case FILE_AGAIN:
	    rp->flags |= RR_OPENAGAIN;
	    error_text = COPY_STRING(error_text);
	    break;

	case FILE_NOMATCH:
	    rp->flags |= RR_OPENNOMATCH;
	}
    }
}
#endif

#ifdef notyet
/*
 * rtf_reroute - close a reroute database when smail is done with router
 */
void
rtf_reroute(rp)
    struct router *rp;
{
    struct reroute_private *priv;

    priv = (struct reroute_private *)rp->private;
    rp->flags &= ~(RR_OPENFAIL | RR_OPENAGAIN | RR_OPENNOMATCH);
    if (priv->database) {
	close_database(priv->database);
    }
    if (priv->error_text) {
	xfree(priv->error_text);
    }
}
#endif

/*
 * rtb_reroute - read the configuration file attributes
 */
char *
rtb_reroute(rp, attrs)
    struct router *rp;			/* router entry being defined */
    struct attribute *attrs;		/* list of per-driver attributes */
{
    char *error;
    static struct attr_table reroute_attributes[] = {
	{ "file", t_string, NULL, NULL, OFFSET(reroute_private, file) },
	{ "proto", t_string, NULL, NULL, OFFSET(reroute_private, proto) },
	{ "domain", t_string, NULL, NULL, OFFSET(reroute_private, domain) },
	{ "required", t_string, NULL, NULL,
	  OFFSET(reroute_private, required) },
	{ "retries", t_int, NULL, NULL, OFFSET(reroute_private, retries) },
	{ "interval", t_int, NULL, NULL, OFFSET(reroute_private, interval) },
	{ "reopen", t_boolean, NULL, NULL, RR_REOPEN },
	{ "tryagain", t_boolean, NULL, NULL, RR_TRYAGAIN },
	{ "optional", t_boolean, NULL, NULL, RR_OPTIONAL },
	{ "matchall", t_boolean, NULL, NULL, RR_MATCHALL },
	{ "matchlocal", t_boolean, NULL, NULL, RR_MATCHLOCAL },
	{ "matchdb", t_boolean, NULL, NULL, RR_MATCHDB },
	{ "bounceonly", t_boolean, NULL, NULL, RR_BOUNCEONLY },
	{ "cacheopen", t_boolean, NULL, NULL, RR_CACHEOPEN },
    };
    static struct attr_table *end_reroute_attributes =
	ENDTABLE(reroute_attributes);
    static struct reroute_private reroute_template = {
	NULL,				/* file */
	"lsearch",			/* proto */
	NULL,				/* domains */
	NULL,				/* required */
#ifdef	HAVE_RENAME
	0,				/* retries */
#else	/* not HAVE_RENAME */
	1,				/* retries */
#endif	/* not HAVE_RENAME */
	2,				/* interval */
	NULL,				/* database -- for internal use */
    };
    struct reroute_private *priv;	/* new reroute_private structure */

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

    rp->private = (char *)priv;
    /* fill in the attributes of the private data */
    error = fill_attributes((char *)priv,
			    attrs,
			    &rp->flags,
			    reroute_attributes,
			    end_reroute_attributes);

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


/*
 * reroute_lookup - lookup a host in a reroute database
 *
 * 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	Rerouting was done, reparse.
 *	DB_NOMATCH	No rerouting was possible.
 *	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	The reroute database could not be opened and
 *			is optional.
 *	FILE_AGAIN	File is required to exist but does not,
 *			Try again later.
 *	FILE_FAIL	A major error has been caught in router,
 *			notify postmaster.
 */
/*ARGSUSED*/
static int
reroute_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 */
{
    int success;
    char *new_path;
    char *flag;

    success = find_domain(rp, addr, &new_path, &flag, error_p);

    switch (success) {
    case FILE_NOMATCH:
	if ((rp->flags & RR_OPTIONAL) == 0) {
	    struct reroute_private *priv;

	    priv = (struct reroute_private *)rp->private;
	    *error_p = open_failed(rp, priv->file, "Database not found");
	    success = FILE_FAIL;
	}
	/* FALL THROUGH */

    case FILE_FAIL:
    case FILE_AGAIN:
    case DB_NOMATCH:
    case DB_FAIL:
    case DB_AGAIN:
	return success;
    }

    /* check the reroute flag */
    if (flag[0] == '+') {
	DEBUG1(DBG_DRIVER_MID, "reroute_lookup: rerouted: address=%s\n",
	       new_path);
	rt_info->next_host = NULL;
	rt_info->route = NULL;
	rt_info->matchlen = strlen(addr->target);
	addr->remainder = new_path;
	return DB_SUCCEED;	/* reparse */
    }

    *error_p = bad_entry(rp, flag);
    return DB_AGAIN;
}

/*
 * close_if_reopen - close the database if the reopen flag is set.
 */
static void
close_if_reopen(rp)
    struct router *rp;
{
    register struct reroute_private *priv;

    priv = (struct reroute_private *)rp->private;
    if (priv->database && (rp->flags&RR_REOPEN)) {
	/*
	 * close the database if it was open and the
	 * reopen attribute is on stating that the database
	 * should be opened on every call to the router.
	 */
	close_database(priv->database);
	priv->database = NULL;
    }
}

/*
 * find_domain - return the database entry for the given domain
 *
 * match all or part of the target.  If a match is found, the target
 * string that matched is returned in new_path.  Return one of:
 *
 * DB_SUCCEED	operation was successful and a match was found
 * DB_FAIL	unrecoverable error in lookup
 * DB_AGAIN	retry operation at a later time
 * DB_NOMATCH	no match was found for target
 * FILE_FAIL	unrecoverable database error
 * FILE_AGAIN	try using the database later
 * FILE_NOMATCH	the file was not found
 */
static int
find_domain(rp, addr, new_path, flag, error_p)
    struct router *rp;			/* router entry */
    struct addr *addr;			/* addr structure containing target */
    char **new_path;			/* new path returned by lookup */
    char **flag;			/* reroute flag returned by lookup */
    struct error **error_p;		/* lookup error */
{
    struct reroute_private *priv;	/* private data */
    char *savedomain = NULL;		/* saved position of removed domain */
    char *saveat = NULL;		/* saved position of removed at sign */
    char *saveangle = NULL;		/* saved position of removed > */
    char *savebang;			/* saved position of removed bang */
    char *mark_start = NULL;		/* marked position of < */
    char *mark_end = NULL;		/* marked position of > */
    char *ap = addr->in_addr;		/* original address we are checking */
    char *path = addr->remainder;	/* path that we are scanning */
    register char *token;		/* used for token scanning */
    register char *target;		/* target being searched for */
    int success;			/* return from function calls */
    char *error_text;			/* error messages from subroutines */

    priv = (struct reroute_private *)rp->private;

    /*
     * check whether the last address delimiter in the remainder part of
     * the address is a bang (`!').  If not, don't match
     */
    token = back_address_token(path, path + strlen(path));
    if (token == NULL || --token <= path || *token != '!') {
	return DB_NOMATCH;	/* nothing to reroute */
    }
    savebang = token;

    /*
     * now we know that we have at least two address components separated
     * by a bang character.  So this is a candidate for rerouting
     *
     * next we make sure that the original address, and therefore the
     * remainder part of the address that we are later scanning for
     * known hosts, is a pure bang path
     */

    /*
     * scan for < and > pairs and find the last or innermost matching
     * pair.  We can safely assume that preparse_address() has already
     * checked that < and > are properly nested, so we don't need to
     * check this again
     */
    for (token = ap; token && *token; token = address_token(token)) {
	if (*token == '<') {
	    mark_start = token + 1;
	    mark_end = NULL;
	} else if (*token == '>') {
	    if (mark_end == NULL) {
		mark_end = token;
	    }
	}
    }
    /* narrow to the route-addr */
    if (mark_start && mark_end) {
	ap = mark_start;
	*mark_end = '\0';
	saveangle = mark_end;
    }

    /*
     * is there an at sign (`@') at the end of the original address? If
     * found, does it precede our own host name? If so, strip it off,
     * otherwise don't match
     */
    token = back_address_token(ap, ap + strlen(ap));
    if (token == NULL || --token <= ap) {
	if (saveangle) {
	    *saveangle = '>';
	}
	return DB_NOMATCH;	/* nothing to reroute */
    }
    if (*token == '@') {
	if (islocalhost(token + 1)) {
	    *token = '\0';
	    saveat = token;
	} else {
	    if (saveangle) {
		*saveangle = '>';
	    }
	    return DB_NOMATCH;	/* not a pure bang path */
	}
    }

    /*
     * check whether there are any address separators other than the
     * bang character in the original address.  If so, don't match
     */
    token = ap;
    if (*token == '@') {
	if (saveat) {
	    *saveat = '@';
	}
	if (saveangle) {
	    *saveangle = '>';
	}
	return DB_NOMATCH;	/* not a pure bang path */
    }
    while ((token = address_token(token)) && *token) {
	if (*token != '!') {
	    if (saveat) {
		*saveat = '@';
	    }
	    if (saveangle) {
		*saveangle = '>';
	    }
	    return DB_NOMATCH;	/* not a pure bang path */
	}
	token++;
    }

    if (saveat) {
	*saveat = '@';		/* restore address */
    }
    if (saveangle) {
	*saveangle = '>';	/* restore address */
    }

    /*
     * scan the path backwards for hosts we have in our database.
     * If we don't know any host, don't match
     */
    for (; savebang; savebang = token) {
	/* select host to lookup */
	*savebang = '\0';
	token = back_address_token(path, savebang);
	if (token && --token < path) {
	    token = NULL;
	}
	*new_path = target = token == NULL? path: token + 1;

	/*
	 * check for a required domain.  If none from the list of required
	 * domains is found at the end of the target, continue with the
	 * next target
	 */
	if (priv->required) {
	    if (match_end_domain(priv->required, target) == NULL) {
		/* did not end in a required domain */
		*savebang = '!';		/* restore the target */
		continue;
	    }
	}

	/*
	 * if the RR_MATCHLOCAL flag is set and the target matched one of
	 * the local host names, rerouting is done for the previous target.
	 * Note that we're scanning the path backwards, so "previous" means
	 * on the right side.  The local host matching is restricted to
	 * bounce mails if the RR_BOUNCEONLY flag is set.
	 */
	if ((rp->flags & RR_MATCHLOCAL)
	    && (!(rp->flags & RR_BOUNCEONLY) || error_sender)
	    && islocalhost(target[0] == '.'? target + 1: target)) {
	    DEBUG1(DBG_DRIVER_HI, "find_domain: local host match: %s\n",
		   *new_path);
	    *savebang = '!';		/* restore the target */
	    *new_path = savebang + 1;	/* adjust result pointer */
	    *flag = "+";		/* fake the reroute flag */
	    return DB_SUCCEED;
	}

	/*
	 * if the RR_MATCHALL flag is set, rerouting is done for every
	 * bang path address
	 */
	if (rp->flags & RR_MATCHALL) {
	    DEBUG1(DBG_DRIVER_HI, "find_domain: unconditional match: %s\n",
		   *new_path);
	    *savebang = '!';		/* restore the target */
	    *flag = "+";		/* fake the reroute flag */
	    return DB_SUCCEED;
	}

	/* should we match the reroute database? */
	if (!(rp->flags & RR_MATCHDB)) {
	    *savebang = '!';		/* restore the target */
	    continue;			/* no database lookup */
	}

	/* open the database if it is not already open */
	if (priv->database == NULL) {
	    error_text = priv->error_text;
	    if (rp->flags & RR_OPENFAIL) {
		success = FILE_FAIL;
	    } else if (rp->flags & RR_OPENAGAIN) {
		success = FILE_AGAIN;
	    } else if (rp->flags & RR_OPENNOMATCH) {
		success = FILE_NOMATCH;
	    } else {
		success = open_database(priv->file, priv->proto,
					priv->retries, priv->interval,
					(struct stat *)NULL, &priv->database,
					&error_text);
	    }
	    if (success != FILE_SUCCEED) {
		*savebang = '!';		/* restore the target */
		switch (success) {
		case FILE_NOMATCH:
		    rp->flags |= RR_OPENNOMATCH;
		    if ((rp->flags & (RR_OPTIONAL | RR_MATCHLOCAL))
					== (RR_OPTIONAL | RR_MATCHLOCAL)) {
			    continue;
		    }
		    return FILE_NOMATCH;

		case FILE_FAIL:
		    rp->flags |= RR_OPENFAIL;
		    break;

		case FILE_AGAIN:
		    rp->flags |= RR_OPENAGAIN;
		    break;
		}
		if (priv->error_text) {
		    xfree(priv->error_text);
		}
		priv->error_text = COPY_STRING(error_text);
		*error_p = open_failed(rp, priv->file, error_text);
		return success;
	    }
	}

	/*
	 * check for a domain to be stripped.  If the target ends in one
	 * of the domains listed in the domain attribute, that part of the
	 * target is stripped.  The domain list is searched from left to
	 * right and the first match found is used.
	 */
	if (priv->domain) {
	    savedomain = match_end_domain(priv->domain,
					  target[0] == '.'? target + 1: target);
	    if (savedomain) {
		*savedomain = '\0';
	    }
	}

	/*
	 * lookup the target as is
	 */
	success = lookup_database(priv->database, target, flag, &error_text);
	if (success != DB_NOMATCH) {
	    if (savedomain) {
		*savedomain = '.';		/* restore the target */
	    }
	    if (success == DB_SUCCEED) {
		if ((*flag)[0] == '-') {
		    *savebang = '!';		/* restore the target */
		    continue;			/* next target */
		}
		DEBUG1(DBG_DRIVER_HI, "find_domain: database match: %s\n",
		       *new_path);
	    } else {
		*error_p = lookup_error(rp, target, error_text);
	    }
	    *savebang = '!';		/* restore the target */
	    return success;
	}
	if (target[0] == '.') {
	    /*
	     * if it starts with a `.', look it up without the dot
	     */
	    success = lookup_database(priv->database, target + 1,
				      flag, &error_text);
	    if (success != DB_NOMATCH) {
		if (savedomain) {
		    *savedomain = '.';		/* restore the target */
		}
		if (success == DB_SUCCEED) {
		    if ((*flag)[0] == '-') {
			*savebang = '!';	/* restore the target */
			continue;		/* next target */
		    }
		    DEBUG1(DBG_DRIVER_HI, "find_domain: database match: %s\n",
			   *new_path);
		} else {
		    *error_p = lookup_error(rp, target, error_text);
		}
		*savebang = '!';		/* restore the target */
		return success;
	    }
	}

	/*
	 * strip away leading domain parts until a match is found,
	 * or no parts of the domain remain
	 */
	while (target) {
	    /* advance past an initial dot */
	    if (target[0] == '.') {
		target++;
	    }

	    /* advance to the next dot */
	    target = index(target, '.');
	    if (target) {
		/* if there is anything left, look it up */
		success = lookup_database(priv->database, target,
					  flag, &error_text );
		if (success != DB_NOMATCH) {
		    if (savedomain) {
			*savedomain = '.';
		    }
		    if (success == DB_SUCCEED) {
			if ((*flag)[0] == '-') {
			    break;		/* next target */
			}
			DEBUG1(DBG_DRIVER_HI,
			       "find_domain: database match: %s\n",
			       *new_path);
		    } else {
			*error_p = lookup_error(rp, target, error_text);
		    }
		    *savebang = '!';		/* restore the target */
		    return success;
		}
	    }
	}

	/* no match found */
	if (savedomain) {
	    *savedomain = '.';
	}
	*savebang = '!';		/* restore the target */
    }

    return DB_NOMATCH;
}

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

static struct error *
bad_entry(rp, raw_path)
    struct router *rp;
    char *raw_path;
{
    char *error_text;

    /*
     * ERR_1000 - bad entry in reroute database
     *
     * DESCRIPTION
     *      The reroute line didn't contain the proper reroute flag in
     *      the data field, which should be `+' or `-', separated from
     *      the key field by exactly _one_ whitespace.
     *
     * ACTIONS
     *      Defer the message with a configuration error.
     *
     * RESOLUTION
     *      The postmaster should correct the reroute database entry.
     */
    error_text = xprintf("router %s: bad entry in reroute database: %s",
			 rp->name, raw_path);
    DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);

    return note_error(ERR_CONFERR|ERR_1000, error_text);
}

static struct error *
open_failed(rp, file, open_error)
    struct router *rp;
    char *file;
    char *open_error;
{
    char *error_text;

    /*
     * ERR_1001 - failed to open reroute database
     *
     * DESCRIPTION
     *      open_database() failed to open a reroute database.  The
     *      error encountered should be stored in errno.
     *
     * ACTIONS
     *      Defer all of the input addresses as configuration errors.
     *
     * RESOLUTION
     *      The postmaster should check the director entry against the
     *      database he wishes to use.
     */
    error_text = xprintf("router %s: reroute database %s, open failed: %s",
			 rp->name, file, open_error);
    DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);

    return note_error(ERR_1001, error_text);
}

static struct error *
lookup_error(rp, target, lookup_error_text)
    struct router *rp;
    char *target;
    char *lookup_error_text;
{
    char *error_text;

    /*
     * ERR_1002 - reroute file lookup error
     *
     * DESCRIPTION
     *      lookup_database() returned an error.  Text describing the
     *	    error was returned by lookup_error().
     *
     * ACTIONS
     *      Action depends upon the error.
     *
     * RESOLUTION
     *      Unspecified.
     */
    error_text = xprintf("router %s: target %s, lookup failed: %s",
			 rp->name, target, lookup_error_text);
    DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);

    return note_error(ERR_1002, error_text);
}
