/*
#ident	"@(#)smail/src:RELEASE-3_2_0_114:resolve.c,v 1.27 2000/10/02 02:06:41 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>
#define NEED_SOCKETS			/* grumble */
#include "defs.h"
#include "smail.h"
#include "main.h"
#include "parse.h"
#include "addr.h"
#include "hash.h"
#include "direct.h"
#include "route.h"
#include "transport.h"
#include "alloc.h"
#include "dys.h"
#include "log.h"
#include "exitcodes.h"
#include "extern.h"
#include "debug.h"
#include "error.h"

#ifndef INADDR_NONE
# define INADDR_NONE		((unsigned long) (-1)) /* XXX 64bit too??? */
#endif
#ifndef INADDR_LOOPBACK
# define INADDR_LOOPBACK	((unsigned long) 0x7F000001)
#endif

/* exported variables */
struct hash_table *hit_table = NULL;	/* table to recognize address hits */
struct block *hit_table_block = NULL;	/* block() for bmalloc() et al */


/*
 * 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
 *
 *	hash_addrs - set to TRUE to ignore duplicate addresses
 *
 * 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;

    DEBUG1(DBG_RESOLVE_HI, "resolve_addr_list(): called [will%s check for dups]\n", hash_addrs ? "" : " NOT");

    /*
     * 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 (cur->flags & ADDR_DONTHASH) {
		DEBUG1(DBG_RESOLVE_MID, "%s: not checking for dups because of ADDR_DONTHASH.\n", cur->work_addr);
	    } else if (hash_addrs && 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 &= ~ADDR_FORM_MASK;
		cur->succ = *fail;
		*fail = cur;
		continue;

	    case LOCAL:
		(void) strip(cur->remainder); /* strip any quotes, etc. */
		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;
	}
    }
    if (hash_addrs) {
	struct addr *prev;
	struct hash_table *hit_out_table; /* table to recognize address hits */
	struct block *hit_out_table_block; /* block for bmalloc() et al */
	char *hashval;

	hit_out_table_block = malloc_block();
	hit_out_table = new_hash_table(hit_table_len,
				       hit_out_table_block,
				       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 during rescan.\n", hashval);
		/* we always get around the loop at least once without entering
		 * this side of the 'if', so 'prev' will never be NULL
		 */
		prev->succ = next;
		cur->succ = NULL;
		/* XXX should probably garbage collect the duplicate */
	    } else {
		prev = cur;
	    }
	    xfree(hashval);
	}
	/* free everything */
	free_block(hit_out_table_block);
    }

    return;
}

/*
 * islocalhost - determine if the given target is the local host
 *
 * Given the currently known names for the localhost, determine if the given
 * name or any name resolved from an IP literal matches one of these known
 * names.
 *
 * return TRUE or FALSE.
 *
 * WARNING: an address which looks like an IP literal (i.e. is surrounded in
 * "[]"), but which the inet library claims is not valid will be treated as a
 * non-local host.  This should be OK because we test IP literals last.
 */
int
islocalhost(target)
    register char *target;		/* name to match */
{
#ifdef HAVE_BSD_NETWORKING
    int len;
    char *hostip;
    struct in_addr inet_s;
    struct hostent *hostentp;
#endif /* HAVE_BSD_NETWORKING */

    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;
    }
#ifdef HAVE_BSD_NETWORKING
    /* 
     * Do a (recursive) check if this looks like a [xxx.xxx.xxx.xxx] address
     * and if so whether or not it appears to be on a local interface.
     */
    len = strlen(target);
    if (target[0] == '[' && target[len-1] == ']') {
        hostip = COPY_STRING(target+1); /* make a copy so we don't have to modify target. */
	len = strlen(hostip);
	hostip[len-1] = '\0';
	if (!inet_aton(hostip, &inet_s)) {
	    xfree(hostip);
	    return FALSE;		/* not a valid IP number.... */
	}
	xfree(hostip);
	if (inet_s.s_addr == htonl(INADDR_LOOPBACK)) {
	    return TRUE;		/* obviously us.... */
	}
	/*
	 * Do a reverse query to get the hostname.  This is a bit bogus as it
	 * requires that the lookup work for the check to function properly.
	 */
	if ((hostentp = gethostbyaddr((char *) &(inet_s.s_addr), sizeof(inet_s.s_addr), AF_INET))) {
	    if (islocalhost(hostentp->h_name)) {
	        return TRUE;
	    } else {
	        char *p;
		int i;
		
		for (i = 0; (p = (hostentp->h_aliases)[i]); i++) {
		    if (islocalhost(p)) {
		        return TRUE;
		    }
		}
	    }
	}
    }
#endif /* HAVE_BSD_NETWORKING */
        
    return FALSE;
}
