From 3edd14965519d5db2441fd6cdcc6d7af0673f812 Mon Sep 17 00:00:00 2001
From: Daniel Salzman <daniel.salzman@nic.cz>
Date: Thu, 14 May 2015 09:14:09 +0200
Subject: [PATCH] conf: allow multivalued acl.address and acl.key, separate
 deny

---
 doc/configuration.rst          |  23 +++---
 doc/man/knot.conf.5in          |  26 +++---
 doc/reference.rst              |  29 ++++---
 src/knot/conf/scheme.c         |  18 ++---
 src/knot/conf/scheme.h         |   1 +
 src/knot/nameserver/axfr.c     |   2 +-
 src/knot/nameserver/internet.c |   2 +-
 src/knot/nameserver/ixfr.c     |   2 +-
 src/knot/nameserver/notify.c   |   2 +-
 src/knot/nameserver/update.c   |   4 +-
 src/knot/updates/acl.c         |  90 +++++++++++----------
 src/knot/updates/acl.h         |   8 +-
 tests/Makefile.am              |   1 +
 tests/acl.c                    | 142 ++++++++++++++++++++++++++++++++-
 14 files changed, 255 insertions(+), 95 deletions(-)

diff --git a/doc/configuration.rst b/doc/configuration.rst
index 59bdaca6d8..58de4b5b74 100644
--- a/doc/configuration.rst
+++ b/doc/configuration.rst
@@ -82,21 +82,18 @@ Access control list (ACL)
 =========================
 
 ACL list specifies which remotes are allowed to send the server a specific
-query or do a specific action. A remote can be a single IP address or a
-network subnet. Also a TSIG key can be specified::
+request. A remote can be a single IP address or a network subnet. Also a TSIG
+key can be specified::
 
     acl:
-      - id: single_rule
-        address: 192.168.1.1      # Single IP address
-        action: [notify, update]  # Allow zone notifications and updates zone
+      - id: address_rule
+        address: [2001:db8::1, 192.168.2.0/24] # Allowed IP address list
+        action: [transfer, update]  # Allow zone transfers and updates
 
-      - id: subnet_rule
-        address: 192.168.2.0/24   # Network subnet
-        action: transfer          # Allow zone transfers
-
-      - id: deny_rule
-        address: 192.168.2.100    # Negative match
-        action: deny              # The remote query is denied
+      - id: deny_rule             # Negative match rule
+        address: 192.168.2.100
+        action: transfer
+        deny: on                  # The request is denied
 
       - id: key_rule
         key: key1                 # Access based just on TSIG key
@@ -106,7 +103,7 @@ These rules can then be referenced from a zone :ref:`template_acl`::
 
     zone:
       - domain: example.com
-        acl: [single_rule, deny_rule, subnet_rule, key_rule]
+        acl: [address_rule, deny_rule, key_rule]
 
 Slave zone
 ==========
diff --git a/doc/man/knot.conf.5in b/doc/man/knot.conf.5in
index 6983e602d9..24ec2b49dc 100644
--- a/doc/man/knot.conf.5in
+++ b/doc/man/knot.conf.5in
@@ -297,7 +297,7 @@ Shared key secret.
 Default: empty
 .SH ACL SECTION
 .sp
-Access control list rules definition.
+Access control list rule definition.
 .INDENT 0.0
 .INDENT 3.5
 .sp
@@ -305,9 +305,10 @@ Access control list rules definition.
 .ft C
 acl:
   \- id: STR
-    address: ADDR[/INT]
-    key: key_id
-    action: deny | transfer | notify | update ...
+    address: ADDR[/INT] ...
+    key: key_id ...
+    action: transfer | notify | update ...
+    deny: BOOL
 .ft P
 .fi
 .UNINDENT
@@ -317,13 +318,14 @@ acl:
 An ACL rule identifier.
 .SS address
 .sp
-A single IP address or network subnet with the given prefix the query
-must match.
+An ordered list of IP addresses or network subnets. The query must match
+one of them. Empty value means that address match is not required.
 .sp
 Default: empty
 .SS key
 .sp
