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