Home | History | Annotate | Line # | Download | only in global
      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