/* @(#) $Id: route.c,v 1.20 1994/10/17 17:04:37 nm4 Exp $ */

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

/*
 * route.c:
 *	Compute route to target hosts and fill in the transport,
 *	next host and next address for a recipient address.
 *
 *	external functions: route_remote_addrs, verify_remote_addrs,
 *			    cache_routers, finish_routers,
 *			    match_end_domain, route_driver_finish,
 *			    find_router, find_route_driver,
 *			    read_router_file, read_method_file
 */

#define NEED_SOCKETS
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "defs.h"
#include "smail.h"
#include "route.h"
#ifdef HAVE_BSD_NETWORKING
#  define USE_MX_HINTS
#  include "bindsmtpth.h"
#endif /* HAVE_BSD_NETWORKING */
#include "transport.h"
#include "addr.h"
#include "dys.h"
#include "log.h"
#include "exitcodes.h"
#include "parse.h"
#include "smailconf.h"
#ifndef DEPEND
# include "extern.h"
# include "debug.h"
# include "error.h"
#endif

/* variables exported from this file */
int cached_routers = FALSE;		/* TRUE if cache_routers() called */

/* functions local to this file */
static void premunge_remote_addrs();
static void compute_transport();
static void compute_next_addr();
static void next_addr_basic();
static void next_addr_uucp();
static char *route_to_route_addr();
static char *rebuild_work_addr();
static char *router_read_method();
static char *router_driv_function();


/*
 * route_remote_addrs - route addrs to transport, next host and next_addr
 */
void
route_remote_addrs(in, out, retry, defer, fail)
    struct addr *in;			/* input local-form addrs */
    struct addr **out;			/* output resolved addrs */
    struct addr **retry;		/* addr structures to reparse */
    struct addr **defer;		/* addrs to defer to a later time */
    struct addr **fail;			/* unresolvable addrs */
{
    struct router *rp;			/* temp for stepping thru routers */
    register struct addr *cur;		/* temp for stepping through addrs */
    struct addr *next;			/* next value for cur */
    struct transport_hints * hint;
#ifdef USE_MX_HINTS
    struct mx_transport_hint * mx_hint;
    struct ipaddr_hint * a_hint;
#endif /* USE_MX_HINTS */

    DEBUG(DBG_ROUTE_HI, "route_remote_addrs called\n");

    /* first munge the addr structure as required */
    premunge_remote_addrs(in);

    /* sort addresses to simplify duplicate elimination by rtd_standard() */
    in = addr_sort(in, OFFSET(addr, target));

    /*
     * give the complete input list to each router in turn.
     * router drivers must obey the rules about overriding
     * of routes produced by previous routers.  At the end
     * this routine will cleanup the addr structures.
     */
    for (rp = routers; rp; rp = rp->succ) {
	struct addr *new_in;		/* addrs to put through next router */
	/* look up the router driver by name */
	struct route_driver *driver = find_route_driver(rp->driver);

	if (driver == NULL) {
	    /*
	     * ERR_109 - router driver not found
	     *
	     * DESCRIPTION
	     *      A driver name was not in the table of router drivers.
	     *
	     * ACTIONS
	     *      Defer all input addresses with configuration errors.
	     *      Since it cannot be known if this router would have
	     *      overridden previous routers, all input addresses must be
	     *      deferred.
	     *
	     * RESOLUTION
	     *      The postmaster must check the router configuration
	     *      before deliver can be performed.
	     */
	    insert_addr_list(in,
			     defer,
			     note_error(ERR_CONFERR|ERR_109,
					xprintf(
					      "router %s: driver %s not found",
						rp->name,
						rp->driver)));
	    return;
	}

	/* call the driver */
	new_in = NULL;
	(*driver->driver)(rp, in, &new_in, defer, fail);
	in = new_in;
    }

