summaryrefslogtreecommitdiffstats
path: root/src/global/server_acl.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/global/server_acl.c')
-rw-r--r--src/global/server_acl.c305
1 files changed, 305 insertions, 0 deletions
diff --git a/src/global/server_acl.c b/src/global/server_acl.c
new file mode 100644
index 0000000..5385a5a
--- /dev/null
+++ b/src/global/server_acl.c
@@ -0,0 +1,305 @@
+/*++
+/* NAME
+/* server_acl 3
+/* SUMMARY
+/* server access list
+/* SYNOPSIS
+/* #include <server_acl.h>
+/*
+/* void server_acl_pre_jail_init(mynetworks, param_name)
+/* const char *mynetworks;
+/* const char *param_name;
+/*
+/* SERVER_ACL *server_acl_parse(extern_acl, param_name)
+/* const char *extern_acl;
+/* const char *param_name;
+/*
+/* int server_acl_eval(client_addr, intern_acl, param_name)
+/* const char *client_addr;
+/* SERVER_ACL *intern_acl;
+/* const char *param_name;
+/* DESCRIPTION
+/* This module implements a permanent allow/denylist that
+/* is meant to be evaluated immediately after a client connects
+/* to a server.
+/*
+/* server_acl_pre_jail_init() does before-chroot initialization
+/* for the permit_mynetworks setting.
+/*
+/* server_acl_parse() converts an access list from raw string
+/* form to binary form. It should also be called as part of
+/* before-chroot initialization.
+/*
+/* server_acl_eval() evaluates an access list for the specified
+/* client address. The result is SERVER_ACL_ACT_PERMIT (permit),
+/* SERVER_ACL_ACT_REJECT (reject), SERVER_ACL_ACT_DUNNO (no
+/* decision), or SERVER_ACL_ACT_ERROR (error, unknown command
+/* or database access error).
+/*
+/* Arguments:
+/* .IP mynetworks
+/* Network addresses that match "permit_mynetworks".
+/* .IP param_name
+/* The configuration parameter name for the access list from
+/* main.cf. The information is used for error reporting (nested
+/* table, unknown keyword) and to select the appropriate
+/* behavior from parent_domain_matches_subdomains.
+/* .IP extern_acl
+/* External access list representation.
+/* .IP intern_acl
+/* Internal access list representation.
+/* .IP client_addr
+/* The client IP address as printable string (without []).
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <dict.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <addr_match_list.h>
+#include <match_parent_style.h>
+#include <mynetworks.h>
+#include <server_acl.h>
+
+/* Application-specific. */
+
+static ADDR_MATCH_LIST *server_acl_mynetworks;
+static ADDR_MATCH_LIST *server_acl_mynetworks_host;
+
+#define STR vstring_str
+
+/* server_acl_pre_jail_init - initialize */
+
+void server_acl_pre_jail_init(const char *mynetworks, const char *origin)
+{
+ if (server_acl_mynetworks) {
+ addr_match_list_free(server_acl_mynetworks);
+ if (server_acl_mynetworks_host)
+ addr_match_list_free(server_acl_mynetworks_host);
+ }
+ server_acl_mynetworks =
+ addr_match_list_init(origin, MATCH_FLAG_RETURN
+ | match_parent_style(origin), mynetworks);
+ if (warn_compat_break_mynetworks_style)
+ server_acl_mynetworks_host =
+ addr_match_list_init(origin, MATCH_FLAG_RETURN
+ | match_parent_style(origin), mynetworks_host());
+}
+
+/* server_acl_parse - parse access list */
+
+SERVER_ACL *server_acl_parse(const char *extern_acl, const char *origin)
+{
+ char *saved_acl = mystrdup(extern_acl);
+ SERVER_ACL *intern_acl = argv_alloc(1);
+ char *bp = saved_acl;
+ char *acl;
+
+#define STREQ(x,y) (strcasecmp((x), (y)) == 0)
+#define STRNE(x,y) (strcasecmp((x), (y)) != 0)
+
+ /*
+ * Nested tables are not allowed. Tables are opened before entering the
+ * chroot jail, while access lists are evaluated after entering the
+ * chroot jail.
+ */
+ while ((acl = mystrtokq(&bp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) {
+ if (strchr(acl, ':') != 0) {
+ if (strchr(origin, ':') != 0) {
+ msg_warn("table %s: lookup result \"%s\" is not allowed"
+ " -- ignoring remainder of access list",
+ origin, acl);
+ argv_add(intern_acl, SERVER_ACL_NAME_DUNNO, (char *) 0);
+ break;
+ } else {
+ if (dict_handle(acl) == 0)
+ dict_register(acl, dict_open(acl, O_RDONLY, DICT_FLAG_LOCK
+ | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST));
+ }
+ }
+ argv_add(intern_acl, acl, (char *) 0);
+ }
+ argv_terminate(intern_acl);
+
+ /*
+ * Cleanup.
+ */
+ myfree(saved_acl);
+ return (intern_acl);
+}
+
+/* server_acl_eval - evaluate access list */
+
+int server_acl_eval(const char *client_addr, SERVER_ACL * intern_acl,
+ const char *origin)
+{
+ const char *myname = "server_acl_eval";
+ char **cpp;
+ DICT *dict;
+ SERVER_ACL *argv;
+ const char *acl;
+ const char *dict_val;
+ int ret;
+
+ for (cpp = intern_acl->argv; (acl = *cpp) != 0; cpp++) {
+ if (msg_verbose)
+ msg_info("source=%s address=%s acl=%s",
+ origin, client_addr, acl);
+ if (STREQ(acl, SERVER_ACL_NAME_REJECT)) {
+ return (SERVER_ACL_ACT_REJECT);
+ } else if (STREQ(acl, SERVER_ACL_NAME_PERMIT)) {
+ return (SERVER_ACL_ACT_PERMIT);
+ } else if (STREQ(acl, SERVER_ACL_NAME_WL_MYNETWORKS)) {
+ if (addr_match_list_match(server_acl_mynetworks, client_addr)) {
+ if (warn_compat_break_mynetworks_style
+ && !addr_match_list_match(server_acl_mynetworks_host,
+ client_addr))
+ msg_info("using backwards-compatible default setting "
+ VAR_MYNETWORKS_STYLE "=%s to permit "
+ "request from client \"%s\"",
+ var_mynetworks_style, client_addr);
+ return (SERVER_ACL_ACT_PERMIT);
+ }
+ if (server_acl_mynetworks->error != 0) {
+ msg_warn("%s: %s: mynetworks lookup error -- ignoring the "
+ "remainder of this access list", origin, acl);
+ return (SERVER_ACL_ACT_ERROR);
+ }
+ } else if (strchr(acl, ':') != 0) {
+ if ((dict = dict_handle(acl)) == 0)
+ msg_panic("%s: unexpected dictionary: %s", myname, acl);
+ if ((dict_val = dict_get(dict, client_addr)) != 0) {
+ /* Fake up an ARGV to avoid lots of mallocs and frees. */
+ if (dict_val[strcspn(dict_val, ":" CHARS_COMMA_SP)] == 0) {
+ ARGV_FAKE_BEGIN(fake_argv, dict_val);
+ ret = server_acl_eval(client_addr, &fake_argv, acl);
+ ARGV_FAKE_END;
+ } else {
+ argv = server_acl_parse(dict_val, acl);
+ ret = server_acl_eval(client_addr, argv, acl);
+ argv_free(argv);
+ }
+ if (ret != SERVER_ACL_ACT_DUNNO)
+ return (ret);
+ } else if (dict->error != 0) {
+ msg_warn("%s: %s: table lookup error -- ignoring the remainder "
+ "of this access list", origin, acl);
+ return (SERVER_ACL_ACT_ERROR);
+ }
+ } else if (STREQ(acl, SERVER_ACL_NAME_DUNNO)) {
+ return (SERVER_ACL_ACT_DUNNO);
+ } else {
+ msg_warn("%s: unknown command: %s -- ignoring the remainder "
+ "of this access list", origin, acl);
+ return (SERVER_ACL_ACT_ERROR);
+ }
+ }
+ if (msg_verbose)
+ msg_info("source=%s address=%s - no match",
+ origin, client_addr);
+ return (SERVER_ACL_ACT_DUNNO);
+}
+
+ /*
+ * Access lists need testing. Not only with good inputs; error cases must
+ * also be handled appropriately.
+ */
+#ifdef TEST
+#include <unistd.h>
+#include <stdlib.h>
+#include <vstring_vstream.h>
+#include <name_code.h>
+#include <split_at.h>
+
+char *var_server_acl = "";
+
+#define UPDATE_VAR(s,v) do { if (*(s)) myfree(s); (s) = mystrdup(v); } while (0)
+
+int main(void)
+{
+ VSTRING *buf = vstring_alloc(100);
+ SERVER_ACL *argv;
+ int ret;
+ int have_tty = isatty(0);
+ char *bufp;
+ char *cmd;
+ char *value;
+ const NAME_CODE acl_map[] = {
+ SERVER_ACL_NAME_ERROR, SERVER_ACL_ACT_ERROR,
+ SERVER_ACL_NAME_PERMIT, SERVER_ACL_ACT_PERMIT,
+ SERVER_ACL_NAME_REJECT, SERVER_ACL_ACT_REJECT,
+ SERVER_ACL_NAME_DUNNO, SERVER_ACL_ACT_DUNNO,
+ 0,
+ };
+
+ /*
+ * No static initializer because these are owned by a library.
+ */
+ var_par_dom_match = DEF_PAR_DOM_MATCH;
+ var_mynetworks = "";
+
+#define VAR_SERVER_ACL "server_acl"
+
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
+ bufp = STR(buf);
+ if (have_tty == 0) {
+ vstream_printf("> %s\n", bufp);
+ vstream_fflush(VSTREAM_OUT);
+ }
+ if (*bufp == '#')
+ continue;
+ if ((cmd = mystrtok(&bufp, " =")) == 0 || STREQ(cmd, "?")) {
+ vstream_printf("usage: %s=value|%s=value|address=value\n",
+ VAR_MYNETWORKS, VAR_SERVER_ACL);
+ } else if ((value = mystrtok(&bufp, " =")) == 0) {
+ vstream_printf("missing value\n");
+ } else if (STREQ(cmd, VAR_MYNETWORKS)) {
+ UPDATE_VAR(var_mynetworks, value);
+ } else if (STREQ(cmd, VAR_SERVER_ACL)) {
+ UPDATE_VAR(var_server_acl, value);
+ } else if (STREQ(cmd, "address")) {
+ server_acl_pre_jail_init(var_mynetworks, VAR_MYNETWORKS);
+ argv = server_acl_parse(var_server_acl, VAR_SERVER_ACL);
+ ret = server_acl_eval(value, argv, VAR_SERVER_ACL);
+ argv_free(argv);
+ vstream_printf("%s: %s\n", value, str_name_code(acl_map, ret));
+ } else {
+ vstream_printf("unknown command: \"%s\"\n", cmd);
+ }
+ vstream_fflush(VSTREAM_OUT);
+ }
+ vstring_free(buf);
+ exit(0);
+}
+
+#endif