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