/*
#ident	"@(#)smail/src:RELEASE-3_2_0_103:verify.c,v 1.21 1998/09/27 00:51:32 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.
 */

/*
 * verify.c:
 *	address verification functions and user interfaces for address
 *	verification and expansion.
 *
 *	external functions: verify_addr_list
 */
#include <sys/types.h>
#include <stdio.h>
#include <ctype.h>
#include "defs.h"
#include "smail.h"
#include "dys.h"
#include "addr.h"
#include "direct.h"
#include "route.h"
#include "exitcodes.h"
#include "lookup.h"
#ifndef DEPEND
# include "extern.h"
# include "debug.h"
# include "error.h"
#endif

static void illegal_relay_error( /* struct addr *cur, char *sender_host_addr */ );


/*
 * verify_addr_list - verify deliverability to a set of addresses
 *
 * call upon the verify entry points to the director and router drivers
 * to perform simple verification for a list of addresses.
 *
 * For directors, verification is only one level deep; i.e., if any
 * director matches a local address, the address verifies true.
 *
 * An error structure is stored in the error fields for entries in the
 * defer and fail output lists.  Only work_addr, target and remainder are
 * set in the okay output list.  If it is desired that work_addr be
 * restored back to the original value, it is the callers responsibility
 * to arrange this.
 *
 * The first router that finds a match for a target wins, as we do not
 * need to actually determine who has the best match, we only need to know
 * if a match exists.
 */
void
verify_addr_list(in, okay, defer, fail)
    struct addr *in;			/* input addr list */
    struct addr **okay;			/* output list of verified addrs */
    struct addr **defer;		/* temporariliy unverifiable addrs */
    struct addr **fail;			/* unverified addrs */
{
    register struct addr *cur;		/* current address being processed */
    struct addr *next;			/* next address to process */
    struct addr *local;			/* addrs that parsed local */
    struct addr *remote;		/* addrs that parsed remote */

    DEBUG(DBG_ADDR_HI, "verify_addr_list called.\n");

    /*
     * form the local and remote lists, and put parse errors into fail.
     */
    remote = NULL;
    local = NULL;
    for (cur = in; cur; cur = next) {
	int form;			/* form from parse_address() */

	next = cur->succ;

	/* get the form, plus the target and remainder */
	form = parse_address(cur->work_addr, &cur->target, &cur->remainder,
			     &cur->parseflags);

	/* split into lists based on the form */
	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,
				    xprintf("address parse error: %s",
					    cur->remainder));
	    cur->flags |= PARSE_ERROR;
	    cur->succ = *fail;
	    *fail = cur;
	    break;

	case LOCAL:
	    /*
	     * a local form in quotes must be reparsed,
	     * otherwise put it on the local addr list.
	     */
	    if (strip(cur->remainder)) {
		/* it was in quotes, put it back on the input */
		DEBUG1(DBG_ADDR_MID, "verify_addr_list() will reparse quoted local form: %s.\n",
		      cur->remainder);
		cur->work_addr = cur->remainder;
		next = cur;
	    } else {
		if (!cur->local_name) {
                    cur->local_name = visible_name;
		}
		cur->succ = local;
		local = cur;
	    }
	    break;

	default:			/* remote forms covered by smtp_remote_allow */
	    /*
	     * a remote form may point to the local host, if so
	     * put it back on the input, otherwise it goes on the
	     * remote list.
	     */
	    if (islocalhost(cur->target)) {
		/* it is the local host, save name & parse it again */
		DEBUG2(DBG_ADDR_MID, "verify_addr_list() will reparse '%s' for local host %s.\n",
		      cur->remainder, cur->target);
                cur->local_name = copy(cur->target);
		cur->work_addr = cur->remainder;
		next = cur;
	    } else if (!in->parent && sender_host_addr && smtp_remote_allow &&
		       !match_ip(sender_host_addr, smtp_remote_allow)) {
#ifndef HAVE_BIND
		/* XXX for now just don't allow any remote relay */
		illegal_relay_error(cur, sender_host_addr);
		cur->succ = *fail;
		*fail = cur;
#else
		int mxresult;
		int local_precedence;

		switch ((mxresult = bind_check_if_local_mxs(cur, &local_precedence))) {
		case DB_SUCCEED:
		    DEBUG1(DBG_ADDR_MID, "bind_check_if_local_mxs() found MXs for %s.", cur->in_addr);
		    if (local_precedence >= 0) {
			char *dmmytg = NULL;
			char *dmmyrem = NULL;
			int dmmyflg = 0;

			/* it's OK -- we MX for them, unless... */
			if (parse_address(cur->remainder, &dmmytg, &dmmyrem, &dmmyflg) != LOCAL) {
			    illegal_relay_error(cur, sender_host_addr);
			    cur->succ = *fail;
			    *fail = cur;
			} else {
			    cur->succ = remote;
			    remote = cur;
			}
		    } else {
			illegal_relay_error(cur, sender_host_addr);
			cur->succ = *fail;
			*fail = cur;
		    }
		    break;
		case DB_AGAIN:		/* DNS lookup must be deferred */
		case FILE_AGAIN:	/* lost contact with server, etc. */
		case FILE_NOMATCH:	/* could not connect to server */
		    DEBUG1(DBG_ADDR_LO, "bind_check_if_local_mxs() DNS comms problem when checking for MXs for %s.", cur->in_addr);
		    if (!cur->error) {
			char *error_text;

			error_text = xprintf("defer checking for MXs for %s",
					     cur->in_addr);
			cur->error = note_error(ERR_DONTLOG | ERR_163, error_text);
		    }
		    cur->succ = *defer;
		    *defer = cur;
		    break;
		case DB_NOMATCH:	/* no such domain */
		case DB_FAIL:		/* bad DNS request? */
		    DEBUG1(DBG_ADDR_LO, "bind_check_if_local_mxs() NXDOMAIN or bad request when checking for MXs for %s.", cur->in_addr);
		    if (!cur->error) {
			char *error_text;

			error_text = xprintf("%s is not a valid domain",
					     cur->in_addr);
			cur->error = note_error(ERR_NPOWNER | ERR_164, error_text);
		    }	
		    cur->succ = *fail;
		    *fail = cur;
		    break;
		case FILE_FAIL:		/* major DNS error! */
		    DEBUG1(DBG_ADDR_LO, "bind_check_if_local_mxs() major DNS error when checking for MXs for %s.", cur->in_addr);
		    if (!cur->error) {
			char *error_text;

			error_text = xprintf("error checking for MXs for %s",
					     cur->in_addr);
			cur->error = note_error(ERR_NPOWNER | ERR_165, error_text);
		    }
		    cur->succ = *fail;
		    *fail = cur;
		    break;
		default:
		    DEBUG2(DBG_ADDR_LO, "bind_check_if_local_mxs() impossible result %d when checking for MXs for %s.", mxresult, cur->in_addr);
		    if (!cur->error) {
			char *error_text;

			error_text = xprintf("fatal internal error %d checking for MXs for %s.",
					     mxresult, cur->in_addr);
			cur->error = note_error(ERR_NPOWNER | ERR_165, error_text);
		    }
		    cur->succ = *fail;
		    *fail = cur;
		    break;
		}
#endif /* HAVE_BIND */
	    } else {
		DEBUG2(smtp_remote_allow ? DBG_ADDR_HI : DBG_ADDR_LO,
		       "permitting remote relay by [%s] to %s.\n",
		       sender_host_addr ? sender_host_addr : "UNKNOWN", cur->in_addr);
		cur->succ = remote;
		remote = cur;
	    }
	    break;
	}
    }

    /* if there are any local addrs, run them through the directors */
    if (local) {
	verify_local_addrs(local, okay, defer, fail);
    }

    /* if there are any remote addrs, run them through the routers */
    if (remote) {
	verify_remote_addrs(remote, okay, defer, fail);
    }
}


