1/*++
2/* NAME
3/*	server_acl 3
4/* SUMMARY
5/*	server access list
6/* SYNOPSIS
7/*	#include <server_acl.h>
8/*
9/*	void	server_acl_pre_jail_init(mynetworks, param_name)
10/*	const char *mynetworks;
11/*	const char *param_name;
12/*
13/*	SERVER_ACL *server_acl_parse(extern_acl, param_name)
14/*	const char *extern_acl;
15/*	const char *param_name;
16/*
17/*	int	server_acl_eval(client_addr, intern_acl, param_name)
18/*	const char *client_addr;
19/*	SERVER_ACL *intern_acl;
20/*	const char *param_name;
21/* DESCRIPTION
22/*	This module implements a permanent black/whitelist that
23/*	is meant to be evaluated immediately after a client connects
24/*	to a server.
25/*
26/*	server_acl_pre_jail_init() does before-chroot initialization
27/*	for the permit_mynetworks setting.
28/*
29/*	server_acl_parse() converts an access list from raw string
30/*	form to binary form. It should also be called as part of
31/*	before-chroot initialization.
32/*
33/*	server_acl_eval() evaluates an access list for the specified
34/*	client address.  The result is SERVER_ACL_ACT_PERMIT (permit),
35/*	SERVER_ACL_ACT_REJECT (reject), SERVER_ACL_ACT_DUNNO (no
36/*	decision), or SERVER_ACL_ACT_ERROR (error, unknown command
37/*	or database access error).
38/*
39/*	Arguments:
40/* .IP mynetworks
41/*	Network addresses that match "permit_mynetworks".
42/* .IP param_name
43/*	The configuration parameter name for the access list from
44/*	main.cf.  The information is used for error reporting (nested
45/*	table, unknown keyword) and to select the appropriate
46/*	behavior from parent_domain_matches_subdomains.
47/* .IP extern_acl
48/*	External access list representation.
49/* .IP intern_acl
50/*	Internal access list representation.
51/* .IP client_addr
52/*	The client IP address as printable string (without []).
53/* LICENSE
54/* .ad
55/* .fi
56/*	The Secure Mailer license must be distributed with this software.
57/* AUTHOR(S)
58/*	Wietse Venema
59/*	IBM T.J. Watson Research
60/*	P.O. Box 704
61/*	Yorktown Heights, NY 10598, USA
62/*--*/
63
64/* System library. */
65
66#include <sys_defs.h>
67#include <string.h>
68
69#ifdef STRCASECMP_IN_STRINGS_H
70#include <strings.h>
71#endif
72
73/* Utility library. */
74
75#include <msg.h>
76#include <mymalloc.h>
77#include <stringops.h>
78#include <dict.h>
79
80/* Global library. */
81
82#include <mail_params.h>
83#include <addr_match_list.h>
84#include <match_parent_style.h>
85#include <server_acl.h>
86
87/* Application-specific. */
88
89#define SERVER_ACL_SEPARATORS	", \t\r\n"
90
91static ADDR_MATCH_LIST *server_acl_mynetworks;
92
93#define STR vstring_str
94
95/* server_acl_pre_jail_init - initialize */
96
97void    server_acl_pre_jail_init(const char *mynetworks, const char *origin)
98{
99    if (server_acl_mynetworks)
100	addr_match_list_free(server_acl_mynetworks);
101    server_acl_mynetworks =
102	addr_match_list_init(MATCH_FLAG_RETURN | match_parent_style(origin),
103			     mynetworks);
104}
105
106/* server_acl_parse - parse access list */
107
108SERVER_ACL *server_acl_parse(const char *extern_acl, const char *origin)
109{
110    char   *saved_acl = mystrdup(extern_acl);
111    SERVER_ACL *intern_acl = argv_alloc(1);
112    char   *bp = saved_acl;
113    char   *acl;
114
115#define STREQ(x,y) (strcasecmp((x), (y)) == 0)
116#define STRNE(x,y) (strcasecmp((x), (y)) != 0)
117
118    /*
119     * Nested tables are not allowed. Tables are opened before entering the
120     * chroot jail, while access lists are evaluated after entering the
121     * chroot jail.
122     */
123    while ((acl = mystrtok(&bp, SERVER_ACL_SEPARATORS)) != 0) {
124	if (strchr(acl, ':') != 0) {
125	    if (strchr(origin, ':') != 0) {
126		msg_warn("table %s: lookup result \"%s\" is not allowed"
127			 " -- ignoring remainder of access list",
128			 origin, acl);
129		argv_add(intern_acl, SERVER_ACL_NAME_DUNNO, (char *) 0);
130		break;
131	    } else {
132		if (dict_handle(acl) == 0)
133		    dict_register(acl, dict_open(acl, O_RDONLY, DICT_FLAG_LOCK
134						 | DICT_FLAG_FOLD_FIX));
135	    }
136	}
137	argv_add(intern_acl, acl, (char *) 0);
138    }
139    argv_terminate(intern_acl);
140
141    /*
142     * Cleanup.
143     */
144    myfree(saved_acl);
145    return (intern_acl);
146}
147
148/* server_acl_eval - evaluate access list */
149
150int     server_acl_eval(const char *client_addr, SERVER_ACL * intern_acl,
151			        const char *origin)
152{
153    const char *myname = "server_acl_eval";
154    char  **cpp;
155    DICT   *dict;
156    SERVER_ACL *argv;
157    const char *acl;
158    const char *dict_val;
159    int     ret;
160
161    for (cpp = intern_acl->argv; (acl = *cpp) != 0; cpp++) {
162	if (msg_verbose)
163	    msg_info("source=%s address=%s acl=%s",
164		     origin, client_addr, acl);
165	if (STREQ(acl, SERVER_ACL_NAME_REJECT)) {
166	    return (SERVER_ACL_ACT_REJECT);
167	} else if (STREQ(acl, SERVER_ACL_NAME_PERMIT)) {
168	    return (SERVER_ACL_ACT_PERMIT);
169	} else if (STREQ(acl, SERVER_ACL_NAME_WL_MYNETWORKS)) {
170	    if (addr_match_list_match(server_acl_mynetworks, client_addr))
171		return (SERVER_ACL_ACT_PERMIT);
172	    if (server_acl_mynetworks->error != 0) {
173		msg_warn("%s: %s: mynetworks lookup error -- ignoring the "
174			 "remainder of this access list", origin, acl);
175		return (SERVER_ACL_ACT_ERROR);
176	    }
177	} else if (strchr(acl, ':') != 0) {
178	    if ((dict = dict_handle(acl)) == 0)
179		msg_panic("%s: unexpected dictionary: %s", myname, acl);
180	    if ((dict_val = dict_get(dict, client_addr)) != 0) {
181		/* Fake up an ARGV to avoid lots of mallocs and frees. */
182		if (dict_val[strcspn(dict_val, ":" SERVER_ACL_SEPARATORS)] == 0) {
183		    ARGV_FAKE_BEGIN(fake_argv, dict_val);
184		    ret = server_acl_eval(client_addr, &fake_argv, acl);
185		    ARGV_FAKE_END;
186		} else {
187		    argv = server_acl_parse(dict_val, acl);
188		    ret = server_acl_eval(client_addr, argv, acl);
189		    argv_free(argv);
190		}
191		if (ret != SERVER_ACL_ACT_DUNNO)
192		    return (ret);
193	    } else if (dict->error != 0) {
194		msg_warn("%s: %s: table lookup error -- ignoring the remainder "
195			 "of this access list", origin, acl);
196		return (SERVER_ACL_ACT_ERROR);
197	    }
198	} else if (STREQ(acl, SERVER_ACL_NAME_DUNNO)) {
199	    return (SERVER_ACL_ACT_DUNNO);
200	} else {
201	    msg_warn("%s: unknown command: %s -- ignoring the remainder "
202		     "of this access list", origin, acl);
203	    return (SERVER_ACL_ACT_ERROR);
204	}
205    }
206    if (msg_verbose)
207	msg_info("source=%s address=%s - no match",
208		 origin, client_addr);
209    return (SERVER_ACL_ACT_DUNNO);
210}
211
212 /*
213  * Access lists need testing. Not only with good inputs; error cases must
214  * also be handled appropriately.
215  */
216#ifdef TEST
217#include <unistd.h>
218#include <stdlib.h>
219#include <vstring_vstream.h>
220#include <name_code.h>
221#include <split_at.h>
222
223char   *var_par_dom_match = DEF_PAR_DOM_MATCH;
224char   *var_mynetworks = "";
225char   *var_server_acl = "";
226
227#define UPDATE_VAR(s,v) do { if (*(s)) myfree(s); (s) = mystrdup(v); } while (0)
228
229int     main(void)
230{
231    VSTRING *buf = vstring_alloc(100);
232    SERVER_ACL *argv;
233    int     ret;
234    int     have_tty = isatty(0);
235    char   *bufp;
236    char   *cmd;
237    char   *value;
238    const NAME_CODE acl_map[] = {
239	SERVER_ACL_NAME_ERROR, SERVER_ACL_ACT_ERROR,
240	SERVER_ACL_NAME_PERMIT, SERVER_ACL_ACT_PERMIT,
241	SERVER_ACL_NAME_REJECT, SERVER_ACL_ACT_REJECT,
242	SERVER_ACL_NAME_DUNNO, SERVER_ACL_ACT_DUNNO,
243	0,
244    };
245
246#define VAR_SERVER_ACL "server_acl"
247
248    while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
249	bufp = STR(buf);
250	if (have_tty == 0) {
251	    vstream_printf("> %s\n", bufp);
252	    vstream_fflush(VSTREAM_OUT);
253	}
254	if (*bufp == '#')
255	    continue;
256	if ((cmd = mystrtok(&bufp, " =")) == 0 || STREQ(cmd, "?")) {
257	    vstream_printf("usage: %s=value|%s=value|address=value\n",
258			   VAR_MYNETWORKS, VAR_SERVER_ACL);
259	} else if ((value = mystrtok(&bufp, " =")) == 0) {
260	    vstream_printf("missing value\n");
261	} else if (STREQ(cmd, VAR_MYNETWORKS)) {
262	    UPDATE_VAR(var_mynetworks, value);
263	} else if (STREQ(cmd, VAR_SERVER_ACL)) {
264	    UPDATE_VAR(var_server_acl, value);
265	} else if (STREQ(cmd, "address")) {
266	    server_acl_pre_jail_init(var_mynetworks, VAR_SERVER_ACL);
267	    argv = server_acl_parse(var_server_acl, VAR_SERVER_ACL);
268	    ret = server_acl_eval(value, argv, VAR_SERVER_ACL);
269	    argv_free(argv);
270	    vstream_printf("%s: %s\n", value, str_name_code(acl_map, ret));
271	} else {
272	    vstream_printf("unknown command: \"%s\"\n", cmd);
273	}
274	vstream_fflush(VSTREAM_OUT);
275    }
276    vstring_free(buf);
277    exit(0);
278}
279
280#endif
281