-A \fI\%reference\fP to the TSIG key the query must match.
+An ordered list of \fI\%reference\fPs to TSIG keys. The query must
+match one of them. Empty value means that TSIG key is not required.
 .sp
 Default: empty
 .SS action
@@ -333,8 +335,6 @@ An ordered list of allowed actions.
 Possible values:
 .INDENT 0.0
 .IP \(bu 2
-\fBdeny\fP \- Block the matching query
-.IP \(bu 2
 \fBtransfer\fP \- Allow zone transfer
 .IP \(bu 2
 \fBnotify\fP \- Allow incoming notify
@@ -342,7 +342,13 @@ Possible values:
 \fBupdate\fP \- Allow zone updates
 .UNINDENT
 .sp
-Default: deny
+Default: empty
+.SS deny
+.sp
+Deny if \fI\%address\fP, \fI\%key\fP and
+\fI\%action\fP match.
+.sp
+Default: off
 .SH CONTROL SECTION
 .sp
 Configuration of the server remote control.
diff --git a/doc/reference.rst b/doc/reference.rst
index 434576edf4..73cbed13da 100644
--- a/doc/reference.rst
+++ b/doc/reference.rst
@@ -351,15 +351,16 @@ Default: empty
 ACL section
 ===========
 
-Access control list rules definition.
+Access control list rule definition.
 
 ::
 
  acl:
    - id: STR
-     address: ADDR[/INT]
-     key: key_id
-     action: deny | transfer | notify | update ...
+     address: ADDR[/INT] ...
+     key: key_id ...
+     action: transfer | notify | update ...
+     deny: BOOL
 
 .. _acl_id:
 
@@ -373,8 +374,8 @@ An ACL rule identifier.
 address
 -------
 
-A single IP address or network subnet with the given prefix the query
-must match.
+An ordered list of IP addresses or network subnets. The query must match
+one of them. Empty value means that address match is not required.
 
 Default: empty
 
@@ -383,7 +384,8 @@ Default: empty
 key
 ---
 
-A :ref:`reference<key_id>` to the TSIG key the query must match.
+An ordered list of :ref:`reference<key_id>`\ s to TSIG keys. The query must
+match one of them. Empty value means that TSIG key is not required.
 
 Default: empty
 
@@ -396,12 +398,21 @@ An ordered list of allowed actions.
 
 Possible values:
 
-- ``deny`` - Block the matching query
 - ``transfer`` - Allow zone transfer
 - ``notify`` - Allow incoming notify
 - ``update`` - Allow zone updates
 
-Default: deny
+Default: empty
+
+.. _acl_deny:
+
+deny
+----
+
+Deny if :ref:`address<acl_address>`, :ref:`key<acl_key>` and
+:ref:`action<acl_action>` match.
+
+Default: off
 
 .. _Control section:
 
diff --git a/src/knot/conf/scheme.c b/src/knot/conf/scheme.c
index 119f99448a..9cf82a4925 100644
--- a/src/knot/conf/scheme.c
+++ b/src/knot/conf/scheme.c
@@ -48,10 +48,9 @@ static const lookup_table_t key_algs[] = {
 };
 
 static const lookup_table_t acl_actions[] = {
-	{ ACL_ACTION_DENY, "deny" },
-	{ ACL_ACTION_XFER, "transfer" },
-	{ ACL_ACTION_NOTF, "notify" },
-	{ ACL_ACTION_DDNS, "update" },
+	{ ACL_ACTION_NOTIFY,   "notify" },
+	{ ACL_ACTION_TRANSFER, "transfer" },
+	{ ACL_ACTION_UPDATE,   "update" },
 	{ 0, NULL }
 };
 
@@ -107,11 +106,12 @@ static const yp_item_t desc_key[] = {
 };
 
 static const yp_item_t desc_acl[] = {
-	{ C_ID,      YP_TSTR, YP_VNONE },
-	{ C_ADDR,    YP_TNET, YP_VNONE },
-	{ C_KEY,     YP_TREF, YP_VREF = { C_KEY }, YP_FNONE, { check_ref } },
-	{ C_ACTION,  YP_TOPT, YP_VOPT = { acl_actions, ACL_ACTION_DENY }, YP_FMULTI },
-	{ C_COMMENT, YP_TSTR, YP_VNONE },
+	{ C_ID,      YP_TSTR,  YP_VNONE },
+	{ C_ADDR,    YP_TNET,  YP_VNONE, YP_FMULTI },
+	{ C_KEY,     YP_TREF,  YP_VREF = { C_KEY }, YP_FMULTI, { check_ref } },
+	{ C_ACTION,  YP_TOPT,  YP_VOPT = { acl_actions, ACL_ACTION_NONE }, YP_FMULTI },
+	{ C_DENY,    YP_TBOOL, YP_VNONE },
+	{ C_COMMENT, YP_TSTR,  YP_VNONE },
 	{ NULL }
 };
 
diff --git a/src/knot/conf/scheme.h b/src/knot/conf/scheme.h
index 939ba5b170..d086e17d0f 100644
--- a/src/knot/conf/scheme.h
+++ b/src/knot/conf/scheme.h
@@ -36,6 +36,7 @@
 #define C_BG_WORKERS		"\x12""background-workers"
 #define C_COMMENT		"\x07""comment"
 #define C_CTL			"\x07""control"
+#define C_DENY			"\x04""deny"
 #define C_DISABLE_ANY		"\x0B""disable-any"
 #define C_DOMAIN		"\x06""domain"
 #define C_DNSSEC_ENABLE		"\x0D""dnssec-enable"
diff --git a/src/knot/nameserver/axfr.c b/src/knot/nameserver/axfr.c
index dcf59a66a1..28b6b02743 100644
--- a/src/knot/nameserver/axfr.c
+++ b/src/knot/nameserver/axfr.c
@@ -110,7 +110,7 @@ static int axfr_query_check(struct query_data *qdata)
 {
 	/* Check valid zone, transaction security and contents. */
 	NS_NEED_ZONE(qdata, KNOT_RCODE_NOTAUTH);
-	NS_NEED_AUTH(qdata, qdata->zone->name, ACL_ACTION_XFER);
+	NS_NEED_AUTH(qdata, qdata->zone->name, ACL_ACTION_TRANSFER);
 	/* Check expiration. */
 	NS_NEED_ZONE_CONTENTS(qdata, KNOT_RCODE_SERVFAIL);
 
diff --git a/src/knot/nameserver/internet.c b/src/knot/nameserver/internet.c
index f383364a52..4cbc1eecf3 100644
--- a/src/knot/nameserver/internet.c
+++ b/src/knot/nameserver/internet.c
@@ -832,7 +832,7 @@ int internet_query(knot_pkt_t *response, struct query_data *qdata)
 	/* No applicable ACL, refuse transaction security. */
 	if (knot_pkt_has_tsig(qdata->query)) {
 		/* We have been challenged... */
-		NS_NEED_AUTH(qdata, qdata->zone->name, ACL_ACTION_XFER);
+		NS_NEED_AUTH(qdata, qdata->zone->name, ACL_ACTION_TRANSFER);
 
 		/* Reserve space for TSIG. */
 		knot_pkt_reserve(response, knot_tsig_wire_maxsize(&qdata->sign.tsig_key));
diff --git a/src/knot/nameserver/ixfr.c b/src/knot/nameserver/ixfr.c
index 42e9b9e286..08588dd84a 100644
--- a/src/knot/nameserver/ixfr.c
+++ b/src/knot/nameserver/ixfr.c
@@ -212,7 +212,7 @@ static int ixfr_query_check(struct query_data *qdata)
 	NS_NEED_QNAME(qdata, their_soa->owner, KNOT_RCODE_FORMERR);
 
 	/* Check transcation security and zone contents. */
-	NS_NEED_AUTH(qdata, qdata->zone->name, ACL_ACTION_XFER);
+	NS_NEED_AUTH(qdata, qdata->zone->name, ACL_ACTION_TRANSFER);
 	NS_NEED_ZONE_CONTENTS(qdata, KNOT_RCODE_SERVFAIL); /* Check expiration. */
 
 	return KNOT_STATE_DONE;
diff --git a/src/knot/nameserver/notify.c b/src/knot/nameserver/notify.c
index e2a6b2cc39..3155c33bf5 100644
--- a/src/knot/nameserver/notify.c
+++ b/src/knot/nameserver/notify.c
@@ -54,7 +54,7 @@ static int notify_check_query(struct query_data *qdata)
 	/* Check valid zone, transaction security. */
 	zone_t *zone = (zone_t *)qdata->zone;
 	NS_NEED_ZONE(qdata, KNOT_RCODE_NOTAUTH);
-	NS_NEED_AUTH(qdata, zone->name, ACL_ACTION_NOTF);
+	NS_NEED_AUTH(qdata, zone->name, ACL_ACTION_NOTIFY);
 
 	return KNOT_STATE_DONE;
 }
diff --git a/src/knot/nameserver/update.c b/src/knot/nameserver/update.c
index dcb7299add..b2c093df70 100644
--- a/src/knot/nameserver/update.c
+++ b/src/knot/nameserver/update.c
@@ -419,7 +419,7 @@ static void forward_requests(zone_t *zone, list_t *requests)
 static bool update_tsig_check(struct query_data *qdata, struct knot_request *req)
 {
 	// Check that ACL is still valid.
-	if (!process_query_acl_check(qdata->zone->name, ACL_ACTION_DDNS, qdata)) {
+	if (!process_query_acl_check(qdata->zone->name, ACL_ACTION_UPDATE, qdata)) {
 		UPDATE_LOG(LOG_WARNING, "ACL check failed");
 		knot_wire_set_rcode(req->resp->wire, qdata->rcode);
 		return false;
@@ -529,7 +529,7 @@ int update_query_process(knot_pkt_t *pkt, struct query_data *qdata)
 
 	/* Need valid transaction security. */
 	zone_t *zone = (zone_t *)qdata->zone;
-	NS_NEED_AUTH(qdata, zone->name, ACL_ACTION_DDNS);
+	NS_NEED_AUTH(qdata, zone->name, ACL_ACTION_UPDATE);
 	/* Check expiration. */
 	NS_NEED_ZONE_CONTENTS(qdata, KNOT_RCODE_SERVFAIL);
 
diff --git a/src/knot/updates/acl.c b/src/knot/updates/acl.c
index 3894835acc..e825daf71f 100644
--- a/src/knot/updates/acl.c
+++ b/src/knot/updates/acl.c
@@ -96,49 +96,39 @@ bool acl_allowed(conf_val_t *acl, acl_action_t action,
 	}
 
 	while (acl->code == KNOT_EOK) {
-		/* Check if the action is allowed. */
-		bool match = false, deny = false;
-		conf_val_t action_val = conf_id_get(conf(), C_ACL, C_ACTION, acl);
-		while (action_val.code == KNOT_EOK) {
-			unsigned act = conf_opt(&action_val);
-			if (act & action) {
-				match = true;
-			}
-			if (act == ACL_ACTION_DENY) {
-				deny = true;
-			}
-			conf_val_next(&action_val);
-		}
-		if (!match) {
-			conf_val_next(acl);
-			continue;
-		}
+		conf_val_t val;
 
-		/* Check if the address prefix matches. */
-		conf_val_t addr_val = conf_id_get(conf(), C_ACL, C_ADDR, acl);
-		if (addr_val.code == KNOT_EOK) {
+		/* Check if the address matches the current acl address list. */
+		val = conf_id_get(conf(), C_ACL, C_ADDR, acl);
+		while (val.code == KNOT_EOK) {
 			unsigned prefix;
 			struct sockaddr_storage ss;
-			ss = conf_net(&addr_val, &prefix);
+			ss = conf_net(&val, &prefix);
 			if (!netblock_match(addr, &ss, prefix)) {
-				conf_val_next(acl);
+				conf_val_next(&val);
 				continue;
 			}
+
+			break;
+		}
+		/* Check for address match or empty list. */
+		if (val.code != KNOT_EOK && val.code != KNOT_ENOENT) {
+			goto next_acl;
 		}
 
-		/* Check if the key matches. */
+		/* Check if the key matches the current acl key list. */
 		conf_val_t key_val = conf_id_get(conf(), C_ACL, C_KEY, acl);
-		if (key_val.code == KNOT_EOK) {
+		while (key_val.code == KNOT_EOK) {
 			/* No key provided, but required. */
 			if (tsig->name == NULL) {
-				conf_val_next(acl);
+				conf_val_next(&key_val);
 				continue;
 			}
 
 			/* Compare key names. */
 			const knot_dname_t *key_name = conf_dname(&key_val);
 			if (knot_dname_cmp(key_name, tsig->name) != 0) {
-				conf_val_next(acl);
+				conf_val_next(&key_val);
 				continue;
 			}
 
@@ -146,30 +136,50 @@ bool acl_allowed(conf_val_t *acl, acl_action_t action,
 			conf_val_t alg_val = conf_id_get(conf(), C_KEY, C_ALG,
 			                                 &key_val);
 			if (conf_opt(&alg_val) != tsig->algorithm) {
-				conf_val_next(acl);
+				conf_val_next(&key_val);
 				continue;
 			}
-		/* No key required, but provided. */
-		} else if (tsig->name != NULL) {
-			conf_val_next(acl);
-			continue;
+
+			break;
+		}
+		/* Check for key match or empty list without key provided. */
+		if (key_val.code != KNOT_EOK &&
+		    !(key_val.code == KNOT_ENOENT && tsig->name == NULL)) {
+			goto next_acl;
 		}
 
-		if (deny) {
-			conf_val_next(acl);
-			continue;
+		/* Check if the action is allowed. */
+		val = conf_id_get(conf(), C_ACL, C_ACTION, acl);
+		while (val.code == KNOT_EOK) {
+			if (conf_opt(&val) != action) {
+				conf_val_next(&val);
+				continue;
+			}
+
+			break;
+		}
+		/* Check for action match. */
+		if (val.code != KNOT_EOK) {
+			goto next_acl;
+		}
+
+		/* Check if denied. */
+		val = conf_id_get(conf(), C_ACL, C_DENY, acl);
+		if (conf_bool(&val)) {
+			return false;
 		}
 
-		/* Fill the output with tsig secret. */
+		/* Fill the output with tsig secret if provided. */
 		if (tsig->name != NULL) {
-			conf_val_t secret_val = conf_id_get(conf(), C_KEY,
-			                                    C_SECRET, &key_val);
-			conf_data(&secret_val);
-			tsig->secret.data = (uint8_t *)secret_val.data;
-			tsig->secret.size = secret_val.len;
+			val = conf_id_get(conf(), C_KEY, C_SECRET, &key_val);
+			conf_data(&val);
+			tsig->secret.data = (uint8_t *)val.data;
+			tsig->secret.size = val.len;
 		}
 
 		return true;
+next_acl:
+		conf_val_next(acl);
 	}
 
 	return false;
diff --git a/src/knot/updates/acl.h b/src/knot/updates/acl.h
index 041f4d9500..8138a3e108 100644
--- a/src/knot/updates/acl.h
+++ b/src/knot/updates/acl.h
@@ -32,10 +32,10 @@
 
 /*! \brief ACL actions. */
 typedef enum {
-	ACL_ACTION_DENY = 0,
-	ACL_ACTION_XFER = 1,
-	ACL_ACTION_NOTF = 2,
-	ACL_ACTION_DDNS = 3
+	ACL_ACTION_NONE     = 0,
+	ACL_ACTION_NOTIFY   = 1,
+	ACL_ACTION_TRANSFER = 2,
+	ACL_ACTION_UPDATE   = 3
 } acl_action_t;
 
 /*!
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 0e9aaf5d72..e9ad3756f9 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -64,6 +64,7 @@ check-local: $(check_PROGRAMS)
 					-L $(top_builddir)/tests/runtests.log \
 					$(check_PROGRAMS)
 
+acl_SOURCES = acl.c test_conf.h
 process_query_SOURCES = process_query.c fake_server.h test_conf.h
 process_answer_SOURCES = process_answer.c fake_server.h test_conf.h
 CLEANFILES = runtests.log
diff --git a/tests/acl.c b/tests/acl.c
index 483e711b04..3e454123a4 100644
--- a/tests/acl.c
+++ b/tests/acl.c
@@ -18,10 +18,10 @@
 #include <sys/socket.h>
 #include <tap/basic.h>
 
+#include "test_conf.h"
 #include "libknot/libknot.h"
 #include "libknot/internal/sockaddr.h"
 #include "knot/updates/acl.h"
-#include "knot/conf/conf.h"
 
 static void check_sockaddr_set(struct sockaddr_storage *ss, int family,
                                const char *straddr, int port)
@@ -30,10 +30,8 @@ static void check_sockaddr_set(struct sockaddr_storage *ss, int family,
 	ok(ret == KNOT_EOK, "set address '%s'", straddr);
 }
 
-int main(int argc, char *argv[])
+static void test_netblock_match(void)
 {
-	plan_lazy();
-
 	int ret;
 	struct sockaddr_storage t = { 0 };
 
@@ -102,6 +100,142 @@ int main(int argc, char *argv[])
 	ok(ret == true, "match: ipv6 - last byte, precise prefix");
 	ret = netblock_match(&t, &ref6, 127);
 	ok(ret == false, "match: ipv6 - last byte, not match");
+}
+
+#define ZONE	"example.zone"
+#define KEY1	"key1_md5"
+#define KEY2	"key2_md5"
+#define KEY3	"key3_sha256"
+
+static void test_acl_allowed(void)
+{
+	int ret;
+	conf_val_t acl;
+	struct sockaddr_storage addr = { 0 };
+
+	knot_dname_t *zone_name = knot_dname_from_str_alloc(ZONE);
+	ok(zone_name != NULL, "create zone dname");
+	knot_dname_t *key1_name = knot_dname_from_str_alloc(KEY1);
+	ok(key1_name != NULL, "create "KEY1);
+	knot_dname_t *key2_name = knot_dname_from_str_alloc(KEY2);
+	ok(key2_name != NULL, "create "KEY2);
+	knot_dname_t *key3_name = knot_dname_from_str_alloc(KEY3);
+	ok(key3_name != NULL, "create "KEY3);
+
+	knot_tsig_key_t key0 = { NULL };
+	knot_tsig_key_t key1 = { key1_name, DNSSEC_TSIG_HMAC_MD5 };
+	knot_tsig_key_t key2 = { key2_name, DNSSEC_TSIG_HMAC_MD5 };
+	knot_tsig_key_t key3 = { key3_name, DNSSEC_TSIG_HMAC_SHA256 };
+
+	const char *conf_str =
+		"key:\n"
+		"  - id: "KEY1"\n"
+		"    algorithm: hmac-md5\n"
+		"    secret: Zm9v\n"
+		"  - id: "KEY2"\n"
+		"    algorithm: hmac-md5\n"
+		"    secret: Zm9v\n"
+		"  - id: "KEY3"\n"
+		"    algorithm: hmac-sha256\n"
+		"    secret: Zm8=\n"
+		"\n"
+		"acl:\n"
+		"  - id: acl_key_addr\n"
+		"    address: [ 2001::1 ]\n"
+		"    key: [ key1_md5 ]\n"
+		"    action: [ transfer ]\n"
+		"  - id: acl_deny\n"
+		"    address: [ 240.0.0.2 ]\n"
+		"    action: [ notify ]\n"
+		"    deny: on\n"
+		"  - id: acl_multi_addr\n"
+		"    address: [ 192.168.1.1, 240.0.0.0/24 ]\n"
+		"    action: [ notify, update ]\n"
+		"  - id: acl_multi_key\n"
+		"    key: [ key2_md5, key3_sha256 ]\n"
+		"    action: [ notify, update ]\n"
+		"\n"
+		"zone:\n"
+		"  - domain: "ZONE"\n"
+		"    acl: [ acl_key_addr, acl_deny, acl_multi_addr, acl_multi_key]";
+
+	ret = test_conf(conf_str, NULL);
+	ok(ret == KNOT_EOK, "Prepare configuration");
+
+	acl = conf_zone_get(conf(), C_ACL, zone_name);
+	ok(acl.code == KNOT_EOK, "Get zone ACL");
+	check_sockaddr_set(&addr, AF_INET6, "2001::1", 0);
+	ret = acl_allowed(&acl, ACL_ACTION_TRANSFER, &addr, &key1);
+	ok(ret == true, "Address, key, action match");
+
+	acl = conf_zone_get(conf(), C_ACL, zone_name);
+	ok(acl.code == KNOT_EOK, "Get zone ACL");
+	check_sockaddr_set(&addr, AF_INET6, "2001::2", 0);
+	ret = acl_allowed(&acl, ACL_ACTION_TRANSFER, &addr, &key1);
+	ok(ret == false, "Address not match, key, action match");
+
+	acl = conf_zone_get(conf(), C_ACL, zone_name);
+	ok(acl.code == KNOT_EOK, "Get zone ACL");
+	check_sockaddr_set(&addr, AF_INET6, "2001::1", 0);
+	ret = acl_allowed(&acl, ACL_ACTION_TRANSFER, &addr, &key0);
+	ok(ret == false, "Address match, no key, action match");
+
+	acl = conf_zone_get(conf(), C_ACL, zone_name);
+	ok(acl.code == KNOT_EOK, "Get zone ACL");
+	check_sockaddr_set(&addr, AF_INET6, "2001::1", 0);
+	ret = acl_allowed(&acl, ACL_ACTION_TRANSFER, &addr, &key2);
+	ok(ret == false, "Address match, key not match, action match");
+
+	acl = conf_zone_get(conf(), C_ACL, zone_name);
+	ok(acl.code == KNOT_EOK, "Get zone ACL");
+	check_sockaddr_set(&addr, AF_INET6, "2001::1", 0);
+	ret = acl_allowed(&acl, ACL_ACTION_NOTIFY, &addr, &key1);
+	ok(ret == false, "Address, key match, action not match");
+
+	acl = conf_zone_get(conf(), C_ACL, zone_name);
+	ok(acl.code == KNOT_EOK, "Get zone ACL");
+	check_sockaddr_set(&addr, AF_INET, "240.0.0.1", 0);
+	ret = acl_allowed(&acl, ACL_ACTION_NOTIFY, &addr, &key0);
+	ok(ret == true, "Second address match, no key, action match");
+
+	acl = conf_zone_get(conf(), C_ACL, zone_name);
+	ok(acl.code == KNOT_EOK, "Get zone ACL");
+	check_sockaddr_set(&addr, AF_INET, "240.0.0.1", 0);
+	ret = acl_allowed(&acl, ACL_ACTION_NOTIFY, &addr, &key1);
+	ok(ret == false, "Second address match, extra key, action match");
+
+	acl = conf_zone_get(conf(), C_ACL, zone_name);
+	ok(acl.code == KNOT_EOK, "Get zone ACL");
+	check_sockaddr_set(&addr, AF_INET, "240.0.0.2", 0);
+	ret = acl_allowed(&acl, ACL_ACTION_NOTIFY, &addr, &key0);
+	ok(ret == false, "Denied address match, no key, action match");
+
+	acl = conf_zone_get(conf(), C_ACL, zone_name);
+	ok(acl.code == KNOT_EOK, "Get zone ACL");
+	check_sockaddr_set(&addr, AF_INET, "240.0.0.2", 0);
+	ret = acl_allowed(&acl, ACL_ACTION_UPDATE, &addr, &key0);
+	ok(ret == true, "Denied address match, no key, action not match");
+
+	acl = conf_zone_get(conf(), C_ACL, zone_name);
+	ok(acl.code == KNOT_EOK, "Get zone ACL");
+	check_sockaddr_set(&addr, AF_INET, "1.1.1.1", 0);
+	ret = acl_allowed(&acl, ACL_ACTION_UPDATE, &addr, &key3);
+	ok(ret == true, "Arbitrary address, second key, action match");
+
+	conf_free(conf(), false);
+	knot_dname_free(&zone_name, NULL);
+	knot_dname_free(&key1_name, NULL);
+	knot_dname_free(&key2_name, NULL);
+	knot_dname_free(&key3_name, NULL);
+}
+
+int main(int argc, char *argv[])
+{
+	plan_lazy();
+
+	test_netblock_match();
+
+	test_acl_allowed();
 
 	return 0;
 }
-- 
GitLab