/*
#ident	"@(#)smail/src:RELEASE-3_2_0_103:resolve.c,v 1.13 1998/08/02 17:41:35 woods 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.
 */

/*
 * resolve.c:
 *	resolve addresses to completed addr structures with transports.
 *
 *	external functions: resolve_addr_list, islocalhost
 */
#include <sys/types.h>
#include <stdio.h>
#include "defs.h"
#include "smail.h"
#include "dys.h"
#include "hash.h"
#include "log.h"
#include "addr.h"
#include "route.h"
#include "transport.h"
#include "exitcodes.h"
#ifndef DEPEND
# include "extern.h"
# include "debug.h"
# include "error.h"
#endif

/* exported variables */
struct hash_table *hit_table;		/* table to recognize address hits */


/*
 * resolve_addr_list - resolve addresses to transports and next hosts
 *
 * given a list of user-supplied addresses on input, produce resolved
 * and unresolvable addresses on output.
 *
 * inputs:
 *	in	- the list of input address structures
 *
 * outputs:
 *	out	- the list of completely resolved address structures.
 *		  transport, next_host and next_addr will be properly
 *		  filled in for all of these structures.
 *	defer	- a list of temporarily unresolvable address structures.
 *		  an error structure is stored in the error element.
 *		  These addresses should be retried at a later time.  If
 *		  ERR_CONFERR is set in the error->info element, the
 *		  problem is a configuration error.
 *	fail	- a list of unresolvable address structures.  An error
 *		  structure is stored in the error element.  If
 *		  ERR_NSENDER is set, a note is returned to the sender.
 *		  If ERR_NPOSTMASTER is set, then a note is mailed to
 *		  the postmaster.  If ERR_NSOWNER is set then a note is
 *		  sent to an owner for an address, or to the sender if
 *		  the address has no owner.  If ERR_NPOWNER is set then
 *		  a note is sent to an owner or to the postmaster if the
 *		  address has no owner.
 */