    /*
     * now that all routers have had their chance we cleanup
     * the input list to form the output list of resolved
     * addresses, and note addresses that no router could
     * route to.
     */
    for (cur = in; cur; cur = next) {
	next = cur->succ;

	if (cur->match_count == -1 ||
	    (cur->flags & (ADDR_PARTLOCAL|ADDR_FULLMATCH)) == ADDR_PARTLOCAL)
	{
	    /* no router could match the target, fail address */
	    exitvalue = EX_NOHOST;	/* set exit status */
	    /*
	     * ERR_101 - unknown host
	     *
	     * DESCRIPTION
	     *      The target in this addr structure was not matched by any
	     *      of the routers.
	     *
	     * ACTIONS
	     *      A message is sent to the owner of the address, or to the
	     *      sender if the address has no owner.
	     *
	     * RESOLUTION
	     *      The address owner or sender should determine the correct
	     *      domain name.  If the owner or sender believes the
	     *      message was in error, he or she should send mail to the
	     *      postmaster.
	     */
	    cur->error = note_error(ERR_NSOWNER|ERR_101, "unknown host");
	    cur->succ = *fail;
	    *fail = cur;
	    continue;
	}
	if (cur->next_host == NULL) {
	    /* routed to local host, reparse */
	    (void) strcpy(cur->work_addr, cur->remainder);
	    cur->flags &= ~(ADDR_FINISHED | ADDR_FULLMATCH);
	    cur->transport = NULL;
	    cur->tphint_list = NULL;
	    if (cur->route) {
		xfree(cur->route);
		cur->route = NULL;
	    }
	    cur->remainder = NULL;
	    cur->succ = *retry;
	    *retry = cur;
	    continue;
	}
	if (cur->transport == NULL) {
	    /* router did not compute a transport, we must */
	    compute_transport(cur);
	    if (cur->error && (cur->error->info & ERR_CONFERR)) {
		/* defer configuration errors */
		cur->succ = *defer;
		*defer = cur;
		continue;
	    }
	}
	if (cur->next_addr == NULL) {
	    compute_next_addr(cur);
	}
	if (cur->error) {
	    /* got an error, fail the address */
	    cur->succ = *fail;
	    *fail = cur;
	} else {
	    /* link into the output queue if things went okay */
	    if (cur->next_host) {
		DEBUG3(DBG_ROUTE_LO, "    routed %s --> %s at %s\n",
		       cur->in_addr, cur->next_addr, cur->next_host);
	    } else {
		DEBUG2(DBG_ROUTE_LO,
		       "    routed %s --> %s on the local host\n",
		       cur->in_addr, cur->next_addr);
	    }
	    for (hint = cur->tphint_list; hint; hint = hint->succ) {
#ifdef USE_MX_HINTS
	      if (EQ("mx",hint->hint_name))
		{
		  mx_hint = (struct mx_transport_hint *) hint->private;
		  DEBUG3(DBG_ROUTE_LO,
			 "      transport hint %s %d %s\n", hint->hint_name,
			 mx_hint->preference,
			 mx_hint->exchanger);
		  for (a_hint = mx_hint->ipaddrs; a_hint; a_hint = a_hint->succ)
		    {
		      DEBUG2(DBG_ROUTE_LO,
			     "        address hint %s %s\n",
			     a_hint->hostname,inet_ntoa(a_hint->addr));
		    }
		}
	      else
#endif /* USE_MX_HINTS */
		DEBUG1(DBG_ROUTE_LO,
		       "      transport hint %s\n", hint->hint_name);
	    }
	    cur->succ = *out;
	    *out = cur;
	}
    }
}


/*
 * verify_remote_addrs - perform quick verify of remote addresses
 *
 * form a list of okay (verified) addrs, plus deferred (not currently
 * determinable) addrs and failed (not deliverable) addrs.
 */
void
verify_remote_addrs(in, okay, defer, fail)
    struct addr *in;			/* input remote addr list */
    struct addr **okay;			/* output list of verified addrs */
    struct addr **defer;		/* temporariliy unverifiable addrs */
    struct addr **fail;			/* unverified addrs */
{
    struct router *rp;			/* temp for stepping thru routers */
    register struct addr *cur;		/* temp for stepping through addrs */

    DEBUG(DBG_ROUTE_HI, "verify_remote_addrs called\n");

    /* first munge the addr structure as required */
    premunge_remote_addrs(in);

    /*
     * give the complete input list to each router in turn.
     * router drivers must obey the rules about overriding
     * of routes produced by previous routers.  At the end
     * this routine will cleanup the addr structures.
     */
    for (rp = routers; in && rp; rp = rp->succ) {
	struct addr *retry;		/* addrs to put through next router */
	/* look up the router driver by name */
	struct route_driver *driver = find_route_driver(rp->driver);

	if (driver == NULL) {
	    /*
	     * ERR_109 - router driver not found
	     *
	     * DESCRIPTION
	     *      A driver name was not in the table of router drivers.
	     *
	     * ACTIONS
	     *      Defer all input addresses with configuration errors.
	     *      Since it cannot be known if this router would have
	     *      overridden previous routers, all input addresses must be
	     *      deferred.
	     *
	     * RESOLUTION
	     *      The postmaster must check the router configuration
	     *      before delivery can be performed.
	     */
	    insert_addr_list(in,
			     defer,
			     note_error(ERR_CONFERR|ERR_109,
					xprintf(
					      "router %s: driver %s not found",
						rp->name,
						rp->driver)));
	    return;
	}

	/* call the driver */
	retry = NULL;
	(*driver->verify)(rp, in, &retry, okay, defer, fail);
	in = retry;
    }

