/*++
/* NAME
/*	server_acl 3
/* SUMMARY
/*	server access list
/* SYNOPSIS
/*	#include <server_acl.h>
/*
/*	void	server_acl_pre_jail_init(mynetworks, param_name)
/*	const char *mynetworks;
/*	const char *param_name;
/*
/*	SERVER_ACL *server_acl_parse(extern_acl, param_name)
/*	const char *extern_acl;
/*	const char *param_name;
/*
/*	int	server_acl_eval(client_addr, intern_acl, param_name)
/*	const char *client_addr;
/*	SERVER_ACL *intern_acl;
/*	const char *param_name;
/* DESCRIPTION
/*	This module implements a permanent allow/denylist that
/*	is meant to be evaluated immediately after a client connects
/*	to a server.
/*
/*	server_acl_pre_jail_init() does before-chroot initialization
/*	for the permit_mynetworks setting.
/*
/*	server_acl_parse() converts an access list from raw string
/*	form to binary form. It should also be called as part of
/*	before-chroot initialization.
/*
/*	server_acl_eval() evaluates an access list for the specified
/*	client address.  The result is SERVER_ACL_ACT_PERMIT (permit),
/*	SERVER_ACL_ACT_REJECT (reject), SERVER_ACL_ACT_DUNNO (no
/*	decision), or SERVER_ACL_ACT_ERROR (error, unknown command
/*	or database access error).
/*
/*	Arguments:
/* .IP mynetworks
/*	Network addresses that match "permit_mynetworks".
/* .IP param_name
/*	The configuration parameter name for the access list from
/*	main.cf.  The information is used for error reporting (nested
/*	table, unknown keyword) and to select the appropriate
/*	behavior from parent_domain_matches_subdomains.
/* .IP extern_acl
/*	External access list representation.
/* .IP intern_acl
/*	Internal access list representation.
/* .IP client_addr
/*	The client IP address as printable string (without []).
/* LICENSE
/* .ad
/* .fi
/*	The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/*	Wietse Venema
/*	IBM T.J. Watson Research
/*	P.O. Box 704
/*	Yorktown Heights, NY 10598, USA
/*
/*	Wietse Venema
/*	Google, Inc.
/*	111 8th Avenue
/*	New York, NY 10011, USA
/*
/*	Wietse Venema
/*	porcupine.org
/*--*/

/* System library. */

#include <sys_defs.h>
#include <string.h>

#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif

/* Utility library. */

#include <msg.h>
#include <mymalloc.h>
#include <stringops.h>
#include <dict.h>

/* Global library. */

#include <mail_params.h>
#include <addr_match_list.h>
#include <match_parent_style.h>
#include <mynetworks.h>
#include <server_acl.h>

/* Application-specific. */

static ADDR_MATCH_LIST *server_acl_mynetworks;
static ADDR_MATCH_LIST *server_acl_mynetworks_host;

#define STR vstring_str

/* server_acl_pre_jail_init - initialize */

void    server_acl_pre_jail_init(const char *mynetworks, const char *origin)
{
    if (server_acl_mynetworks) {
	addr_match_list_free(server_acl_mynetworks);
	if (server_acl_mynetworks_host)
	    addr_match_list_free(server_acl_mynetworks_host);
    }
    server_acl_mynetworks =
	addr_match_list_init(origin, MATCH_FLAG_RETURN
			     | match_parent_style(origin), mynetworks);
    if (warn_compat_break_mynetworks_style)
	server_acl_mynetworks_host =
	    addr_match_list_init(origin, MATCH_FLAG_RETURN
			   | match_parent_style(origin), mynetworks_host());
}

/* server_acl_parse - parse access list */

