xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/server_acl.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /*	$NetBSD: server_acl.c,v 1.2 2017/02/14 01:16: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 black/whitelist 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 
66 /* System library. */
67 
68 #include <sys_defs.h>
69 #include <string.h>
70 
71 #ifdef STRCASECMP_IN_STRINGS_H
72 #include <strings.h>
73 #endif
74 
75 /* Utility library. */
76 
77 #include <msg.h>
78 #include <mymalloc.h>
79 #include <stringops.h>
80 #include <dict.h>
81 
82 /* Global library. */
83 
84 #include <mail_params.h>
85 #include <addr_match_list.h>
86 #include <match_parent_style.h>
87 #include <mynetworks.h>
88 #include <server_acl.h>
89 
90 /* Application-specific. */
91 
92 static ADDR_MATCH_LIST *server_acl_mynetworks;
93 static ADDR_MATCH_LIST *server_acl_mynetworks_host;
94 
95 #define STR vstring_str
96 
97 /* server_acl_pre_jail_init - initialize */
98 
99 void    server_acl_pre_jail_init(const char *mynetworks, const char *origin)
100 {
101     if (server_acl_mynetworks) {
102 	addr_match_list_free(server_acl_mynetworks);
103 	if (server_acl_mynetworks_host)
104 	    addr_match_list_free(server_acl_mynetworks_host);
105     }
106     server_acl_mynetworks =
107 	addr_match_list_init(origin, MATCH_FLAG_RETURN
108 			     | match_parent_style(origin), mynetworks);
109     if (warn_compat_break_mynetworks_style)
110 	server_acl_mynetworks_host =
111 	    addr_match_list_init(origin, MATCH_FLAG_RETURN
112 				 | match_parent_style(origin), mynetworks_host());
113 }
114 
115 /* server_acl_parse - parse access list */
116 
117 SERVER_ACL *server_acl_parse(const char *extern_acl, const char *origin)
118 {
119     char   *saved_acl = mystrdup(extern_acl);
120     SERVER_ACL *intern_acl = argv_alloc(1);
121     char   *bp = saved_acl;
122     char   *acl;
123 
124 #define STREQ(x,y) (strcasecmp((x), (y)) == 0)
125 #define STRNE(x,y) (strcasecmp((x), (y)) != 0)
126 
127     /*
128      * Nested tables are not allowed. Tables are opened before entering the
129      * chroot jail, while access lists are evaluated after entering the
130      * chroot jail.
131      */
132     while ((acl = mystrtokq(&bp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) {
133 	if (strchr(acl, ':') != 0) {
134 	    if (strchr(origin, ':') != 0) {
135 		msg_warn("table %s: lookup result \"%s\" is not allowed"
136 			 " -- ignoring remainder of access list",
137 			 origin, acl);
138 		argv_add(intern_acl, SERVER_ACL_NAME_DUNNO, (char *) 0);
139 		break;
140 	    } else {
141 		if (dict_handle(acl) == 0)
142 		    dict_register(acl, dict_open(acl, O_RDONLY, DICT_FLAG_LOCK
143 						 | DICT_FLAG_FOLD_FIX
144 						 | DICT_FLAG_UTF8_REQUEST));
145 	    }
146 	}
147 	argv_add(intern_acl, acl, (char *) 0);
148     }
149     argv_terminate(intern_acl);
150 
151     /*
152      * Cleanup.
153      */
154     myfree(saved_acl);
155     return (intern_acl);
156 }
157 
158 /* server_acl_eval - evaluate access list */
159 
160 int     server_acl_eval(const char *client_addr, SERVER_ACL * intern_acl,
161 			        const char *origin)
162 {
163     const char *myname = "server_acl_eval";
164     char  **cpp;
165     DICT   *dict;
166     SERVER_ACL *argv;
167     const char *acl;
168     const char *dict_val;
169     int     ret;
170 
171     for (cpp = intern_acl->argv; (acl = *cpp) != 0; cpp++) {
172 	if (msg_verbose)
173 	    msg_info("source=%s address=%s acl=%s",
174 		     origin, client_addr, acl);
175 	if (STREQ(acl, SERVER_ACL_NAME_REJECT)) {
176 	    return (SERVER_ACL_ACT_REJECT);
177 	} else if (STREQ(acl, SERVER_ACL_NAME_PERMIT)) {
178 	    return (SERVER_ACL_ACT_PERMIT);
179 	} else if (STREQ(acl, SERVER_ACL_NAME_WL_MYNETWORKS)) {
180 	    if (addr_match_list_match(server_acl_mynetworks, client_addr)) {
181 		if (warn_compat_break_mynetworks_style
182 		    && !addr_match_list_match(server_acl_mynetworks_host,
183 					      client_addr))
184 		    msg_info("using backwards-compatible default setting "
185 			     VAR_MYNETWORKS_STYLE "=%s to permit "
186 			     "request from client \"%s\"",
187 			     var_mynetworks_style, client_addr);
188 		return (SERVER_ACL_ACT_PERMIT);
189 	    }
190 	    if (server_acl_mynetworks->error != 0) {
191 		msg_warn("%s: %s: mynetworks lookup error -- ignoring the "
192 			 "remainder of this access list", origin, acl);
193 		return (SERVER_ACL_ACT_ERROR);
194 	    }
195 	} else if (strchr(acl, ':') != 0) {
196 	    if ((dict = dict_handle(acl)) == 0)
197 		msg_panic("%s: unexpected dictionary: %s", myname, acl);
198 	    if ((dict_val = dict_get(dict, client_addr)) != 0) {
199 		/* Fake up an ARGV to avoid lots of mallocs and frees. */
200 		if (dict_val[strcspn(dict_val, ":" CHARS_COMMA_SP)] == 0) {
201 		    ARGV_FAKE_BEGIN(fake_argv, dict_val);
202 		    ret = server_acl_eval(client_addr, &fake_argv, acl);
203 		    ARGV_FAKE_END;
204 		} else {
205 		    argv = server_acl_parse(dict_val, acl);
206 		    ret = server_acl_eval(client_addr, argv, acl);
207 		    argv_free(argv);
208 		}
209 		if (ret != SERVER_ACL_ACT_DUNNO)
210 		    return (ret);
211 	    } else if (dict->error != 0) {
212 		msg_warn("%s: %s: table lookup error -- ignoring the remainder "
213 			 "of this access list", origin, acl);
214 		return (SERVER_ACL_ACT_ERROR);
215 	    }
216 	} else if (STREQ(acl, SERVER_ACL_NAME_DUNNO)) {
217 	    return (SERVER_ACL_ACT_DUNNO);
218 	} else {
219 	    msg_warn("%s: unknown command: %s -- ignoring the remainder "
220 		     "of this access list", origin, acl);
221 	    return (SERVER_ACL_ACT_ERROR);
222 	}
223     }
224     if (msg_verbose)
225 	msg_info("source=%s address=%s - no match",
226 		 origin, client_addr);
227     return (SERVER_ACL_ACT_DUNNO);
228 }
229 
230  /*
231   * Access lists need testing. Not only with good inputs; error cases must
232   * also be handled appropriately.
233   */
234 #ifdef TEST
235 #include <unistd.h>
236 #include <stdlib.h>
237 #include <vstring_vstream.h>
238 #include <name_code.h>
239 #include <split_at.h>
240 
241 char   *var_par_dom_match = DEF_PAR_DOM_MATCH;
242 char   *var_mynetworks = "";
243 char   *var_server_acl = "";
244 
245 #define UPDATE_VAR(s,v) do { if (*(s)) myfree(s); (s) = mystrdup(v); } while (0)
246 
247 int     main(void)
248 {
249     VSTRING *buf = vstring_alloc(100);
250     SERVER_ACL *argv;
251     int     ret;
252     int     have_tty = isatty(0);
253     char   *bufp;
254     char   *cmd;
255     char   *value;
256     const NAME_CODE acl_map[] = {
257 	SERVER_ACL_NAME_ERROR, SERVER_ACL_ACT_ERROR,
258 	SERVER_ACL_NAME_PERMIT, SERVER_ACL_ACT_PERMIT,
259 	SERVER_ACL_NAME_REJECT, SERVER_ACL_ACT_REJECT,
260 	SERVER_ACL_NAME_DUNNO, SERVER_ACL_ACT_DUNNO,
261 	0,
262     };
263 
264 #define VAR_SERVER_ACL "server_acl"
265 
266     while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
267 	bufp = STR(buf);
268 	if (have_tty == 0) {
269 	    vstream_printf("> %s\n", bufp);
270 	    vstream_fflush(VSTREAM_OUT);
271 	}
272 	if (*bufp == '#')
273 	    continue;
274 	if ((cmd = mystrtok(&bufp, " =")) == 0 || STREQ(cmd, "?")) {
275 	    vstream_printf("usage: %s=value|%s=value|address=value\n",
276 			   VAR_MYNETWORKS, VAR_SERVER_ACL);
277 	} else if ((value = mystrtok(&bufp, " =")) == 0) {
278 	    vstream_printf("missing value\n");
279 	} else if (STREQ(cmd, VAR_MYNETWORKS)) {
280 	    UPDATE_VAR(var_mynetworks, value);
281 	} else if (STREQ(cmd, VAR_SERVER_ACL)) {
282 	    UPDATE_VAR(var_server_acl, value);
283 	} else if (STREQ(cmd, "address")) {
284 	    server_acl_pre_jail_init(var_mynetworks, VAR_MYNETWORKS);
285 	    argv = server_acl_parse(var_server_acl, VAR_SERVER_ACL);
286 	    ret = server_acl_eval(value, argv, VAR_SERVER_ACL);
287 	    argv_free(argv);
288 	    vstream_printf("%s: %s\n", value, str_name_code(acl_map, ret));
289 	} else {
290 	    vstream_printf("unknown command: \"%s\"\n", cmd);
291 	}
292 	vstream_fflush(VSTREAM_OUT);
293     }
294     vstring_free(buf);
295     exit(0);
296 }
297 
298 #endif
299