/*
#ident	"@(#)smail/src/routers:RELEASE-3_2_0_120:rewrite.c,v 1.37 2004/08/17 19:49:10 woods Exp"
 */
/*
 * rewrite.c
 *	rewriting router driver for smail3.
 *	A P Barrett <barrett@ee.und.ac.za>, 28 Jan 1995.
 */

/*
 * New code and information here is Copyright (C) 1995, Alan P. Barrett.
 *
 * Most of this code was stolen from the pathalias driver,
 * which carries the following copyright notice.
 *
 *    Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
 *    Copyright (C) 1992  Ronald S. Karr
 * 
 * See the file COPYING, distributed with smail, for restriction
 * and warranty information.
 */

/*
 * rewrite.c
 *	rewriting driver that matches addresses against a database and
 *	gives back a new address that is subsequently reparsed.
 *
 *	XXX: There really should be a better way of sharing code between
 *	drivers that use pathalias-style database search strategies
 *	but then use the data in different ways.
 *
 * Specifications for the rewrite driver:
 *
 *	associated transports:
 *
 *	    No specific transport is set.
 *
 *	private attribute data:
 *
 *	    file (string):  the name of the file which contains the
 *		host to route 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.
 *
 *		bsearch - performs a straightforward binary search
 *		    on a sorted file (default).
 *		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 (list of domains):  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.
 *
 *	    required (list of domains):  specifies a list of domains which
 *		targets are required to be within for a match to be successfull
 *
 *	    retries, interval (numbers):  Specify the number of attempts a
 *		failed attempt to open the database will be retried, and the
 *		interval in seconds between each retry.
 *
 *
 *	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
 *		paths file.  If not set, and if 'tryagain' is also not set,
 *		then freeze the message in the error queue and tag it with a
 *		configuration error.
 *
 *	    tryagain:  if set, then if the open fails, try again on a later
 *		spool directory queue run.  If not set, and if 'optional' is
 *		also not set, then freeze the message in the error queue and
 *		tag it with a configuration error.
 *
 *	    Note: the optional and tryagain flags are exclusive (since they
 *	    affect the behaviour under the same condition) and cannot be used
 *	    together.
 *
 *	algorithm:
 *
 *	    Given a target (host or domain name), look it up in the
 *	    database, using the same search strategy as the pathalias
 *	    driver.  The best match wins, as with pathalias.
 *
 *	    If a match is found, the information on the matching line
 *	    specifies how to rewrite the address, or whether not to
 *	    rewrite it at all.	If the address is not rewritten, the
 *	    driver behaves as if it had not been matched at all.  If
 *	    the address is rewritten, the driver sets the input address
 *	    up as the parent of the rewritten address, and returns the
 *	    rewritten address to be re-parsed.
 *
 *	    The match_always attribute has no effect on this driver.
 *	    Partial matches are treated like full matches.
 *
 *	file format:
 *	    Each line in the rewrite database has the following format:
 *
 *		domain flag format
 *
 *	    domain: as with pathalias, this is a simple hostname
 *		or a fully qualified domain name, or a partial domain
 *		name beginning with a `.'.
 *
 *	    flag: One of the following:
 *
 *		`-'	Rewriting should not be done.  The router then
 *			behaves as if the target had not been matched.
 *
 *		`+'	Rewriting should be done, using the specified
 *			format if the remainder was a simple mailbox
 *			(user name), and leaving the remainder unchanged
 *			if it was more complex.
 *
 *	    format: The format is a string to be variable expanded
 *		to produce the rewritten address.  The expansion
 *		is performed in a context in which $user refers to the
 *		remainder and $host refers to the target.
 *
 *	    examples:
 *
 *		.special.com	+	special-$user
 *
 *			This rewrites "username@subdomain.special.com" and
 *			"username@special.com" to "special-username".  For this
 *			to be useful, some other part of the smail
 *			configuration should recognise "special-username" as a
 *			local alias.  Also, don't forget to install a local
 *			alias for "special-postmaster"!  This also rewrites
 *			"user%elsewhere@special.com" to "user%elsewhere", which
 *			will later be treated like "user@elsewhere".
 *
 *		special.com	-
 *
 *			This prevents rewriting of "anything@special.com",
 *			overriding the effect of the above example.  When both
 *			this and the above example are used together,
 *			"username@special.com" will not be rewritten, but
 *			"username@subdomain.special.com" will be rewritten.
 *
 *		.foo.org	+	$user-$host
 *
 *			This rewrites "username@foo.org" to "username-foo.org",
 *			and rewrites "username@subdomain.foo.org" to
 *			"username-subdomain.foo.org".
 *
 *		frobozz.com	+	${lookup:user:lsearch{frobozz-aliases}{$value}{postmaster}}
 *
 *			This rewrites "username@frobozz.com" by searching for
 *			"username" in the file "frobozz-aliases" (in the smail
 *			lib directory).  If the search fails, it rewrites to
 *			"postmaster".
 *
 *			Note that the format of the file searched by the
 *			${lookup...} expansion is a lot more restricted than
 *			the format of a normal alias file, and that each input
 *			must map to exactly one target address value in such a
 *			file.
 *
 *		frobozz.com	+	${lookup:user:lsearch{frobozz-aliases}{$value}{"$user@$host"}}
 *
 *			As for the previous case, except that if the search
 *			fails, it rewrites to "username@frobozz.com" (note the
 *			quotes), and this will have the effect of bouncing the
 *			message with an error message that says (in a bounce):
 *
 *			"username@frobozz.com" ... not deliverable: no such user or mailbox
 *
 *			or in SMTP:
 *
 *			550-5.1.1 reject sending to address '<username@frobozz.com>'.
 *			550-5.1.1 The address <username@frobozz.com> was not accepted.
 *			550-5.1.1 Reason given was: (ERR_100) no such user or mailbox.
 *			550 5.1.1 Permanent failure logged.
 *
 *		frobozz.com	+	${lookup:user:lsearch{frobozz-aliases}{$value}{$input_addr}}
 *
 *			As for the previous case, except that if the file
 *			lookup fails it leaves the address unchanged.  The
 *			unchanged address will be passed on to subsequent
 *			routers, which might be able to route it or might fail
 *			it as an unknown host.
 *
 *			Note that quoting 
 *
 *		XXX: There should probably be a way to fine-tune
 *		the error handling.
 */