    /*
     * some cleanup is required on the the addr list
     */
    for (cur = in; cur; cur = cur->succ) {
	if (cur->flags&ADDR_PUTDOT) {
	    /* a dot was removed from the end of the target, put it back */
	    cur->target[strlen(cur->target)] = '.';
	} else if (cur->flags&ADDR_MOVEDOT) {
	    /* move a dot back to the end of the target, from the front */
	    (void) strcpy(cur->target, cur->target + 1);
	    cur->target[strlen(cur->target)] = '.';
	}
    }
}


/*
 * cache_routers - call cache entrypoints for all routers
 *
 * cache information used by router drivers.  This can be called when
 * it is determined that there will be an attempt to deliver more than
 * one mail message, to increase the overall efficiency of the mailer.
 *
 * Daemons can call this periodically to recache stale data.
 */
void
cache_routers()
{
    struct router *rp;			/* temp for stepping thru routers */
    struct route_driver *driver;

    for (rp = routers; rp; rp = rp->succ) {
	driver = find_route_driver(rp->driver);
	if (driver && driver->cache) {
	    (*driver->cache)(rp);
	}
    }
    cached_routers = TRUE;
}

#ifdef notyet
/*
 * finish_routers - free resources used by all routers
 *
 * free information that was cached by routers or used by routers in
 * the process of routing.  Routers can cache data for efficiency, or
 * can maintain state between invocations.  This function is called
 * when routers will no longer be needed, allowing routers to free any
 * resources that they were using that will no longer be needed.  For
 * example, it is a good idea for routers to close any files that they
 * opened, as file descriptors are a precious resource in some
 * machines.
 */
void
finish_routers()
{
    struct router *rp;			/* temp for stepping thru routers */
    struct route_driver *driver;

    for (rp = routers; rp; rp = rp->succ) {
	driver = find_route_driver(rp->driver);
	if (driver && driver->finish) {
	    (*driver->finish)(rp);
	}
    }

    cached_routers = FALSE;
}
#endif


/*
 * premunge_remote_addrs - pre-routing munging on remote addr structures
 *
 * do a premunge on the target, by taking a dot at the end and
 * putting it at the front (if one is not already at the front).
 * Also, initialize the flags field, to remove any extraneously
 * set flags.
 */
static void
premunge_remote_addrs(list)
    struct addr *list;			/* list of remote addrs to premunge */
{
    register struct addr *cur;		/* current address being processed */

    for (cur = list; cur; cur = cur->succ) {
	register int len = strlen(cur->target);

	cur->flags &= ~(ADDR_PUTDOT |
			ADDR_MOVEDOT |
			ADDR_ERROR |
			ADDR_FINISHED |
			ADDR_FULLMATCH |
			ADDR_NOTUSER |
			ADDR_ISUSER);
	cur->match_count = -1;
	if (cur->target[len-1] == '.') {
	    if (cur->target[0] == '.') {
		cur->target[len-1] = '\0';
		cur->flags |= ADDR_PUTDOT;
	    } else {
		/* have to move the target so that a dot can be inserted */
		register char *p = cur->target;

		while (--len) {
		    p[len] = p[len - 1];
		}
		p[0] = '.';
		cur->flags |= ADDR_MOVEDOT;
	    }
	}
    }
}


/*
 * match_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 one of those domains.  If so, return a pointer to the
 * `.' that precedes the domain that matched, else return NULL.  The
 * list is scanned from left to right, with the first match returned.
 */