SERVER_ACL *server_acl_parse(const char *extern_acl, const char *origin)
{
    char   *saved_acl = mystrdup(extern_acl);
    SERVER_ACL *intern_acl = argv_alloc(1);
    char   *bp = saved_acl;
    char   *acl;

#define STREQ(x,y) (strcasecmp((x), (y)) == 0)
#define STRNE(x,y) (strcasecmp((x), (y)) != 0)

    /*
     * Nested tables are not allowed. Tables are opened before entering the
     * chroot jail, while access lists are evaluated after entering the
     * chroot jail.
     */
    while ((acl = mystrtokq(&bp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) {
	if (strchr(acl, ':') != 0) {
	    if (strchr(origin, ':') != 0) {
		msg_warn("table %s: lookup result \"%s\" is not allowed"
			 " -- ignoring remainder of access list",
			 origin, acl);
		argv_add(intern_acl, SERVER_ACL_NAME_DUNNO, (char *) 0);
		break;
	    } else {
		acl = dict_open(acl, O_RDONLY, DICT_FLAG_LOCK
				| DICT_FLAG_FOLD_FIX
				| DICT_FLAG_UTF8_REQUEST)->reg_name;
	    }
	}
	argv_add(intern_acl, acl, (char *) 0);
    }
    argv_terminate(intern_acl);

    /*
     * Cleanup.
     */
    myfree(saved_acl);
    return (intern_acl);
}

/* server_acl_eval - evaluate access list */

int     server_acl_eval(const char *client_addr, SERVER_ACL * intern_acl,
			        const char *origin)
{
    const char *myname = "server_acl_eval";
    char  **cpp;
    DICT   *dict;
    SERVER_ACL *argv;
    const char *acl;
    const char *dict_val;
    int     ret;

    for (cpp = intern_acl->argv; (acl = *cpp) != 0; cpp++) {
	if (msg_verbose)
	    msg_info("source=%s address=%s acl=%s",
		     origin, client_addr, acl);
	if (STREQ(acl, SERVER_ACL_NAME_REJECT)) {
	    return (SERVER_ACL_ACT_REJECT);
	} else if (STREQ(acl, SERVER_ACL_NAME_PERMIT)) {
	    return (SERVER_ACL_ACT_PERMIT);
	} else if (STREQ(acl, SERVER_ACL_NAME_WL_MYNETWORKS)) {
	    if (addr_match_list_match(server_acl_mynetworks, client_addr)) {
		if (warn_compat_break_mynetworks_style
		    && !addr_match_list_match(server_acl_mynetworks_host,
					      client_addr))
		    msg_info("using backwards-compatible default setting "
			     VAR_MYNETWORKS_STYLE "=%s to permit "
			     "request from client \"%s\"",
			     var_mynetworks_style, client_addr);
		return (SERVER_ACL_ACT_PERMIT);
	    }
	    if (server_acl_mynetworks->error != 0) {
		msg_warn("%s: %s: mynetworks lookup error -- ignoring the "
			 "remainder of this access list", origin, acl);
		return (SERVER_ACL_ACT_ERROR);
	    }
	} else if (strchr(acl, ':') != 0) {
	    if ((dict = dict_handle(acl)) == 0)
		msg_panic("%s: unexpected dictionary: %s", myname, acl);
	    if ((dict_val = dict_get(dict, client_addr)) != 0) {
		/* Fake up an ARGV to avoid lots of mallocs and frees. */
		if (dict_val[strcspn(dict_val, ":" CHARS_COMMA_SP)] == 0) {
		    ARGV_FAKE_BEGIN(fake_argv, dict_val);
		    ret = server_acl_eval(client_addr, &fake_argv, acl);
		    ARGV_FAKE_END;
		} else {
		    argv = server_acl_parse(dict_val, acl);
		    ret = server_acl_eval(client_addr, argv, acl);
		    argv_free(argv);
		}
		if (ret != SERVER_ACL_ACT_DUNNO)
		    return (ret);
	    } else if (dict->error != 0) {
		msg_warn("%s: %s:%s: table lookup error -- ignoring the "
			 "remainder of this access list", origin, dict->type,
			 dict->name);
		return (SERVER_ACL_ACT_ERROR);
	    }
	} else if (STREQ(acl, SERVER_ACL_NAME_DUNNO)) {
	    return (SERVER_ACL_ACT_DUNNO);
	} else {
	    msg_warn("%s: unknown command: %s -- ignoring the remainder "
		     "of this access list", origin, acl);
	    return (SERVER_ACL_ACT_ERROR);
	}
    }
    if (msg_verbose)
	msg_info("source=%s address=%s - no match",
		 origin, client_addr);
    return (SERVER_ACL_ACT_DUNNO);
}

 /*
  * Access lists need testing. Not only with good inputs; error cases must
  * also be handled appropriately.
  */
#ifdef TEST
#include <unistd.h>
#include <stdlib.h>
#include <vstring_vstream.h>
#include <name_code.h>
#include <split_at.h>

char   *var_server_acl = "";

#define UPDATE_VAR(s,v) do { if (*(s)) myfree(s); (s) = mystrdup(v); } while (0)

int     main(void)
{
    VSTRING *buf = vstring_alloc(100);
    SERVER_ACL *argv;
    int     ret;
    int     have_tty = isatty(0);
    char   *bufp;
    char   *cmd;
    char   *value;
    const NAME_CODE acl_map[] = {
	SERVER_ACL_NAME_ERROR, SERVER_ACL_ACT_ERROR,
	SERVER_ACL_NAME_PERMIT, SERVER_ACL_ACT_PERMIT,
	SERVER_ACL_NAME_REJECT, SERVER_ACL_ACT_REJECT,
	SERVER_ACL_NAME_DUNNO, SERVER_ACL_ACT_DUNNO,
	0,
    };

    /*
     * No static initializer because these are owned by a library.
     */
    var_par_dom_match = DEF_PAR_DOM_MATCH;
    var_mynetworks = "";

#define VAR_SERVER_ACL "server_acl"

    while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
	bufp = STR(buf);
	if (have_tty == 0) {
	    vstream_printf("> %s\n", bufp);
	    vstream_fflush(VSTREAM_OUT);
	}
	if (*bufp == '#')
	    continue;
	if ((cmd = mystrtok(&bufp, " =")) == 0 || STREQ(cmd, "?")) {
	    vstream_printf("usage: %s=value|%s=value|address=value\n",
			   VAR_MYNETWORKS, VAR_SERVER_ACL);
	} else if ((value = mystrtok(&bufp, " =")) == 0) {
	    vstream_printf("missing value\n");
	} else if (STREQ(cmd, VAR_MYNETWORKS)) {
	    UPDATE_VAR(var_mynetworks, value);
	} else if (STREQ(cmd, VAR_SERVER_ACL)) {
	    UPDATE_VAR(var_server_acl, value);
	} else if (STREQ(cmd, "address")) {
	    server_acl_pre_jail_init(var_mynetworks, VAR_MYNETWORKS);
	    argv = server_acl_parse(var_server_acl, VAR_SERVER_ACL);
	    ret = server_acl_eval(value, argv, VAR_SERVER_ACL);
	    argv_free(argv);
	    vstream_printf("%s: %s\n", value, str_name_code(acl_map, ret));
	} else {
	    vstream_printf("unknown command: \"%s\"\n", cmd);
	}
	vstream_fflush(VSTREAM_OUT);
    }
    vstring_free(buf);
    exit(0);
}

#endif