/*
 * match_ip - match an ip against an allowed list
 *
 * Takes a list of IP:IP:IP and compares against an IP string.  Allows
 * "*" star type wildcards at the end of IPs.
 *
 * WARNING: cannot diddle with pattern -- may be read-only string.
 *
 * Returns 1 if matched, else 0.
 */
int
match_ip(ip, pattern)
    char *ip;			/* ip address we are matching */
    char *pattern;		/* pattern we are using to match it */
{
    char	*pat_p, *end_p, wild = 0;
    int		len;
    
    /* run through the pattern list */
    for (pat_p = pattern; /* NOTEST */ ; pat_p = end_p + 1, wild = 0) {

	/* skip any spaces at front */
	while (*pat_p && isspace(*pat_p))
	       pat_p++;
	
	/* find the end of the next pattern */
	end_p = pat_p;
	while (*end_p && *end_p != ':' && !isspace(*pat_p))
	       end_p++;
	
	/* skip empty patterns */
	len = end_p - pat_p;
	if (len > 0) {
	    /* do we have a wild-card at end of pattern? */
	    if (*(end_p - 1) == '*') {
		len--;			/* may be set to zero if pattern is "*" */
		wild = 1;
	    }
	    if (smtp_local_net && len > 0 && strncmpic("localnet", pat_p, len) == 0) {
		/* our pattern will be the string in smtp_local_net */
		len = strlen(smtp_local_net);
		if (smtp_local_net[len - 1] == '*') {
		    len--;
		    wild = 1;
		}
		/* have match if pat was wild or we checked the whole IP */
		if (len && strncmp(ip, smtp_local_net, len) == 0 && (wild || ip[len] == '\0')) {
		    DEBUG3(DBG_ADDR_HI,
			   "ip '%s' verified by localnet '%s', up to len %d\n",
			   ip, smtp_local_net, len);
		    return 1;
		}
	    } else {
		/* We have a match if pat was wild or we checked the whole IP.
		 * NOTE:  len may be zero for pattern of just "*" which will
		 * mean the first term of the expression will always be true,
		 * which is what we want in this case.
		 */
		if (strncmp(ip, pat_p, len) == 0 && (wild || ip[len] == '\0')) {
		    DEBUG3(DBG_ADDR_HI,
			   "ip '%s' verified by sub-pattern of '%s', up to len %d\n",
			   ip, pat_p, len);
		    return 1;
		}
	    }
	}
	if (*end_p == '\0')
	    break;
    }
    
    DEBUG2(DBG_ADDR_HI, "ip '%s' not verified by pattern '%s'\n",
	   ip, pattern);
    
    return 0;
}


static void
illegal_relay_error(cur)
    struct addr *cur;
{
    char *error_text;

    /*
     * ERR_104 - security violation
     *
     * DESCRIPTION
     *	The incoming SMTP connection is not from a
     *	local network, and is attempting to send mail
     *	to another remote domain.
     *
     * ACTIONS
     *      The address verification is failed.
     *
     * RESOLUTION
     *      The postmaster should verify that addresses of
     *      all valid local networks are listed properly in
     *      the smtp_remote_allow variable.
     */
    error_text = xprintf("security violation: remote address not permitted");
    cur->error = note_error(ERR_NPOSTMAST | ERR_104, error_text);
}
