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