#include "defs.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.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

#include "../smail.h"
#include "../alloc.h"
#include "../list.h"
#include "../main.h"
#include "../parse.h"
#include "../addr.h"
#include "../route.h"
#include "../lookup.h"
#include "../smailstring.h"
#include "../dys.h"
#include "../transport.h"
#include "../smailconf.h"
#include "rtlib.h"
#include "rewrite.h"
#include "../extern.h"
#include "../debug.h"
#include "../error.h"
#include "../smailport.h"

/*
 * XXX: these error codes should go in error.h, or should be shared with
 * other pathalias-like routers.
 */
#define ERR_1201 1201L	/* bad entry in rewrite database */
#define ERR_1202 1202L	/* failed to open rewrite database */
#define ERR_1203 1203L	/* rewrite file lookup error */
#define ERR_1204 1204L	/* rewrite expansion failed */
#define ERR_1205 1205L	/* preparse error on rewritten address */

/* functions local to this file */
static int rewrite_lookup __P((struct router *, struct addr *, int, struct rt_info *, struct error **));
static void close_if_reopen __P((struct router *));
static int find_domain __P((struct router *, struct addr *, char **, char **, struct error **));
static struct error *bad_entry __P((struct router *, char *, char *));
static struct error *open_failed __P((struct router *, char *, char *));
static struct error *lookup_error __P((struct router *, char *, char *));
static struct error *expand_error __P((struct router *, char *, char *));