void
resolve_addr_list(in, out, defer, fail, hash_addrs)
    struct addr *in;			/* the address list to resolve */
    struct addr **out;			/* produced addr list w/transports */
    struct addr **defer;		/* addrs to defer to a later time */
    struct addr **fail;			/* unresolvable addrs */
    int hash_addrs;			/* TRUE to prevent duplicate addrs */
{
    struct addr *cur;			/* current address being processed */
    struct addr *local;			/* addrs that parsed local */
    struct addr *remote;		/* addrs that parsed remote */
    struct addr *next;			/* next value for cur */

    remote = NULL;
    local = NULL;

    DEBUG(DBG_RESOLVE_HI, "resolve_addr_list called\n");

    /*
     * Resolve all addresses in our input queue.
     *
     * Each step through the loop advances the progress
     * in resolving all addresses to a transport.
     *
     * The loop is done when nothing remains to be processed.
     *
     * As an optimization, remote form processing is not
     * done unless/until no local processing was required.
     */
    while (in || remote) {
	/*
	 * split the input list into local and remote forms.
	 */
	for (cur = in, in = NULL; cur; cur = next) {
	    int form;			/* address form from parse_address() */

	    DEBUG1(DBG_RESOLVE_HI, "resolve_addr_list working on %s.\n", cur->work_addr);
	    next = cur->succ;

	    /* First, if allowed, we try adding the current address to the
	     * master hash table to ensure we don't do extra work checking
	     * obviously identical addresses.  Note that since we don't
	     * re-parse anything that looks anything like a remote address we
	     * don't protect from multiple deliveries to different addresses
	     * that really do resolve to the same adress.  We leave final
	     * duplicate stripping to a final scan of the out list.
	     */
	    if (hash_addrs &&
		!(cur->flags & ADDR_DONTHASH) &&
		add_to_hash(cur->work_addr, (char *)NULL, 0, hit_table) == ALREADY_HASHED) {
		DEBUG1(DBG_RESOLVE_LO, "%s: has already been seen -- skipping.\n", cur->work_addr);
		continue;
	    }

	    form = parse_address(cur->work_addr, &cur->target,
				 &cur->remainder, &cur->parseflags);
	    switch (form) {
	    case FAIL:
	    case PARSE_ERROR:
		/*
		 * ERR_111 - address parse error
		 *
		 * DESCRIPTION
		 *      parse_address() encountered an error while parsing
		 *      the work_addr for this address.  The error is stored
		 *      in cur->remainder.
		 *
		 * ACTIONS
		 *      A message about the parse error should be returned
		 *      to the owner of the address or to the sender.
		 *
		 * RESOLUTION
		 *      The owner or sender should correct the address and
		 *      resubmit the message.
		 */
		cur->error = note_error(ERR_NSOWNER|ERR_111, cur->remainder);
		cur->flags |= PARSE_ERROR;
		cur->succ = *fail;
		*fail = cur;
		continue;

	    case LOCAL:
		/*
		 * a local-form in quotes must be reparsed
		 */
		if (strip(cur->remainder)) {
		    /* it was in quotes, put it back on the input */
		    DEBUG1(DBG_RESOLVE_HI, "re-parsing quoted local form %s.\n", cur->remainder);
		    next = cur;
		} else {
		    if (!cur->local_name)
			cur->local_name = visible_name;
		    cur->succ = local;
		    local = cur;
		    DEBUG1(DBG_RESOLVE_HI, "moved local form %s to be directed.\n", cur->remainder);
		}
		break;

	    default:			/* anything else is a remote-form address */
		/* determine if the target host is actually a local host */
		if (islocalhost(cur->target)) {
		    DEBUG1(DBG_RESOLVE_HI, "target %s was actually a local host, will reparse remainder.\n", cur->target);
		    /* remember the local name */
		    cur->local_name = COPY_STRING(cur->target);/* XXX will leak? */
		    /* it is a local host, but save the name for virtual host processing */
		    cur->work_addr = COPY_STRING(cur->remainder);/* XXX will leak? */
		    next = cur;		/* erni: must parse again, remainder could be remote */
		    continue;
		}
		cur->flags &= ~(ADDR_FORM_MASK);
		cur->flags |= form;
		cur->succ = remote;
		remote = cur;
		DEBUG1(DBG_RESOLVE_HI, "moved target %s to be routed.\n", cur->target);
		break;
	    }
	}

	/*
	 * either process local or remote addresses.
	 */
	if (local) {
	    direct_local_addrs(local, out, &in, defer, fail);
	    local = NULL;
	} else {
	    route_remote_addrs(remote, out, &in, defer, fail);
	    remote = NULL;
	}
    }
    /* XXX we could probably garbage-collect hit_table at this point */
    if (hash_addrs) {
	struct addr *prev;
	struct hash_table *hit_out_table; /* table to recognize address hits */
	char *hashval;

	/* XXX - hit_out_table should be associated with a block */
	hit_out_table = new_hash_table(hit_table_len,
				       (struct block *) NULL,
				       HASH_DEFAULT);
	/*
	 * Re-scan the out list for duplicates.  Note that a duplicate is
	 * one that has the same destination *and* the same transport.
	 */
	for (cur = *out, prev = NULL; cur; cur = next) {
	    next = cur->succ;
	    hashval = xprintf("%s at %s via %s",
			      cur->next_addr,
			      cur->next_host ? cur->next_host : "(localhost)",
			      cur->transport->name);
	    if (add_to_hash(hashval, (char *) NULL, 0, hit_out_table) == ALREADY_HASHED) {
		DEBUG1(DBG_RESOLVE_LO, "%s: has already been seen -- skipping.\n", hashval);
		prev->succ = next;
		cur->succ = NULL;
		/* XXX should probably garbage collect the duplicate */
	    } else {
		prev = cur;
	    }
	    xfree(hashval);
	}
    }
}

/*
 * islocalhost - determine if the given target is the local host
 *
 * given the currently known names for the localhost, determine
 * if the given name matches one of these known names.
 *
 * return TRUE or FALSE.
 */
int
islocalhost(target)
    register char *target;		/* name to match */
{
    if ((uucp_name && EQIC(target, uucp_name)) ||
	(hostnames && is_string_in_list(target, hostnames)) ||
	(more_hostnames && is_string_in_list(target, more_hostnames)))
    {
	return TRUE;
    }
    return FALSE;
}
