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