static struct attr_table rewrite_attributes[] = {
    /* note that many of these are shared with pathalias */
    { "file", t_string, 0, NULL, NULL, OFFSET(rewrite_private, file) },
    { "proto", t_string, 0, NULL, NULL, OFFSET(rewrite_private, proto) },
    { "domain", t_string, 0, NULL, NULL, OFFSET(rewrite_private, domain) },
    { "required", t_string, 0, NULL, NULL, OFFSET(rewrite_private, required) },
    { "retries", t_int, 0, NULL, NULL, OFFSET(rewrite_private, retries) },
    { "interval", t_int, 0, NULL, NULL, OFFSET(rewrite_private, interval) },
    { "reopen", t_boolean, 0, NULL, NULL, RW_REOPEN },
    { "tryagain", t_boolean, 0, NULL, NULL, RW_TRYAGAIN },
    { "optional", t_boolean, 0, NULL, NULL, RW_OPTIONAL },
#ifdef FIXME
    { NULL, 0, NULL, NULL, NULL }
#endif
};
/* FIXME:  avoid this ugly stuff -- use null-entry termination semantics... */
/* point to end of attribute table */
static struct attr_table *end_rewrite_attributes =
    ENDTABLE(rewrite_attributes);



/*
 * rtd_rewrite - rewrite using a rewrite database
 */