char *
match_end_domain(domains, target)
    char *domains;			/* colon separated list of domains */
    char *target;			/* target to test against */
{
    register char *cur;			/* current domain being checked */

    if (!domains)
	return NULL;

    for (cur = strcolon(domains); cur; cur = strcolon((char *)NULL)) {
	char *d = is_suffix(cur, target, TRUE);
	if (d && d != target) {
	    return d - 1;
	}
    }

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

/*
 * is_suffix - try to match a domain against a target
 *
 * given a domain, determine if the given target ends in that domain.
 * If so, return a pointer to the domain in the target, else NULL.
 */

char *
is_suffix(domain, target, proper_suffix)
    char *domain;			/* domain name */
    char *target;			/* target to test against */
    int proper_suffix;			/* fail if target equals domain */
{
    int skip;

    if (*domain == '.')
	domain++;
    if (*domain == '\0')
	return NULL;
    skip = strlen(target) - strlen(domain);

    if (((skip == 0 && !proper_suffix) ||
	 (skip > 0 && target[skip - 1] == '.')) &&
	EQIC(target + skip, domain))
    {
	return target + skip;
    }

    return NULL;
}

/*
 * route_driver_finish - fill in addr structures for a route driver
 *
 * route drivers may call this routine for each addr structure they
 * have computed a route to.  This routine completes the addr structure
 * if it is determined that the computed route has precedence over any
 * previously computed route.  In the case that the computed route has
 * precedence over future routers, the ADDR_FINISHED flag is set in
 * the addr.flags struct element.
 */
void
route_driver_finish(rp, addr, match_count, next_host, route, transport, hints)
    struct router *rp;			/* the calling router */
    struct addr *addr;			/* addr structure in question */
    int match_count;			/* target match count */
    char *next_host;			/* computed next_host value */
    char *route;			/* computed route value */
    struct transport *transport;	/* optional transport from driver */
    struct transport_hints *hints;	/* transport hints from driver */
{
    if (addr->flags&ADDR_FINISHED) {
	/*
	 * a router erroneously found a new route for a finished addr,
	 * ignore it
	 */
	return;
    }

    /*
     * consider this a match, if it matched more characters than
     * any earlier router
     */

    if (match_count > addr->match_count) {

	/*
	 * if the router matched the address completely, then
	 * consider the address to have been matched completely
	 */

	if (strlen(addr->target) == match_count ||
	    (addr->target[0] == '.' &&
	     strlen(addr->target) == match_count - 1))
	{
	    addr->flags |= ADDR_FINISHED|ADDR_FULLMATCH;
	} else {
	    if (next_host == NULL) {

		/*
		 * the local host cannot be matched partially, so
		 * ignore any attempts to do so
		 */

		return;
	    }
	    if (rp->flags&USE_ALWAYS) {
		addr->flags |= ADDR_FINISHED;
	    }
	}

	/*
	 * this route has precedence over previous routes
	 */

	addr->router = rp;
	addr->match_count = match_count;
	if (addr->next_host) {
	    xfree(addr->next_host);
	}
	if (next_host) {
	    addr->next_host = COPY_STRING(next_host);
	} else {
	    addr->next_host = NULL;
	}
	if (addr->route) {
	    xfree(addr->route);
	}
	if (route) {
	    addr->route = COPY_STRING(route);
	} else {
	    addr->route = NULL;
	}
	addr->transport = transport;
	addr->tphint_list = hints;
	if (addr->next_addr) {
	    /*
	     * cleanup after a previous router that computed the
	     * next_addr itself
	     */
	    xfree(addr->next_addr);
	    addr->next_addr = NULL;
	}
	if (next_host) {
	    DEBUG3(DBG_ROUTE_LO, "%s: %s matched by %s:\n",
		   addr->in_addr, addr->target, addr->router->name);
	} else {
	    DEBUG3(DBG_ROUTE_LO, "%s: reparse address %s generated by %s:\n",
		   addr->in_addr, addr->remainder, addr->router->name);
	}
    }
}

/*
 * compute_next_addr - compute the next_addr for a routed recipient address
 */
static void
compute_next_addr(addr)
    struct addr *addr;			/* address to be completed */
{
    char *r_route;

    if (addr->error) {
	/* ignore the addr if it already has a pending error condition */
	return;
    }

    if (addr->flags&ADDR_PUTDOT) {
	/* a dot was removed from the end of the target, put it back */
	addr->target[strlen(addr->target)] = '.';
    } else if (addr->flags&ADDR_MOVEDOT) {
	/* move a dot back to the end of the target, from the front */
	(void) strcpy(addr->target, addr->target+1);
	addr->target[strlen(addr->target)] = '.';
    }

    if (! (addr->flags & ADDR_FULLMATCH)) {

	/*
	 * if the target was not matched completely, append the
	 * target to the route.
	 */

	if (addr->route) {
	    r_route = xprintf("%s!%s", addr->route, addr->target);
	    xfree(addr->route);
	} else {
	    r_route = COPY_STRING(addr->target);
	}
	addr->route = r_route;
    }

    /*
     * select a method for transforming the remainder/route/next_host
     * triplet into a next_addr.  Default is method suitable for the
     * inter-smail communication, but that's about it.
     */

    if (addr->transport->flags & RT_NOXFORM) {
	if (addr->route) {
	    r_route = route_to_route_addr(addr->route);
	    addr->next_addr = xprintf("%s:%s", r_route, addr->remainder);
	} else {
	    addr->next_addr = COPY_STRING(addr->remainder);
	}
    }
    if (addr->transport->flags & UUCP_XFORM) {
	next_addr_uucp(addr);
    } else if (addr->transport->flags & INET_XFORM) {
	next_addr_basic(addr, TRUE);
    } else {
	next_addr_basic(addr, FALSE);
    }
    return;
}

/*
 * next_addr_basic - compute a next_addr for a basic transport
 *
 * This transformation leaves the address alone to the greatest extent
 * possible, and makes an effort to do transformations that work well
 * in environments with mixed-zone (sendmail and smail3) mailers.
 *
 * If the inet flag is TRUE, then the address will be left conformant
 * to RFC822 and RFC1123.  However, routes will be included in
 * route-addr notation.
 */
static void
next_addr_basic(addr, inet)
    struct addr *addr;
    int inet;
{
    char *route = addr->route;
    char *remainder = addr->remainder;
    char *target = addr->target;
    char *next_host = addr->next_host;
    char *error;
    int pflags;
    char *r_route, *r_last;
    char *p;
    int c;

    /*
     * try to rebuild the original address, or at least keep the
     * spirit of the original address form, if a route is needed.
     */

    switch (addr->flags & ADDR_FORM_MASK) {
    case RFC_ROUTE:
    case RFC_ENDROUTE:

	/*
	 * For route-addrs, if no additional route is needed for
	 * delivery to the final destination, then just use the
	 * remainder of the route; otherwise, build an RFC822-style
	 * route out of the needed additional route.
	 */

	if (route == NULL)
	    addr->next_addr = COPY_STRING(remainder);
	else {
	    r_route = route_to_route_addr(route);
	    if ((addr->flags & ADDR_FORM_MASK) == RFC_ENDROUTE)
		addr->next_addr = xprintf("%s:%s", r_route, remainder);
	    else
		addr->next_addr = xprintf("%s,%s", r_route, remainder);
	    xfree(r_route);
	}
	return;

    case MAILBOX:

	/*
	 * for mailbox (user@host) addresses, get back the original
	 * address if there is no further routing; otherwise generate
	 * a mailbox address for a one-hop route; otherwise use RFC822
	 * route-addrs for routing (inet) or !-style routes (non-inet)
	 * if possible.
	 */

	pflags = addr->parseflags;
	switch (parse_address(remainder, (char *)0, (char *)0, &pflags)) {
	case MAILBOX:

	    /*
	     * if the remainder is also a mailbox (which is actually
	     * an illegal form within a mailbox) then just send it if
	     * no further routing is needed; otherwise, make a
	     * route-addr or !-route out of it
	     */

	    if (route == NULL)
		addr->next_addr = COPY_STRING(remainder);
	    else {
		if (inet) {
		    r_route = route_to_route_addr(route);
		    addr->next_addr = xprintf("%s:%s", r_route, remainder);
		    xfree(r_route);
		} else {
		    p = build_partial_uucp_route(remainder,
				&error, addr->parseflags);
		    addr->next_addr = xprintf("%s!%s", route, p);
		}
	    }
	    return;

	case PCT_MAILBOX:
	    if (route && mixed_address(remainder)) {
		inet = TRUE;
		break;
	    }
	    break;
	}
	if (route && strchr(route, '!') == NULL) {
	    addr->next_addr = xprintf("%s@%s", remainder, route);
	} else if (route && inet) {
	    r_route = route_to_route_addr(route);
	    r_last = strrchr(r_route, ',');
	    if (r_last) {
		*r_last++ = '\0';
		r_last++;
		addr->next_addr =
		    xprintf("%s:%s@%s", r_route, remainder, r_last);
	    } else {
		addr->next_addr = xprintf("%s@%s", remainder, r_route + 1);
	    }
	    xfree(r_route);
	} else if (route) {
	    addr->next_addr = xprintf("%s!%s", route, remainder);
	} else {
	    addr->next_addr = xprintf("%s@%s", remainder, next_host);
	}
	return;

    case UUCP_ROUTE:
    case BERKENET:
    case DECNET:
	if (inet) {
	    if (route && strchr(route, '!') == NULL) {
		addr->next_addr = xprintf("%s@%s", remainder, route);
	    } else if (route) {
		r_route = route_to_route_addr(route);
		r_last = strrchr(r_route, ',');
		if (r_last) {
		    *r_last++ = '\0';
		    r_last++;
		    addr->next_addr =
			xprintf("%s:%s@%s", r_route, remainder, r_last);
		} else {
		    addr->next_addr = xprintf("%s@%s", remainder, r_route + 1);
		}
		xfree(r_route);
	    } else {
		addr->next_addr = xprintf("%s@%s", remainder, next_host);
	    }
	} else {
	    if (route)
		addr->next_addr = xprintf("%s!%s", route, remainder);
	    else
		addr->next_addr = COPY_STRING(remainder);
	}
	return;

    case PCT_MAILBOX:

	/*
	 * for user%foo addresses, keep in mind that RFC1123 may prove
	 * a problem if we need to add a route.  To manage this we do
	 * one of two things: keep the user%foo form or change it to
	 * a mailbox address of some kind.  What we do depends on the
	 * mixing of % and ! operators.  Mixed addresses and Internet
	 * conformant addresses are sent out as @route:user@foo, where
	 * the route is formed using RFC822 route syntax; otherwise,
	 * we use route!user%foo.
	 */

	if (route == NULL)
	    addr->next_addr = xprintf("%s@%s", remainder, next_host);
	else if (strchr(route, '!') == NULL) {
	    addr->next_addr = xprintf("%s@%s", remainder, route);
	} else {
	    if (mixed_address(remainder) || inet) {
		r_route = route_to_route_addr(route);
		r_last = strrchr(r_route, ',');
		if (r_last) {
		    *r_last++ = '\0';
		    r_last++;
		    addr->next_addr =
			xprintf("%s:%s@%s", r_route, remainder, r_last);
		} else {
		    addr->next_addr = xprintf("%s@%s", remainder, r_route + 1);
		}
		xfree(r_route);
	    } else {
		addr->next_addr = xprintf("%s!%s", route, remainder);
	    }
	}
	return;

    default:

	/*
	 * we should never reach here, since that is the end of the
	 * addressing forms that can be encountered here.  Log it
	 * and send as UUCP-style address.
	 */

	write_log(LOG_PANIC, "internal error: next_addr_local: unknown form: in_addr:%s target:%s remainder:%s",
		  addr->in_addr, target, remainder);

	if (route)
	    addr->next_addr = xprintf("%s!%s", route, remainder);
	else
	    addr->next_addr = COPY_STRING(remainder);
	return;
    }
}

/*
 * next_addr_uucp - compute a next_addr appropriate for uucp
 *
 * Compute the next_addr by concatenating the route and remainder
 * into a !-style address.  Any % operators in the remainder will
 * be left alone.  Other operators will be left alone.  However,
 * if the input address contained mixed-operators, including all
 * three of !-%-@, and we have to prepend a route, then remove all %
 * operators.  This last point is necessary to avoid ambiguities
 * between RFC1123 and RFC976 sites.
 */
static void
next_addr_uucp(addr)
    struct addr *addr;
{
    char *route = addr->route;
    char *remainder = addr->remainder;
    char *target = addr->target;
    char *error;
    char *r_route;

    if (route && switch_percent_and_bang &&
	addr->parseflags & FOUND_MAILBOX &&
	mixed_address(remainder))
    {
	r_route = build_uucp_route(remainder, &error, addr->parseflags);
    } else {
	r_route = build_partial_uucp_route(remainder, &error, addr->parseflags);
    }
    if (r_route == NULL) {
	/* build_partial_uucp_route failed, fail the address */
	/*
	 * ERR_108 - error building path
	 *
	 * DESCRIPTION
	 *      build_partial_uucp_route() returned an error while
	 *      attempting to build a UUCP-path from the remainder in
	 *      the address.  The specific error was returned in
	 *      `error'.
	 *
	 * ACTIONS
	 *      Notify the sender or the owner of the address of the
	 *      problem.
	 *
	 * RESOLUTION
	 *      The sender or owner should correct the supplied address
	 *      to use acceptible addressing forms.
	 */
	addr->error =
	    note_error(ERR_NSOWNER|ERR_108,
		       xprintf("error building path: %s", error));
	exitvalue = EX_DATAERR;	/* set the exit status */
	return;
    }

    /*
     * build the next_addr by appending the remainder to the route
     */

    if (route) {
	addr->next_addr = xprintf("%s!%s", route, r_route);
	xfree(r_route);
    } else {
	addr->next_addr = r_route;
    }

    return;
}

static char *
route_to_route_addr(p)
    char *p;
{
    struct str str;
    int c;

    STR_INIT(&str);
    STR_NEXT(&str, '@');
    while (c = *p++) {
	if (c == '!') {
	    STR_NEXT(&str, ',');
	    STR_NEXT(&str, '@');
	} else {
	    STR_NEXT(&str, c);
	}
    }
    STR_NEXT(&str, '\0');
    STR_DONE(&str);

    return str.p;
}

/*
 * compute_transport - compute the transport from any available defaults
 *
 * some router drivers do not assign transports themselves but rely
 * on defaults in the router structure.  These defaults can either be
 * a single default transport or a table of transports associated with
 * host names, where the special name `*' is a catchall.
 */
static void
compute_transport(addr)
    struct addr *addr;
{
    struct router *rp = addr->router;

    DEBUG2(DBG_ROUTE_HI, "compute_transport called: host=%s, router=%s\n",
	   addr->next_host, rp->name);
    /* no transport yet assigned, look around for defaults */
    if (rp->method) {
	/* there is a "method", table of possible transports */
	struct method *mp;

	mp = rp->method;
	while (mp->host) {
	    if ((EQIC(addr->next_host, mp->host) || EQ(mp->host, "*")) &&
		  (mp->mingrade < 0 ||
		   (msg_grade >= mp->mingrade && msg_grade <= mp->maxgrade)))
	    {
		/* found the right transport, use it */
		addr->transport = find_transport(mp->transport);
		if (addr->transport == NULL) {
		    /* configuration error, the transport does not exist */
		    /*
		     * ERR_102 - method transport not found
		     *
		     * DESCRIPTION
		     *      The transport to be used for the method was not
		     *      found in the table of transports.
		     *
		     * ACTIONS
		     *      Defer the message as a configuration error.
		     *
		     * RESOLUTION
		     *      The postmaster needs to correct the eror in the
		     *      method file.
		     */
		    addr->error = note_error(ERR_CONFERR|ERR_102,
					     xprintf(
			"router %s: method transport %s not found for host %s",
						     rp->name,
						     mp->transport,
						     mp->host));
		    return;
		}

		DEBUG2(DBG_ROUTE_MID, "use transport %s for %s\n",
		       addr->transport->name, addr->in_addr);
		return;
	    }
	    mp++;
	}
    }

    if (rp->default_transport) {
	/* there is a default, use it */
	addr->transport = find_transport(rp->default_transport);
	if (addr->transport == NULL) {
	    /*
	     * ERR_103 - default transport not found
	     *
	     * DESCRIPTION
	     *      The default transport for the matching router is not in the
	     *      list of transports.
	     *
	     * ACTIONS
	     *      Defer the message as a configuration error.
	     *
	     * RESOLUTION
	     *      The postmaster should correct the router file.
	     */
	    addr->error = note_error(ERR_CONFERR|ERR_103,
				     xprintf(
				   "router %s: default transport %s not found",
					     rp->name,
					     rp->default_transport));
	    return;
	}

	DEBUG2(DBG_ROUTE_MID, "use default transport %s for %s\n",
	       addr->transport->name, addr->in_addr);
	return;
    } else {
	/*
	 * ERR_110 - no transport for router
	 *
	 * DESCRIPTION
	 *      No method or default transport was given for the router that
	 *      produced this address.
	 *
	 * ACTIONS
	 *      Defer the address as a configuration error.
	 *
	 * RESOLUTION
	 *      The postmaster must check the router configuration before
	 *      delivery to the address can be performed.
	 */
	addr->error = note_error(ERR_CONFERR|ERR_110,
				 xprintf("router %s: no transport found",
					 rp->name));
    }
}


/*
 * find_router - given a router's name, return the router structure
 *
 * return NULL if no router of that name exists.
 */
struct router *
find_router(name)
    register char *name;		/* search key */
{
    register struct router *rp;		/* temp for stepping thru routers */

    /* loop through all the routers */
    for (rp = routers; rp; rp = rp->succ) {
	if (EQ(rp->name, name)) {
	    /* found the router in question */
	    return rp;
	}
    }

    return NULL;			/* router not found */
}

/*
 * find_route_driver - given a driver's name, return the driver structure
 *
 * return NULL if driver does not exist.
 */
struct route_driver *
find_route_driver(name)
    register char *name;		/* search key */
{
    register struct route_driver *rdp;	/* pointer to table of drivers */

    for (rdp = route_drivers; rdp->name; rdp++) {
	if (EQ(rdp->name, name)) {
	    return rdp;			/* found the driver */
	}
    }

    return NULL;			/* driver not found */
}


/*
 * read_router_file - read router file
 *
 * read the router file and build a router list describing the
 * entries.  Return an error message or NULL.
 */
char *
read_router_file()
{
    FILE *f;				/* open router file */
    char *error;			/* error from read_standard_file() */
    struct stat statbuf;
    static struct attr_table router_generic[] = {
	{ "driver", t_string, NULL, NULL, OFFSET(router, driver) },
	{ "transport", t_string, NULL, NULL,
	  OFFSET(router, default_transport) },
	{ "method", t_proc, NULL, (tup *)router_read_method, 0 },
	{ "always", t_boolean, NULL, NULL, USE_ALWAYS },
    };
    struct attr_table *end_router_generic = ENDTABLE(router_generic);
    static struct router router_template = {
	NULL,				/* name */
	"pathalias",			/* driver, a reasonable default */
	NULL,				/* succ will be assigned */
	0,				/* flags */
	NULL,				/* method */
	NULL,				/* default_transport */
	NULL,				/* private */
    };

    /*
     * try to open router file, note file stat if possible
     */
    if (router_file == NULL || EQ(router_file, "-")) {
	return NULL;
    }
    f = fopen(router_file, "r");
    if (f == NULL) {
	if (require_configs) {
	    return xprintf("%s: %s", router_file, strerrno());
	}

	add_config_stat(router_file, (struct stat *)NULL);
	return NULL;
    }

    (void)fstat(fileno(f), &statbuf);
    add_config_stat(router_file, &statbuf);

    /* call read_standard_file to do the real work */
    error = read_standard_file(f,
			       (char *)&router_template,
			       sizeof(struct router),
			       OFFSET(router, name),
			       OFFSET(router, flags),
			       OFFSET(router, succ),
			       router_generic,
			       end_router_generic,
			       router_driv_function,
			       (char **)&routers);

    /* finish up */
    (void) fclose(f);

    /* return any error message */
    if (error) {
	return xprintf("%s: %s", router_file, error);
    }
    return NULL;
}

/*
 * router_read_method - handle method attribute for routers
 *
 * the method attribute specifies a file which contains a method table
 * associating hosts with transports.  Use of this attribute causes
 * the table to be read and turned into an in-core method table.
 */
static char *
router_read_method(struct_p, attr)
    char *struct_p;			/* passed router structure */
    struct attribute *attr;		/* parsed attribute */
{
    struct router *rp = (struct router *)struct_p;
    char *error;

    if (attr->value == off) {
	rp->method = NULL;
    } else if (attr->value == on) {
	return xprintf("%s: boolean form for non-boolean attribute",
		       attr->name);
    } else {
	int free_method_fn = FALSE;
	char *method_fn = attr->value;

	if (method_fn[0] != '/') {
	    if (method_dir == NULL) {
		return xprintf("%s: %s not absolute and no method_dir",
			       attr->name, method_fn);
	    }
	    method_fn = xmalloc(strlen(method_dir) + strlen(method_fn) +
				sizeof("/"));
	    (void) sprintf(method_fn, "%s/%s", method_dir, attr->value);
	    free_method_fn = TRUE;
	}
	rp->method = read_method_file(method_fn, &error);

	if (rp->method == NULL) {
	    return xprintf("%s %s: %s", attr->name, attr->value, error);
	}

	if (free_method_fn) {
	    xfree(method_fn);
	}
    }

    /* everything went okay */
    return NULL;
}

/*
 * read_method_file - read a method file and return an in-core method table
 *
 * return NULL if the method file could not be opened.
 */
struct method *
read_method_file(fn, error)
    char *fn;				/* name of method file */
    char **error;			/* store error message here */
{
    FILE *f = fopen(fn, "r");
    struct method *methods;		/* method table */
    int ct = 0;				/* count of methods */
    char *entry;			/* text of file entry */
    char *mgrade;			/* restricted grades for this method */
    struct stat statbuf;

    if (f == NULL) {
	*error = xprintf("open failed: %s", strerrno());
	return NULL;
    }

    (void)fstat(fileno(f), &statbuf);
    add_config_stat(fn, &statbuf);

    /* allocate space for at least the ending record */
    methods = (struct method *)xmalloc(sizeof(*methods));

    /* loop and read all of the table entries in the method file */
    while (entry = read_entry(f)) {
	char *error;
	struct attribute *new;

	new = parse_table(entry, &error);
	if (new == NULL) {
	    fclose(f);
	    return NULL;
	}
	methods = (struct method *)
	    xrealloc((char *)methods, (ct + 2)*sizeof(*methods));
	methods[ct].host = new->name;
	methods[ct].mingrade = -1;
	methods[ct].maxgrade = -1;
	methods[ct].transport = new->value;
	mgrade = strchr(new->name, '/');
	if (mgrade != NULL) {
	    *mgrade++ = '\0';
	    if (isalnum(mgrade[0])) {
		methods[ct].mingrade = mgrade[0];
		methods[ct].maxgrade = mgrade[0];
	    }
	    if (mgrade[1] == '-') {
		if (isalnum(mgrade[2])) {
		     methods[ct].maxgrade = mgrade[2];
		} else {
		     methods[ct].maxgrade = 255;
		}
		if (methods[ct].mingrade == -1)
		     methods[ct].mingrade = 0;
	    }
	}
	ct++;
    }

    /* zero out the ending record */
    methods[ct].host = NULL;
    methods[ct].transport = NULL;

    fclose(f);
    return methods;
}

static char *
router_driv_function(struct_p, driver_attrs)
    char *struct_p;			/* passed router structure */
    struct attribute *driver_attrs;	/* driver-specific attributes */
{
    struct router *rp = (struct router *)struct_p;
    struct route_driver *drv;

    if (rp->driver == NULL) {
	return xprintf("router %s: no driver attribute", rp->name);
    }
    drv = find_route_driver(rp->driver);
    if (drv == NULL) {
	return xprintf("router %s: unknown driver: %s",
		       rp->name, rp->driver);
    }
    if (drv->builder) {
	return (*drv->builder)(rp, driver_attrs);
    }

    return NULL;
}