void
rtd_rewrite(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 */
{
    /* 
     * This router can make changes on a per user basis, or 
     * have outputs affecting a user.
     * Hence this flag is set to prevent rtd_standard caching
     * the routing information from it.
     */
    rp->flags |= RT_AFFECTS_USER;
    rtd_standard(rp, in, out, defer, fail, rewrite_lookup);
    close_if_reopen(rp);
}

/*
 * rtb_rewrite - read the configuration file attributes
 */
char *
rtb_rewrite(rp, attrs)
    struct router *rp;			/* router entry being defined */
    struct attribute *attrs;		/* list of per-driver attributes */
{
    char *error;
    static struct rewrite_private rewrite_template = {
	NULL,				/* file */
	"lsearch",			/* proto */
	NULL,				/* domains */
	NULL,				/* required */
#ifdef	HAVE_RENAME
	0,				/* retries */
#else	/* not HAVE_RENAME */
	1,				/* retries */
#endif	/* not HAVE_RENAME */
	10,				/* interval */
	NULL,				/* database -- for internal use */
	NULL,				/* error_text -- for internal use */
    };
    struct rewrite_private *priv;	/* new rewrite_private structure */
    char *fn = xprintf("rewrite router: %v", rp->name);

    /* copy the template private data */
    priv = (struct rewrite_private *) xmalloc(sizeof(*priv));
    (void) memcpy((char *) priv, (char *) &rewrite_template, sizeof(*priv));
    rp->private = (char *) priv;

    /* fill in the attributes of the private data */
    /* FIXME:  avoid this ugly stuff -- use null-entry termination semantics... */
    if ((error = fill_attributes((char *) priv,
				 attrs,
				 &rp->flags,
				 rewrite_attributes,
				 end_rewrite_attributes,
				 fn))) {
	xfree(fn);
	return error;
    }
    if ((rp->flags & RW_TRYAGAIN) && (rp->flags & RW_OPTIONAL)) {
	xfree(fn);
	return xprintf("%s: `optional' and `tryagain' attributes are incompatible", fn);
    }
    xfree(fn);

    return NULL;
}

/*
 * rtp_rewrite - dump the configuration attributes
 */
void
rtp_rewrite(f, rp)
     FILE * f;
     struct router *rp;
{
    /* FIXME:  avoid this ugly stuff -- use null-entry termination semantics... */
    (void) dump_standard_config(f,
				rp->private,
				rp->name,
				rp->flags,
				rewrite_attributes,
				end_rewrite_attributes);
}

/*
 * rewrite_lookup - lookup a host in a rewrite 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	Matched the target host and rewrote the address
 *			to something different from before.  The new
 *			address is returned in addr->remainder.
 *	DB_NOMATCH	Did not match, or matched but did not rewrite,
 *			or matched and rewrote but rewritten address
 *			was unchanged.
 *	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 rewrite 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
rewrite_lookup(rp, addr, fl, rt_info, error_p)
    struct router *rp;			/* router table entry */
    struct addr *addr;			/* addr structure */
    int fl GCC_UNUSED_HACK;		/* flags from rt[dv]_standard */
    struct rt_info *rt_info;		/* return route info here */
    struct error **error_p;		/* return lookup error here */
{
    int result;		/* status of database lookup */
    char *p;		/* temp */
    char *raw_data;	/* raw data from matching line in database */
    char *match;	/* substring of target that was matched in database */
    char *flag;		/* flag field from raw_data */
    char *format;	/* format field from raw_data */
    char *error = NULL;	/* error from some lower-level functions */
    char *new_address;	/* rewritten address */
    char *test_addr;	/* copy of rewritten address for same-ness check */
    char *test_target = NULL; /* target of rewritten address */
    char *test_remainder = NULL; /* remainder of rewritten address */
    struct rewrite_private *priv;

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

    /* is the target in the database? */
    result = find_domain(rp, addr, &raw_data, &match, error_p);

    switch (result) {
    case FILE_NOMATCH:			/* the file did not exist */
	if (rp->flags & RW_TRYAGAIN) {
	    return FILE_AGAIN;		/* translate to deferal */
	} else if (!(rp->flags & RW_OPTIONAL)) {
	    /* This isn't really necessary, but gives a more standard message */
	    *error_p = open_failed(rp, priv->file, "Database not found");

	    return  FILE_FAIL;		/* translate to an unrecov. error */
	}

	/* else the optional bit _is_ set.... */
	return FILE_NOMATCH;		/* leave as-is */

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

    /* parse raw_data into flag and format */
    p = raw_data;
    if (p) {
	while (*p && isspace((int) *p)) {
	    p++;
	}
    }
    if (p == NULL || *p == '\0') {
	*error_p = bad_entry(rp, priv->file, raw_data);
	return DB_AGAIN;
    }
    flag = p;
    while (*p && !isspace((int) *p)) {
	p++;				/* skip the flags */
    }
    while (*p && isspace((int) *p)) {
	p++;				/* and move to the next token */
    }
    format = p; /* might be empty string.  check later if we care */

    /* does the database say we should rewrite this address? */
    switch (*flag) {

    case '-':
	return DB_NOMATCH;

    case '+':
	if (*format) {
	    break; /* rewrite it */
	} else {
	    *error_p = bad_entry(rp, priv->file, raw_data);
	    return DB_AGAIN;
	}

    default:
	*error_p = bad_entry(rp, priv->file, raw_data);
	return DB_AGAIN;
    }

    /*
     * raw_data points to (and thus p and format point into) a static area
     * which is overwritten by calls to other lookup routines.  preserve the
     * portion format points to for error messages.
     */
    format = COPY_STRING(format);

    /* rewrite the 'mailbox' portion according to the specified format */
    {
	struct addr temp_addr;

	/*
	 * set temp_addr up so that $user and $host expand
	 * the way we want
	 */
	(void) memcpy((char *) &temp_addr, (char *) addr, sizeof(*addr));
	temp_addr.next_addr = addr->remainder;	/* for ${user} */
	temp_addr.next_host = addr->target;	/* for ${host} */

	/* XXX is this COPY_STRING() necessary, and is it a memory leak? */
	new_address = expand_string(COPY_STRING(format), &temp_addr, (char *) NULL, addr->remainder);
    }
    if (!new_address) {
	*error_p = expand_error(rp, format, (char *) NULL);
	return DB_AGAIN;
    }

    /*
     * is the rewritten address the same as it was before rewriting?
     */
    test_addr = COPY_STRING(new_address);
    strip_rfc822_comments(test_addr);
    strip_rfc822_whitespace(test_addr);
    if ((p = preparse_address(test_addr, &error))) {
	/* XXX should we compare the resulting 'form' too? */
	if (parse_address(p, &test_target, &test_remainder, (int *) NULL) == FAIL) {
	    /*
	     * don't return DB_AGAIN; the new value may not be parsable because
	     * it's no longer a single address....
	     */
	    DEBUG3(DBG_DRIVER_HI, "rewrite_lookup: rewritten address %s --> %s not parsable: %s\n",
		   addr->in_addr, test_addr, test_remainder);
	} else {
	    if (EQIC(test_target, addr->target) && EQIC(test_remainder, addr->remainder)) {
		DEBUG1(DBG_DRIVER_MID, "rewrite_lookup: not rewriting %s -- has same value\n", addr->in_addr);
		xfree(test_addr);
		return DB_NOMATCH;
	    }
	}
    } else {
	/*
	 * don't return DB_AGAIN; the new value may not be parsable because
	 * it's no longer a single address....
	 */
	DEBUG2(DBG_DRIVER_HI, "rewrite_lookup: preparse_address(%s): %s\n", test_addr, error);
    }
    xfree(test_addr);

    /*
     * It is necessary to record the fact that the old address is the parent of
     * the new address (mostly so that the rewrite driver can be used to
     * forward to an internal mail server without triggering a remote relay
     * violation, but also so that the parent address will appear in the logs).
     *
     */
    {
	struct addr *new = alloc_addr();

	/* new = addr; */
	(void) memcpy((char *) new, (char *) addr, sizeof(*addr));
	addr->parent = new;
    }

    /*
     * all the following is necessary to tell higher level software to re-parse
     * the address passed back in addr->remainder.
     *
     * The usual place where this kind of thing happens is in a director (not a
     * router).  Conveniently we have already effectivley saved a copy of the
     * original structure in the newly allocated parent structure above.  All
     * we have to do now is to modify the current addr structure to contain the
     * new input address field, which is actually passed through the remainder
     * field. and to tell the routing engine that we've matched the whole
     * length of the target address (which will cause the ADDR_FINISHED and
     * ADDR_FULLMATCH flags to be set in route_driver_finish() thus claiming
     * this address for ourselves).
     */
    addr->in_addr = COPY_STRING(new_address);
    addr->remainder = COPY_STRING(new_address);
    rt_info->next_host = NULL;
    rt_info->route = NULL;

    rt_info->matchlen = strlen(addr->target);

    DEBUG3(DBG_DRIVER_MID, "router %v: rewrite_lookup forcing reparse: rewrote %v --> %v\n",
	   rp->name, addr->parent->in_addr, addr->in_addr);

    return DB_SUCCEED;
}

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

    priv = (struct rewrite_private *)rp->private;
    if (priv->database && (rp->flags & RW_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 match.  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, raw_data, match, error_p)
    struct router *rp;			/* router entry */
    struct addr *addr;			/* addr structure containing target */
    char **raw_data;			/* raw data returned by lookup */
    char **match;			/* store matched target here */
    struct error **error_p;		/* lookup error */
{
    struct rewrite_private *priv;	/* private data */
    char *savedomain = NULL;		/* saved position of removed domain */
    char *target = addr->target;	/* target being searched for */
    int result;				/* return from function calls */
    char *error_text = NULL;		/* error messages from subroutines */

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

    /*
     * check for a required domain.  If none from the list of required
     * domains is found at the end of the target, don't match
     */
    if (priv->required) {
	if (match_end_domain(priv->required, target) == NULL) {

	    /* did not end in a required domain */
	    return DB_NOMATCH;
	}
    }

    /* open the database if it is not already open */
    if (priv->database == NULL) {
	if (!priv->file) {
	    result = FILE_FAIL;
	    error_text = "Database file name not specified";
	} else {
	    result = open_database(priv->file, priv->proto, priv->retries,
				   priv->interval, (struct stat *) NULL,
				   &priv->database, &error_text);
	}
	if (result != FILE_SUCCEED) {
	    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 result;
	}
    }

    /*
     * 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
     */
    result = lookup_database(priv->database, target, raw_data, &error_text);
    if (result != DB_NOMATCH) {
	if (savedomain) {
	    *savedomain = '.';		/* restore the target */
	}
	*match = target;		/* return the match and the data */
	if (result == DB_SUCCEED) {
	    while (isspace((int) **raw_data)) {
		(*raw_data)++;
	    }
	} else {
	    *error_p = lookup_error(rp, target, error_text);
	}
	return result;
    }
    if (target[0] == '.') {
	/*
	 * if it starts with a `.', look it up without the dot
	 */
	result = lookup_database(priv->database, target + 1,
				 raw_data, &error_text);
	if (result != DB_NOMATCH) {
	    if (savedomain) {
		*savedomain = '.';
	    }
	    *match = target + 1;
	    if (result == DB_SUCCEED) {
		while (isspace((int) **raw_data)) {
		    (*raw_data)++;
		}
	    } else {
		*error_p = lookup_error(rp, target, error_text);
	    }
	    return result;
	}
    } else {
	/*
	 * if it does not start with a '.', look it up with a dot.
	 * This involves making a temporary copy with a '.' at the
	 * beginning.
	 */
	char *p = xprintf(".%s", target);

	result = lookup_database(priv->database, p, raw_data, &error_text);
	xfree(p);

	if (result != DB_NOMATCH) {
	    if (savedomain) {
		*savedomain = '.';
	    }
	    *match = target;
	    if (result == DB_SUCCEED) {
		while (isspace((int) **raw_data)) {
		    (*raw_data)++;
		}
	    } else {
		*error_p = lookup_error(rp, target, error_text);
	    }
	    return result;
	}
    }

    /*
     * 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 = strchr(target, '.');
	if (target) {
	    /* if there is anything left, look it up */
	    result = lookup_database(priv->database, target,
				      raw_data, &error_text );
	    if (result != DB_NOMATCH) {
		if (savedomain) {
		    *savedomain = '.';
		}
		*match = target;
		if (result == DB_SUCCEED) {
		    while (isspace((int) **raw_data)) {
			(*raw_data)++;
		    }
		} else {
		    *error_p = lookup_error(rp, target, error_text);
		}
		return result;
	    }
	}
    }

    /* no match found */
    if (savedomain) {
	*savedomain = '.';
    }

    return DB_NOMATCH;
}

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

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

    /*
     * ERR_1201 - bad entry in rewrite database
     *
     * DESCRIPTION
     *      The rewrite line didn't match any of the expected patterns.
     *
     * ACTIONS
     *      Defer the message with a configuration error.
     *
     * RESOLUTION
     *      The postmaster should correct the rewrite database entry.
     */
    error_text = xprintf("router %v: driver %s: bad entry in database %v: %v",
			 rp->name, rp->driver, file, raw_data);
    DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);

    return note_error(ERR_CONFERR|ERR_1201, error_text);
}

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

    /*
     * ERR_1202 - failed to open rewrite database
     *
     * DESCRIPTION
     *      open_database() failed to open a rewrite database.  The
     *      error encountered should be stored in errno.
     *
     * ACTIONS
     *      Defer all of the input addresses as configuration errors, but do
     *      not call it a configuration error if `tryagain' is set.
     *
     * RESOLUTION
     *      The postmaster should check the router entry against the
     *      database he wishes to use.
     */
    error_text = xprintf("router %v: driver %s: database '%v', open failed: %s",
			 rp->name, rp->driver, file, open_error);
    DEBUG2(DBG_DRIVER_LO, "%s: %s\n", error_text, (rp->flags & RW_TRYAGAIN) ? "(will defer)" : "Configuration error!");

    return note_error((rp->flags & RW_TRYAGAIN) ? ERR_1202 : (ERR_CONFERR | ERR_1202), 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_1203 - rewrite 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 %v: driver %s: target %v, lookup failed: %s",
			 rp->name, rp->driver, target, lookup_error_text);
    DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);

    return note_error(ERR_1203, error_text);
}

static struct error *
expand_error(rp, format, infop)
    struct router *rp;
    char *format;
    char *infop;
{
    char *error_text;

    /*
     * ERR_1204 - rewrite expansion failed
     *
     * DESCRIPTION
     *      expand_string() failed to expand the format
     *      for a rewrite router.
     *
     * ACTIONS
     *      Defer the message with a configuration error.
     *
     * RESOLUTION
     *      The postmaster should fix the entry in the
     *	    rewrite database.
     */
    if (!infop) {
	error_text = xprintf("router %v: driver %s: expansion of \"%v\" failed",
			     rp->name, rp->driver, format);
    } else {
	error_text = xprintf("router %v: driver %s: expansion of \"%v\" failed: %s",
			     rp->name, rp->driver, format, infop);
    }
    DEBUG1(DBG_DRIVER_LO, "%s\n", error_text);
    return note_error(ERR_CONFERR|ERR_1204, error_text);
}

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