From b533bb405778037bfe2bce5761726ddda016d037 Mon Sep 17 00:00:00 2001
From: Daniel Salzman <daniel.salzman@nic.cz>
Date: Sat, 28 Feb 2015 14:18:53 +0100
Subject: [PATCH] Integrate new configuration

---
 samples/knot.sample.conf.in           | 136 ++++-----
 src/Makefile.am                       |  36 +--
 src/knot/common/log.c                 | 118 ++++----
 src/knot/common/log.h                 |  29 +-
 src/knot/ctl/knotc_main.c             | 389 +++++++++++++++-----------
 src/knot/ctl/process.c                |  74 ++---
 src/knot/ctl/remote.c                 | 103 ++++---
 src/knot/ctl/remote.h                 |  20 +-
 src/knot/dnssec/policy.c              |  14 +-
 src/knot/dnssec/policy.h              |   2 +-
 src/knot/dnssec/zone-events.c         |  39 ++-
 src/knot/dnssec/zone-events.h         |   8 +-
 src/knot/main.c                       | 120 +++++---
 src/knot/nameserver/axfr.c            |   6 +-
 src/knot/nameserver/chaos.c           |  21 +-
 src/knot/nameserver/internet.c        |  14 +-
 src/knot/nameserver/internet.h        |   4 +-
 src/knot/nameserver/ixfr.c            |  18 +-
 src/knot/nameserver/notify.c          |   4 +-
 src/knot/nameserver/process_answer.c  |   1 +
 src/knot/nameserver/process_query.c   |  60 ++--
 src/knot/nameserver/process_query.h   |   8 +-
 src/knot/nameserver/update.c          |  59 ++--
 src/knot/server/journal.c             |   5 +-
 src/knot/server/journal.h             |   3 +-
 src/knot/server/server.c              | 129 ++++++---
 src/knot/server/server.h              |   4 +-
 src/knot/server/tcp-handler.c         |  22 +-
 src/knot/server/udp-handler.c         |   1 +
 src/knot/updates/acl.c                | 210 +++++++-------
 src/knot/updates/acl.h                |  54 ++--
 src/knot/updates/apply.c              |  15 +-
 src/knot/updates/ddns.c               |   5 +-
 src/knot/zone/events/events.c         |   1 +
 src/knot/zone/events/handlers.c       | 114 +++++---
 src/knot/zone/events/replan.c         |  19 +-
 src/knot/zone/serial.c                |   4 +-
 src/knot/zone/serial.h                |   2 +-
 src/knot/zone/zone-load.c             |  63 +++--
 src/knot/zone/zone-load.h             |  21 +-
 src/knot/zone/zone.c                  |  99 ++++---
 src/knot/zone/zone.h                  |  19 +-
 src/knot/zone/zonedb-load.c           |  90 +++---
 src/knot/zone/zonedb-load.h           |   2 +-
 src/knot/zone/zonefile.c              |  33 +--
 src/knot/zone/zonefile.h              |   4 +-
 src/libknot/errcode.c                 |   2 +-
 src/libknot/rrtype/tsig.c             |   2 +-
 src/libknot/rrtype/tsig.h             |   2 +-
 src/libknot/yparser/ypformat.c        |   4 +
 tests-extra/tests/events/soa/test.py  |  11 +-
 tests-extra/tests/zone/expire/test.py |   4 +-
 tests-extra/tests/zone/timers/test.py |   4 +-
 tests-extra/tools/dnstest/server.py   | 209 ++++++++------
 tests/Makefile.am                     |   5 +-
 tests/acl.c                           | 192 +++++--------
 tests/fake_server.h                   |  28 +-
 tests/journal.c                       |   9 +-
 tests/process_answer.c                |   2 +-
 tests/process_query.c                 |   2 +-
 tests/requestor.c                     |  12 +-
 tests/rrl.c                           |   9 +-
 tests/zone_timers.c                   |  13 +-
 tests/zonedb.c                        |   7 +-
 64 files changed, 1495 insertions(+), 1224 deletions(-)

diff --git a/samples/knot.sample.conf.in b/samples/knot.sample.conf.in
index ede3db3da7..b967fa3775 100644
--- a/samples/knot.sample.conf.in
+++ b/samples/knot.sample.conf.in
@@ -1,89 +1,57 @@
 #
 # This is a sample of a minimal configuration file for Knot DNS.
-#
 # For exhaustive list of all options see man 5 knot.conf or refer to user manual.
 #
 
-system {
-  # Identity of the server (see RFC 4892).
-  identity on;
-
-  # Version of the server (see RFC 4892)
-  version on;
-
-  # User for running server
-  # May also specify user.group (e.g. knot.knot)
-  user knot.knot;
-
-  # Directory for storing run-time data
-  # e.g. PID file and control sockets
-  # default: ${localstatedir}/run/knot, configured with --with-rundir
-  # rundir "@run_dir@";
-}
-
-interfaces {
-  all_ipv4 {
-    address 0.0.0.0;
-    port 53;
-  }
-  all_ipv6 {
-    address [::];
-    port 53;
-  }
-}
-
-control {
-  # Default: knot.sock (relative to rundir)
-  listen-on "knot.sock";
-
-  # As an alternative, you can use an IPv4/v6 address and port
-  # Same syntax as for 'interfaces' items
-  # listen-on { address 127.0.0.1@5533; }
-
-  # Specifies ACL list for remote control
-  # Same syntax as for ACLs in zones
-  # List of remotes or groups delimited by comma
-  # Notice: keep in mind that ACLs bear no effect with UNIX sockets
-  # allow server0, admins;
-}
-
-#remotes {
-#  master0 {
-#    address 198.51.100.1@53;
-#  }
-#  slave0 {
-#    address 203.0.113.1@53;
-#  }
-#}
-
-zones {
-#  This is a default directory to place slave zone files, journals etc.
-#  default: ${localstatedir}/lib/knot, configured with --with-storage
-#  storage "@storage_dir@";
-#
-#  Example master zone
-#  example.com {
-#    file "@config_dir@/example.com.zone";
-#    xfr-out slave0;
-#    notify-out slave0;
-#  }
-#
-#  Example slave zone
-#  example.net {
-#    file "@storage_dir@/example.net.zone
-#    xfr-in master0;
-#    notify-in master0;
-#  }
-}
-
-log {
-  syslog {
-    # Log info and more serious events to syslog
-    any info;
-  }
-
-  # Log warnings, errors and criticals to stderr
-  stderr {
-    any warning;
-  }
-}
+server:
+    # Identity of the server.
+    identity: "Knot DNS"
+
+    # Version of the server (automatic value).
+    version: ""
+
+    # Listening interfaces (all configured IPv4 and IPv6 interfaces).
+    listen: 0.0.0.0@53
+    listen: ::@53
+
+remote:
+  - id: slave
+    address: 203.0.113.1@53
+
+  - id: master
+    address: 198.51.100.1@53
+
+acl:
+  - id: acl_slave
+    address: 203.0.113.1
+    action: xfer
+
+  - id: acl_master
+    address: 198.51.100.1
+    action: notify
+
+template:
+  - id: default
+    storage: "@storage_dir@"
+
+zone:
+    # Master zone.
+  - domain: example.com
+    file: "@storage_dir@/example.com.zone"
+    notify: slave
+    acl: acl_slave
+
+    # Slave zone.
+  - domain: example.net
+    file: "@storage_dir@/example.net.zone"
+    master: master
+    acl: acl_master
+
+log:
+    # Log info and more serious events to syslog.
+  - to: syslog
+    any: info
+
+    # Log warnings, errors and criticals to stderr.
+  - to: stderr
+    any: warning
diff --git a/src/Makefile.am b/src/Makefile.am
index 9c099938f2..d473c76330 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,9 +1,9 @@
 ACLOCAL_AMFLAGS = -I $(top_srcdir)/m4
 SUBDIRS = zscanner dnssec dnstap .
 
-lib_LTLIBRARIES =		\
-	libknot-int.la		\
-	libknot.la		\
+lib_LTLIBRARIES =				\
+	libknot-int.la				\
+	libknot.la				\
 	libknot-yparser.la
 noinst_LTLIBRARIES =
 
@@ -54,9 +54,9 @@ nobase_libknot_int_la_HEADERS =			\
 
 libknot_yparser_ladir = $(includedir)
 nobase_libknot_yparser_la_HEADERS =		\
-	libknot/yparser/yparser.h	\
-	libknot/yparser/ypformat.h	\
-	libknot/yparser/ypscheme.h	\
+	libknot/yparser/yparser.h		\
+	libknot/yparser/ypformat.h		\
+	libknot/yparser/ypscheme.h		\
 	libknot/yparser/yptrafo.h
 
 # dynamic: libknot internal sources
@@ -85,11 +85,11 @@ libknot_int_la_SOURCES =			\
 	$(nobase_libknot_int_la_HEADERS)
 
 libknot_yparser_la_SOURCES =			\
-	libknot/yparser/yparser.c	\
-	libknot/yparser/ypbody.c	\
-	libknot/yparser/ypformat.c	\
-	libknot/yparser/ypscheme.c	\
-	libknot/yparser/yptrafo.c	\
+	libknot/yparser/yparser.c		\
+	libknot/yparser/ypbody.c		\
+	libknot/yparser/ypformat.c		\
+	libknot/yparser/ypscheme.c		\
+	libknot/yparser/yptrafo.c		\
 	$(nobase_libknot_yparser_la_HEADERS)
 
 # dynamic: libknot headers
@@ -168,12 +168,12 @@ libknot_la_LIBADD  = libknot-int.la dnssec/libdnssec.la zscanner/libzscanner.la
 pkgconfig_DATA = libknot.pc libknot-int.pc
 
 if !HAVE_LMDB
-libknot_int_la_SOURCES +=		\
-	libknot/internal/namedb/mdb.c	\
-	libknot/internal/namedb/midl.c	\
-	libknot/internal/namedb/lmdb.h	\
+libknot_int_la_SOURCES +=			\
+	libknot/internal/namedb/mdb.c		\
+	libknot/internal/namedb/midl.c		\
+	libknot/internal/namedb/lmdb.h		\
 	libknot/internal/namedb/midl.h
-libknot_int_la_CPPFLAGS +=		\
+libknot_int_la_CPPFLAGS +=			\
 	-I$(srcdir)/libknot/internal/namedb/
 endif
 
@@ -330,8 +330,8 @@ libknotd_la_CPPFLAGS  = $(AM_CPPFLAGS) $(systemd_CFLAGS)
 libknotd_la_LDFLAGS = $(AM_LDFLAGS) $(systemd_LIBS)
 libknotd_la_LIBADD = libknot.la
 
-knotd_LDADD = libknotd.la
-knotc_LDADD = libknotd.la
+knotd_LDADD = libknotd.la libknot-yparser.la
+knotc_LDADD = libknotd.la libknot-yparser.la
 
 ####################################
 # Optional Knot DNS Daemon modules #
diff --git a/src/knot/common/log.c b/src/knot/common/log.c
index 14d9105357..ac4925d500 100644
--- a/src/knot/common/log.c
+++ b/src/knot/common/log.c
@@ -20,7 +20,7 @@
 #include <stdlib.h>
 #include <unistd.h>
 #include <sys/time.h>
-#include <time.h>			// strftime
+#include <time.h>
 #include <urcu.h>
 
 #ifdef ENABLE_SYSTEMD
@@ -165,11 +165,8 @@ int log_init()
 {
 	/* Setup initial state. */
 	int ret = KNOT_EOK;
-	int emask = LOG_MASK(LOG_WARNING)|LOG_MASK(LOG_ERR)|LOG_MASK(LOG_CRIT);
-	int imask = LOG_MASK(LOG_INFO)|LOG_MASK(LOG_NOTICE);
-
-	/* Add debug messages. */
-	emask |= LOG_MASK(LOG_DEBUG);
+	int emask = LOG_MASK(LOG_CRIT) | LOG_MASK(LOG_ERR) | LOG_MASK(LOG_WARNING);
+	int imask = LOG_MASK(LOG_NOTICE) | LOG_MASK(LOG_INFO) | LOG_MASK(LOG_DEBUG);
 
 	/* Publish base log sink. */
 	struct log_sink *log = sink_setup(0);
@@ -427,28 +424,6 @@ int log_msg_zone_str(int priority, const char *zone, const char *fmt, ...)
 	return result;
 }
 
-void hex_log(const char *data, int length)
-{
-	int ptr = 0;
-	char lbuf[512]={0}; int llen = 0;
-	for (; ptr < length; ptr++) {
-		if (ptr > 0 && ptr % 16 == 0) {
-			lbuf[llen] = '\0';
-			log_msg(LOG_DEBUG, "%s\n", lbuf);
-			llen = 0;
-		}
-		int ret = snprintf(lbuf + llen, sizeof(lbuf) - llen, "0x%02x ",
-		                   (unsigned char)*(data + ptr));
-		if (ret < 0 || ret >= sizeof(lbuf) - llen) {
-			return;
-		}
-		llen += ret;
-	}
-	if (llen > 0) {
-		log_msg(LOG_DEBUG, "%s\n", lbuf);
-	}
-}
-
 int log_update_privileges(int uid, int gid)
 {
 	for (unsigned i = 0; i < s_log->file_count; ++i) {
@@ -460,13 +435,28 @@ int log_update_privileges(int uid, int gid)
 	return KNOT_EOK;
 }
 
-int log_reconfigure(const list_t *logs, void *data)
+static logtype_t get_logtype(const char *logname)
+{
+	assert(logname);
+
+	if (strcasecmp(logname, "syslog") == 0) {
+		return LOGT_SYSLOG;
+	} else if (strcasecmp(logname, "stderr") == 0) {
+		return LOGT_STDERR;
+	} else if (strcasecmp(logname, "stdout") == 0) {
+		return LOGT_STDOUT;
+	} else {
+		return LOGT_FILE;
+	}
+}
+
+int log_reconfigure(conf_t *conf, void *data)
 {
 	// Data not used
 	UNUSED(data);
 
 	// Use defaults if no 'log' section is configured.
-	if (EMPTY_LIST(*logs)) {
+	if (conf_id_count(conf, C_LOG) == 0) {
 		log_close();
 		log_init();
 		return KNOT_EOK;
@@ -474,13 +464,16 @@ int log_reconfigure(const list_t *logs, void *data)
 
 	// Find maximum log facility id
 	unsigned files = 0;
-	node_t *list_node = NULL;
-	WALK_LIST(list_node, *logs) {
-		conf_log_t* log = (conf_log_t*)list_node;
-		if (log->type == LOGT_FILE) {
+	conf_iter_t iter = conf_iter(conf, C_LOG);
+	while (iter.code == KNOT_EOK) {
+		conf_val_t id = conf_iter_id(conf, &iter);
+		if (get_logtype(conf_str(&id)) == LOGT_FILE) {
 			++files;
 		}
+
+		conf_iter_next(conf, &iter);
 	}
+	conf_iter_finish(conf, &iter);
 
 	// Initialize logsystem
 	struct log_sink *log = sink_setup(files);
@@ -489,49 +482,46 @@ int log_reconfigure(const list_t *logs, void *data)
 	}
 
 	// Setup logs
-	list_node = NULL;
-	WALK_LIST(list_node, *logs) {
+	iter = conf_iter(conf, C_LOG);
+	while (iter.code == KNOT_EOK) {
+		conf_val_t id = conf_iter_id(conf, &iter);
+		const char *logname = conf_str(&id);
 
-		// Calculate offset
-		conf_log_t* facility_conf = (conf_log_t*)list_node;
-		int facility = facility_conf->type;
+		// Get facility.
+		int facility = get_logtype(logname);
 		if (facility == LOGT_FILE) {
-			facility = log_open_file(log, facility_conf->file);
+			facility = log_open_file(log, logname);
 			if (facility < 0) {
 				log_error("failed to open log, file '%s'",
-				          facility_conf->file);
+				          logname);
+				conf_iter_next(conf, &iter);
 				continue;
 			}
 		}
 
-		// Setup sources mapping
-		node_t *m = 0;
-		WALK_LIST(m, facility_conf->map) {
+		conf_val_t level_val;
+		unsigned level;
 
-			// Assign mapped level
-			conf_log_map_t *map = (conf_log_map_t*)m;
-			sink_levels_add(log, facility, map->source, map->prios);
-		}
-	}
+		// Set SERVER logging.
+		level_val = conf_id_get(conf, C_LOG, C_SERVER, &id);
+		level = conf_opt(&level_val);
+		sink_levels_add(log, facility, LOG_SERVER, level);
 
-	sink_publish(log);
+		// Set ZONE logging.
+		level_val = conf_id_get(conf, C_LOG, C_ZONE, &id);
+		level = conf_opt(&level_val);
+		sink_levels_add(log, facility, LOG_ZONE, level);
 
-	return KNOT_EOK;
-}
+		// Set ANY logging.
+		level_val = conf_id_get(conf, C_LOG, C_ANY, &id);
+		level = conf_opt(&level_val);
+		sink_levels_add(log, facility, LOG_ANY, level);
 
-void log_free(conf_log_t *log)
-{
-	if (!log) {
-		return;
+		conf_iter_next(conf, &iter);
 	}
+	conf_iter_finish(conf, &iter);
 
-	free(log->file);
-
-	/* Free loglevel mapping. */
-	node_t *n = NULL, *nxt = NULL;
-	WALK_LIST_DELSAFE(n, nxt, log->map) {
-		free((conf_log_map_t*)n);
-	}
+	sink_publish(log);
 
-	free(log);
+	return KNOT_EOK;
 }
diff --git a/src/knot/common/log.h b/src/knot/common/log.h
index 440e39416b..00c1406fc2 100644
--- a/src/knot/common/log.h
+++ b/src/knot/common/log.h
@@ -39,9 +39,8 @@
 #include <stdbool.h>
 #include <assert.h>
 
-#include "libknot/internal/lists.h"
-
 #include "libknot/dname.h"
+#include "knot/conf/conf.h"
 
 /*! \brief Log facility types. */
 typedef enum {
@@ -61,25 +60,6 @@ typedef enum {
 	LOG_ANY    = 7  /*!< Any module. */
 } logsrc_t;
 
-/*!
- * \brief Mapping of loglevels to message sources.
- */
-typedef struct conf_log_map {
-	node_t n;
-	int source; /*!< Log message source mask. */
-	int prios;  /*!< Log priorities mask. */
-} conf_log_map_t;
-
-/*!
- * \brief Log facility descriptor.
- */
-typedef struct conf_log {
-	node_t n;
-	logtype_t type;  /*!< Type of the log (SYSLOG/STDERR/FILE). */
-	char *file;      /*!< Filename in case of LOG_FILE, else NULL. */
-	list_t map;      /*!< Log levels mapping. */
-} conf_log_t;
-
 /*! \brief Format for timestamps in log files. */
 #define KNOT_LOG_TIME_FORMAT "%Y-%m-%dT%H:%M:%S"
 
@@ -172,8 +152,6 @@ int log_msg_zone(int priority, const knot_dname_t *zone, const char *fmt, ...)
 int log_msg_zone_str(int priority, const char *zone, const char *fmt, ...)
     __attribute__((format(printf, 3, 4)));
 
-void hex_log(const char *data, int length);
-
 /* Convenient logging. */
 
 #define log_fatal(msg...)   log_msg(LOG_CRIT,    msg)
@@ -215,9 +193,6 @@ int log_update_privileges(int uid, int gid);
  * \retval KNOT_EINVAL on invalid parameters.
  * \retval KNOT_ENOMEM out of memory error.
  */
-int log_reconfigure(const list_t *logs, void *data);
-
-/*! \brief Free log config. */
-void log_free(conf_log_t *log);
+int log_reconfigure(conf_t *conf, void *data);
 
 /*! @} */
diff --git a/src/knot/ctl/knotc_main.c b/src/knot/ctl/knotc_main.c
index 504dba4731..71bec15f14 100644
--- a/src/knot/ctl/knotc_main.c
+++ b/src/knot/ctl/knotc_main.c
@@ -31,7 +31,6 @@
 #include "libknot/libknot.h"
 #include "knot/common/debug.h"
 #include "knot/ctl/estimator.h"
-#include "knot/ctl/process.h"
 #include "knot/ctl/remote.h"
 #include "knot/conf/conf.h"
 #include "knot/server/tcp-handler.h"
@@ -40,10 +39,9 @@
 
 /*! \brief Controller flags. */
 enum knotc_flag_t {
-	F_NULL =         0 << 0,
-	F_FORCE =        1 << 0,
-	F_VERBOSE =      1 << 1,
-	F_NOCONF =       1 << 2
+	F_NULL    = 0,
+	F_FORCE   = 1 << 0,
+	F_VERBOSE = 1 << 1
 };
 
 /*! \brief Check if flag is present. */
@@ -52,45 +50,58 @@ static inline unsigned has_flag(unsigned flags, enum knotc_flag_t f)
 	return flags & f;
 }
 
+/*! \brief Callback arguments. */
+typedef struct cmd_args {
+	struct sockaddr_storage *addr;
+	knot_tsig_key_t *key;
+	int argc;
+	char **argv;
+	unsigned flags;
+	const char *conf_db;
+} cmd_args_t;
+
 /*! \brief Callback prototype for command. */
-typedef int (*knot_cmdf_t)(int argc, char *argv[], unsigned flags);
+typedef int (*knot_cmdf_t)(cmd_args_t *args);
 
 /*! \brief Command table item. */
 typedef struct knot_cmd {
 	knot_cmdf_t cb;
-	int need_conf;
 	const char *name;
 	const char *params;
 	const char *desc;
 } knot_cmd_t;
 
 /* Forward decls. */
-static int cmd_stop(int argc, char *argv[], unsigned flags);
-static int cmd_reload(int argc, char *argv[], unsigned flags);
-static int cmd_refresh(int argc, char *argv[], unsigned flags);
-static int cmd_flush(int argc, char *argv[], unsigned flags);
-static int cmd_status(int argc, char *argv[], unsigned flags);
-static int cmd_zonestatus(int argc, char *argv[], unsigned flags);
-static int cmd_checkconf(int argc, char *argv[], unsigned flags);
-static int cmd_checkzone(int argc, char *argv[], unsigned flags);
-static int cmd_memstats(int argc, char *argv[], unsigned flags);
-static int cmd_signzone(int argc, char *argv[], unsigned flags);
+static int cmd_stop(cmd_args_t *args);
+static int cmd_reload(cmd_args_t *args);
+static int cmd_refresh(cmd_args_t *args);
+static int cmd_flush(cmd_args_t *args);
+static int cmd_status(cmd_args_t *args);
+static int cmd_zonestatus(cmd_args_t *args);
+static int cmd_checkconf(cmd_args_t *args);
+static int cmd_checkzone(cmd_args_t *args);
+static int cmd_memstats(cmd_args_t *args);
+static int cmd_signzone(cmd_args_t *args);
+static int cmd_import(cmd_args_t *args);
+static int cmd_export(cmd_args_t *args);
 
 /*! \brief Table of remote commands. */
 knot_cmd_t knot_cmd_tbl[] = {
-	{&cmd_stop,       0, "stop",       "",            "Stop server."},
-	{&cmd_reload,     0, "reload",     "[<zone>...]", "Reload particular zones or reload whole\n"
-	                   "                                configuration and changed zones."},
-	{&cmd_refresh,    0, "refresh",    "[<zone>...]", "Refresh slave zones. Flag '-f' forces retransfer\n"
-	                   "                                (zone(s) must be specified)."},
-	{&cmd_flush,      0, "flush",      "[<zone>...]", "Flush journal and update zone files."},
-	{&cmd_status,     0, "status",     "",            "Check if server is running."},
-	{&cmd_zonestatus, 0, "zonestatus", "[<zone>...]", "Show status of configured zones."},
-	{&cmd_checkconf,  1, "checkconf",  "",            "Check current server configuration."},
-	{&cmd_checkzone,  1, "checkzone",  "[<zone>...]", "Check zones."},
-	{&cmd_memstats,   1, "memstats",   "[<zone>...]", "Estimate memory use for zones."},
-	{&cmd_signzone,   0, "signzone",   "<zone>...",   "Sign zones with available DNSSEC keys."},
-	{NULL, 0, NULL, NULL, NULL}
+	{&cmd_stop,       "stop",       "",            "Stop server."},
+	{&cmd_reload,     "reload",     "[<zone>...]", "Reload particular zones or reload whole\n"
+	                "                                configuration and changed zones."},
+	{&cmd_refresh,    "refresh",    "[<zone>...]", "Refresh slave zones. Flag '-f' forces retransfer\n"
+	                "                                (zone(s) must be specified)."},
+	{&cmd_flush,      "flush",      "[<zone>...]", "Flush journal and update zone files."},
+	{&cmd_status,     "status",     "",            "Check if server is running."},
+	{&cmd_zonestatus, "zonestatus", "[<zone>...]", "Show status of configured zones."},
+	{&cmd_checkconf,  "checkconf",  "",            "Check current server configuration."},
+	{&cmd_checkzone,  "checkzone",  "[<zone>...]", "Check zones."},
+	{&cmd_memstats,   "memstats",   "[<zone>...]", "Estimate memory use for zones."},
+	{&cmd_signzone,   "signzone",   "<zone>...",   "Sign zones with available DNSSEC keys."},
+	{&cmd_import,     "import",     "<filename>",  "Import configuration database."},
+	{&cmd_export,     "export",     "<filename>",  "Export configuration database."},
+	{NULL, NULL, NULL, NULL}
 };
 
 /*! \brief Print help. */
@@ -99,17 +110,19 @@ void help(void)
 	printf("Usage: %sc [parameters] <action> [action_args]\n", PACKAGE_NAME);
 	printf("\nParameters:\n"
 	       " -c, --config <file>           Select configuration file.\n"
-	       " -s, --server <server>         Remote UNIX socket/IP address\n"
-	       "                                (default %s).\n"
+	       "                                (default %s)\n"
+	       " -C, --confdb <dir>            Select configuration database directory.\n"
+	       " -s, --server <server>         Remote UNIX socket/IP address.\n"
+	       "                                (default %s)\n"
 	       " -p, --port <port>             Remote server port (only for IP).\n"
-	       " -y, --key <[hmac:]name:key>   Use key specified on the command line\n"
-	       "                                (default algorithm is hmac-md5).\n"
+	       " -y, --key <[hmac:]name:key>   Use key specified on the command line.\n"
+	       "                                (default algorithm is hmac-md5)\n"
 	       " -k, --keyfile <file>          Use key file (as in config section 'keys').\n"
 	       " -f, --force                   Force operation - override some checks.\n"
 	       " -v, --verbose                 Verbose mode - additional runtime information.\n"
 	       " -V, --version                 Print %s server version.\n"
 	       " -h, --help                    Print help and usage.\n",
-	       RUN_DIR "/knot.sock", PACKAGE_NAME);
+	       CONF_DEFAULT_FILE, RUN_DIR "/knot.sock", PACKAGE_NAME);
 	printf("\nActions:\n");
 	knot_cmd_t *c = knot_cmd_tbl;
 	while (c->name != NULL) {
@@ -180,19 +193,13 @@ static int cmd_remote_reply(int c)
 	return ret;
 }
 
-static int cmd_remote(const char *cmd, uint16_t rrt, int argc, char *argv[])
+static int cmd_remote(struct sockaddr_storage *addr, knot_tsig_key_t *key,
+                      const char *cmd, uint16_t rrt, int argc, char *argv[])
 {
 	int rc = 0;
 
-	/* Check remote address. */
-	conf_iface_t *r = conf()->ctl.iface;
-	if (!r || r->addr.ss_family == AF_UNSPEC) {
-		log_error("no remote address for '%s' configured", cmd);
-		return 1;
-	}
-
 	/* Make query. */
-	knot_pkt_t *pkt = remote_query(cmd, r->key);
+	knot_pkt_t *pkt = remote_query(cmd, key);
 	if (!pkt) {
 		log_warning("failed to prepare query for '%s'", cmd);
 		return 1;
@@ -228,8 +235,9 @@ static int cmd_remote(const char *cmd, uint16_t rrt, int argc, char *argv[])
 		}
 	}
 
-	if (r->key) {
-		int res = remote_query_sign(pkt->wire, &pkt->size, pkt->max_size, r->key);
+	if (key) {
+		int res = remote_query_sign(pkt->wire, &pkt->size, pkt->max_size,
+		                            key);
 		if (res != KNOT_EOK) {
 			log_error("failed to sign the packet");
 			knot_pkt_free(&pkt);
@@ -241,9 +249,9 @@ static int cmd_remote(const char *cmd, uint16_t rrt, int argc, char *argv[])
 
 	/* Connect to remote. */
 	char addr_str[SOCKADDR_STRLEN] = {0};
-	sockaddr_tostr(addr_str, sizeof(addr_str), &r->addr);
+	sockaddr_tostr(addr_str, sizeof(addr_str), addr);
 
-	int s = net_connected_socket(SOCK_STREAM, &r->addr, &r->via, 0);
+	int s = net_connected_socket(SOCK_STREAM, addr, NULL, 0);
 	if (s < 0) {
 		log_error("failed to connect to remote host '%s'", addr_str);
 		knot_pkt_free(&pkt);
@@ -252,7 +260,8 @@ static int cmd_remote(const char *cmd, uint16_t rrt, int argc, char *argv[])
 
 	/* Wait for availability. */
 	struct pollfd pfd = { s, POLLOUT, 0 };
-	if (poll(&pfd, 1, conf()->max_conn_reply) != 1) {
+	conf_val_t val = conf_get(conf(), C_SRV, C_MAX_CONN_REPLY);
+	if (poll(&pfd, 1, conf_int(&val)) != 1) {
 		log_error("failed to connect to remote host '%s'", addr_str);
 		close(s);
 		knot_pkt_free(&pkt);
@@ -418,7 +427,8 @@ int main(int argc, char **argv)
 	/* Parse command line arguments */
 	int c = 0, li = 0, rc = 0;
 	unsigned flags = F_NULL;
-	const char *config_fn = conf_find_default();
+	const char *config_fn = CONF_DEFAULT_FILE;
+	const char *config_db = NULL;
 
 	/* Remote server descriptor. */
 	const char *r_addr = NULL;
@@ -433,6 +443,7 @@ int main(int argc, char **argv)
 	/* Long options. */
 	struct option opts[] = {
 		{"config",  required_argument, 0, 'c' },
+		{"confdb",  required_argument, 0, 'C' },
 		{"server",  required_argument, 0, 's' },
 		{"port",    required_argument, 0, 'p' },
 		{"key",     required_argument, 0, 'y' },
@@ -444,11 +455,14 @@ int main(int argc, char **argv)
 		{0, 0, 0, 0}
 	};
 
-	while ((c = getopt_long(argc, argv, "s:p:y:k:fc:vVh", opts, &li)) != -1) {
+	while ((c = getopt_long(argc, argv, "s:p:y:k:fc:C:vVh", opts, &li)) != -1) {
 		switch (c) {
 		case 'c':
 			config_fn = optarg;
 			break;
+		case 'C':
+			config_db = optarg;
+			break;
 		case 's':
 			r_addr = optarg;
 			break;
@@ -509,44 +523,60 @@ int main(int argc, char **argv)
 
 	/* Command not found. */
 	if (!cmd->name) {
-		log_error("invalid command: '%s'", argv[optind]);
+		log_fatal("invalid command: '%s'", argv[optind]);
 		rc = 1;
 		goto exit;
 	}
 
-	/* Open config, create empty if not exists. */
-	if (conf_open(config_fn) != KNOT_EOK) {
-		s_config = conf_new("");
-		flags |= F_NOCONF;
-	}
-
-	/* Check if config file is required. */
-	if (has_flag(flags, F_NOCONF) && cmd->need_conf) {
-		log_error("failed to find a config file, refusing to continue");
-		rc = 1;
-		goto exit;
-	}
+	/* Open configuration. */
+	conf_t *new_conf = NULL;
+	if (config_db == NULL) {
+		int ret = conf_new(&new_conf, conf_scheme, NULL);
+		if (ret != KNOT_EOK) {
+			log_fatal("failed to initialize configuration database "
+			          "(%s)", knot_strerror(ret));
+			rc = 1;
+			goto exit;
+		}
 
-	/* Create remote iface if not present in config. */
-	conf_iface_t *ctl_if = conf()->ctl.iface;
-	if (!ctl_if) {
-		ctl_if = malloc(sizeof(conf_iface_t));
-		memset(ctl_if, 0, sizeof(conf_iface_t));
-		conf()->ctl.iface = ctl_if;
+		/* Import the configuration file. */
+		ret = conf_import(new_conf, config_fn, true);
+		if (ret != KNOT_EOK) {
+			log_fatal("failed to load configuration file '%s' (%s)",
+			          config_fn, knot_strerror(ret));
+			rc = 1;
+			goto exit;
+		}
 
-		/* Fill defaults. */
-		if (!r_addr) {
-			r_addr = RUN_DIR "/knot.sock";
-		} else if (r_port < 0) {
-			r_port = REMOTE_DPORT;
+		new_conf->filename = strdup(config_fn);
+	} else {
+		/* Open configuration database. */
+		int ret = conf_new(&new_conf, conf_scheme, config_db);
+		if (ret != KNOT_EOK) {
+			log_fatal("failed to open configuration database '%s' "
+			          "(%s)", config_db, knot_strerror(ret));
+			rc = 1;
+			goto exit;
 		}
 	}
 
-	/* Install the key. */
-	if (r_key.name) {
-		ctl_if->key = &r_key;
+	/* Run post-open config operations. */
+	int res = conf_post_open(new_conf);
+	if (res != KNOT_EOK) {
+		log_fatal("failed to use configuration (%s)", knot_strerror(res));
+		conf_free(new_conf, false);
+		return EXIT_FAILURE;
 	}
 
+	conf_update(new_conf);
+
+	/* Get control address. */
+	conf_val_t listen_val = conf_get(conf(), C_CTL, C_LISTEN);
+	conf_val_t rundir_val = conf_get(conf(), C_SRV, C_RUNDIR);
+	char *rundir = conf_abs_path(&rundir_val, NULL);
+	struct sockaddr_storage addr = conf_addr(&listen_val, rundir);
+	free(rundir);
+
 	/* Override from command line. */
 	if (r_addr) {
 		/* Check for v6 address. */
@@ -563,11 +593,11 @@ int main(int argc, char **argv)
 			family = AF_UNIX;
 		}
 
-		sockaddr_set(&ctl_if->addr, family, r_addr, sockaddr_port(&ctl_if->addr));
+		sockaddr_set(&addr, family, r_addr, sockaddr_port(&addr));
 	}
 
 	if (r_port > 0) {
-		sockaddr_port_set(&ctl_if->addr, r_port);
+		sockaddr_port_set(&addr, r_port);
 	}
 
 	/* Verbose mode. */
@@ -576,102 +606,132 @@ int main(int argc, char **argv)
 		               LOG_MASK(LOG_INFO) | LOG_MASK(LOG_DEBUG));
 	}
 
+	cmd_args_t args = {
+		&addr,
+		r_key.name != NULL ? &r_key : NULL,
+		argc - optind - 1,
+		argv + optind + 1,
+		flags,
+		config_db
+	};
+
 	/* Execute command. */
 	dnssec_crypto_init();
-	rc = cmd->cb(argc - optind - 1, argv + optind + 1, flags);
+	rc = cmd->cb(&args);
 	dnssec_crypto_cleanup();
 
 exit:
 	/* Finish */
 	knot_tsig_key_free(&r_key);
+	conf_free(conf(), false);
 	log_close();
 	return rc;
 }
 
-static int cmd_stop(int argc, char *argv[], unsigned flags)
+static int cmd_stop(cmd_args_t *args)
 {
-	UNUSED(argc);
-	UNUSED(argv);
-	UNUSED(flags);
-
-	if (argc > 0) {
+	if (args->argc > 0) {
 		printf("command does not take arguments\n");
 		return KNOT_EINVAL;
 	}
 
-	return cmd_remote("stop", KNOT_RRTYPE_TXT, 0, NULL);
+	return cmd_remote(args->addr, args->key, "stop", KNOT_RRTYPE_TXT,
+	                  0, NULL);
+}
+
+static int cmd_reload(cmd_args_t *args)
+{
+	return cmd_remote(args->addr, args->key, "reload", KNOT_RRTYPE_NS,
+	                  args->argc, args->argv);
 }
 
-static int cmd_reload(int argc, char *argv[], unsigned flags)
+static int cmd_refresh(cmd_args_t *args)
 {
-	UNUSED(flags);
+	const char *action = (args->flags & F_FORCE) ? "retransfer" : "refresh";
 
-	return cmd_remote("reload", KNOT_RRTYPE_NS, argc, argv);
+	return cmd_remote(args->addr, args->key, action, KNOT_RRTYPE_NS,
+	                  args->argc, args->argv);
 }
 
-static int cmd_refresh(int argc, char *argv[], unsigned flags)
+static int cmd_flush(cmd_args_t *args)
 {
-	UNUSED(flags);
+	return cmd_remote(args->addr, args->key, "flush", KNOT_RRTYPE_NS,
+	                  args->argc, args->argv);
+}
 
-	if (flags & F_FORCE) {
-		return cmd_remote("retransfer", KNOT_RRTYPE_NS, argc, argv);
-	} else {
-		return cmd_remote("refresh", KNOT_RRTYPE_NS, argc, argv);
+static int cmd_status(cmd_args_t *args)
+{
+	if (args->argc > 0) {
+		printf("command does not take arguments\n");
+		return KNOT_EINVAL;
 	}
+
+	return cmd_remote(args->addr, args->key, "status", KNOT_RRTYPE_TXT,
+	                  0, NULL);
 }
 
-static int cmd_flush(int argc, char *argv[], unsigned flags)
+static int cmd_zonestatus(cmd_args_t *args)
 {
-	UNUSED(flags);
+	return cmd_remote(args->addr, args->key, "zonestatus", KNOT_RRTYPE_NS,
+	                  args->argc, args->argv);
+}
 
-	return cmd_remote("flush", KNOT_RRTYPE_NS, argc, argv);
+static int cmd_signzone(cmd_args_t *args)
+{
+	return cmd_remote(args->addr, args->key, "signzone", KNOT_RRTYPE_NS,
+	                  args->argc, args->argv);
 }
 
-static int cmd_status(int argc, char *argv[], unsigned flags)
+static int cmd_import(cmd_args_t *args)
 {
-	UNUSED(argv);
-	UNUSED(flags);
+	if (args->argc != 1) {
+		printf("command takes one argument\n");
+		return KNOT_EINVAL;
+	}
 
-	if (argc > 0) {
-		printf("command does not take arguments\n");
+	if (!(args->flags & F_FORCE)) {
+		printf("use force option to import/replace config DB\n");
 		return KNOT_EINVAL;
 	}
 
-	return cmd_remote("status", KNOT_RRTYPE_TXT, 0, NULL);
-}
+	conf_t *new_conf = NULL;
+	int ret = conf_new(&new_conf, conf_scheme, args->conf_db);
+	if (ret != KNOT_EOK) {
+		printf("failed to open configuration (%s)", args->conf_db);
+		return ret;
+	}
 
-static int cmd_zonestatus(int argc, char *argv[], unsigned flags)
-{
-	UNUSED(flags);
+	ret = conf_import(new_conf, args->argv[0], true);
+	if (ret != KNOT_EOK) {
+		printf("failed to import configuration (%s)", args->argv[0]);
+		conf_free(new_conf, false);
+		return ret;
+	}
 
-	return cmd_remote("zonestatus", KNOT_RRTYPE_NS, argc, argv);
+	return KNOT_EOK;
 }
 
-static int cmd_signzone(int argc, char *argv[], unsigned flags)
+static int cmd_export(cmd_args_t *args)
 {
-	return cmd_remote("signzone", KNOT_RRTYPE_NS, argc, argv);
+	if (args->argc != 1) {
+		printf("command takes one argument\n");
+		return KNOT_EINVAL;
+	}
+
+	return conf_export(conf(), args->argv[0], YP_SNONE);
 }
 
-static int cmd_checkconf(int argc, char *argv[], unsigned flags)
+static int cmd_checkconf(cmd_args_t *args)
 {
-	UNUSED(argc);
-	UNUSED(argv);
-	UNUSED(flags);
+	UNUSED(args);
 
 	log_info("configuration is valid");
 
 	return 0;
 }
 
-static bool fetch_zone(int argc, char *argv[], conf_zone_t *zone)
+static bool fetch_zone(int argc, char *argv[], const knot_dname_t *name)
 {
-	/* Convert zone name to dname */
-	knot_dname_t *zone_name = knot_dname_from_str_alloc(zone->name);
-	if (zone_name == NULL) {
-		return false;
-	}
-	(void)knot_dname_to_lower(zone_name);
-
 	bool found = false;
 
 	int i = 0;
@@ -681,77 +741,61 @@ static bool fetch_zone(int argc, char *argv[], conf_zone_t *zone)
 
 		if (arg_name != NULL) {
 			(void)knot_dname_to_lower(arg_name);
-			found = knot_dname_is_equal(zone_name, arg_name);
+			found = knot_dname_is_equal(name, arg_name);
 		}
 
 		i++;
 		knot_dname_free(&arg_name, NULL);
 	}
 
-	knot_dname_free(&zone_name, NULL);
 	return found;
 }
 
-static int cmd_checkzone(int argc, char *argv[], unsigned flags)
+static int cmd_checkzone(cmd_args_t *args)
 {
-	UNUSED(flags);
-
 	/* Zone checking */
 	int rc = 0;
 
 	/* Generate databases for all zones */
-	const bool sorted = false;
-	hattrie_iter_t *z_iter = hattrie_iter_begin(conf()->zones, sorted);
-	if (z_iter == NULL) {
-		return KNOT_ERROR;
-	}
-	for (; !hattrie_iter_finished(z_iter); hattrie_iter_next(z_iter)) {
-		conf_zone_t *zone = (conf_zone_t *)*hattrie_iter_val(z_iter);
+	conf_iter_t iter = conf_iter(conf(), C_ZONE);
+	while (iter.code == KNOT_EOK) {
+		conf_val_t id = conf_iter_id(conf(), &iter);
 
 		/* Fetch zone */
-		int zone_match = fetch_zone(argc, argv, zone);
-
-		if (!zone_match && argc > 0) {
-			conf_free_zone(zone);
+		int zone_match = fetch_zone(args->argc, args->argv, conf_dname(&id));
+		if (!zone_match && args->argc > 0) {
 			continue;
 		}
 
 		/* Create zone loader context. */
-		zone_contents_t *loaded_zone = zone_load_contents(zone);
+		zone_contents_t *loaded_zone = zone_load_contents(conf(), conf_dname(&id));
 		if (loaded_zone == NULL) {
 			rc = 1;
 			continue;
 		}
 
-		log_zone_str_info(zone->name, "zone is valid");
+		log_zone_info(conf_dname(&id), "zone is valid");
 		zone_contents_deep_free(&loaded_zone);
+		conf_iter_next(conf(), &iter);
 	}
-	hattrie_iter_free(z_iter);
+	conf_iter_finish(conf(), &iter);
 
 	return rc;
 }
 
-static int cmd_memstats(int argc, char *argv[], unsigned flags)
+static int cmd_memstats(cmd_args_t *args)
 {
-	UNUSED(flags);
-
 	/* Zone checking */
 	double total_size = 0;
 
 	/* Generate databases for all zones */
-	const bool sorted = false;
-	hattrie_iter_t *z_iter = hattrie_iter_begin(conf()->zones, sorted);
-	if (z_iter == NULL) {
-		return KNOT_ERROR;
-	}
-	for (; !hattrie_iter_finished(z_iter); hattrie_iter_next(z_iter)) {
-		conf_zone_t *zone = (conf_zone_t *)*hattrie_iter_val(z_iter);
+	conf_iter_t iter = conf_iter(conf(), C_ZONE);
+	while (iter.code == KNOT_EOK) {
+		conf_val_t id = conf_iter_id(conf(), &iter);
 
 		/* Fetch zone */
-		int zone_match = fetch_zone(argc, argv, zone);
-
-		if (!zone_match && argc > 0) {
-			conf_free_zone(zone);
+		int zone_match = fetch_zone(args->argc, args->argv, conf_dname(&id));
+		if (!zone_match && args->argc > 0) {
 			continue;
 		}
 
@@ -772,24 +816,35 @@ static int cmd_memstats(int argc, char *argv[], unsigned flags)
 		}
 
 		/* Create zone scanner. */
-		zs_scanner_t *zs = zs_scanner_create(zone->name,
+		char *zone_name = knot_dname_to_str_alloc(conf_dname(&id));
+		if (zone_name == NULL) {
+			log_error("not enough memory");
+			hattrie_free(est.node_table);
+			break;
+		}
+		zs_scanner_t *zs = zs_scanner_create(zone_name,
 		                                     KNOT_CLASS_IN, 3600,
 		                                     estimator_rrset_memsize_wrap,
 		                                     process_error,
 		                                     &est);
+		free(zone_name);
 		if (zs == NULL) {
-			log_zone_str_error(zone->name, "failed to load zone");
+			log_zone_error(conf_dname(&id), "failed to load zone");
 			hattrie_free(est.node_table);
+			conf_iter_next(conf(), &iter);
 			continue;
 		}
 
 		/* Do a parser run, but do not actually create the zone. */
-		int ret = zs_scanner_parse_file(zs, zone->file);
+		char *zonefile = conf_zonefile(conf(), conf_dname(&id));
+		int ret = zs_scanner_parse_file(zs, zonefile);
+		free(zonefile);
 		if (ret != 0) {
-			log_zone_str_error(zone->name, "failed to parse zone");
+			log_zone_error(conf_dname(&id), "failed to parse zone");
 			hattrie_apply_rev(est.node_table, estimator_free_trie_node, NULL);
 			hattrie_free(est.node_table);
 			zs_scanner_free(zs);
+			conf_iter_next(conf(), &iter);
 			continue;
 		}
 
@@ -807,15 +862,15 @@ static int cmd_memstats(int argc, char *argv[], unsigned flags)
 		                   est.htable_size +
 		                   malloc_size) * ESTIMATE_MAGIC) / (1024.0 * 1024.0);
 
-		log_zone_str_info(zone->name, "%zu RRs, used memory estimation is %zu MB",
-		                  est.record_count, (size_t)zone_size);
+		log_zone_info(conf_dname(&id), "%zu RRs, used memory estimation is %zu MB",
+		              est.record_count, (size_t)zone_size);
 		zs_scanner_free(zs);
 		total_size += zone_size;
-		conf_free_zone(zone);
+		conf_iter_next(conf(), &iter);
 	}
-	hattrie_iter_free(z_iter);
+	conf_iter_finish(conf(), &iter);
 
-	if (argc == 0) { // for all zones
+	if (args->argc == 0) { // for all zones
 		log_info("estimated memory consumption for all zones is %zu MB",
 		         (size_t)total_size);
 	}
diff --git a/src/knot/ctl/process.c b/src/knot/ctl/process.c
index 3b05e7c144..8b99fe7f96 100644
--- a/src/knot/ctl/process.c
+++ b/src/knot/ctl/process.c
@@ -21,36 +21,38 @@
 #include <string.h>
 #include <signal.h>
 #include <grp.h>
-#include <unistd.h>
 #include <assert.h>
 #include <sys/wait.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <sys/types.h>
 #include <pwd.h>
+#include <urcu.h>
 
-#include "libknot/internal/mem.h"
-#include "libknot/libknot.h"
 #include "knot/ctl/process.h"
 #include "knot/conf/conf.h"
+#include "knot/common/log.h"
+#include "libknot/errcode.h"
+#include "libknot/internal/mem.h"
+#include "libknot/libknot.h"
+
+#define LOCK_FILE	"knot.lock"
 
 char* pid_filename()
 {
 	rcu_read_lock();
-
-	/* Read configuration. */
-	char* ret = NULL;
-
-	if (conf()) {
-		if (conf()->pidfile != NULL)
-			ret = strdup(conf()->pidfile);
-		else if (conf()->rundir != NULL)
-			ret = strcdup(conf()->rundir, "/knot.pid");
-	}
-
+	conf_val_t val = conf_get(conf(), C_SRV, C_RUNDIR);
+	char *rundir = conf_abs_path(&val, NULL);
+	val = conf_get(conf(), C_SRV, C_PIDFILE);
+	char *pidfile = conf_abs_path(&val, rundir);
 	rcu_read_unlock();
 
-	return ret;
+	if (rundir != NULL) {
+		free(rundir);
+		return pidfile;
+	} else {
+		return NULL;
+	}
 }
 
 pid_t pid_read(const char* fn)
@@ -171,35 +173,39 @@ int proc_update_privileges(int uid, int gid)
 		}
 	}
 
-	/* Check storage writeability. */
-	int ret = KNOT_EOK;
-	const bool sorted = false;
-	hattrie_iter_t *z_iter = hattrie_iter_begin(conf()->zones, sorted);
-	if (z_iter == NULL) {
-		return KNOT_ERROR;
-	}
-	for (; !hattrie_iter_finished(z_iter); hattrie_iter_next(z_iter)) {
-		conf_zone_t *zone = (conf_zone_t *)*hattrie_iter_val(z_iter);
-		char *lfile = strcdup(zone->storage, "/knot.lock");
+	/* Check storage writability. */
+	conf_iter_t iter = conf_iter(conf(), C_ZONE);
+	while (iter.code == KNOT_EOK) {
+		conf_val_t id = conf_iter_id(conf(), &iter);
+		conf_val_t val = conf_zone_get(conf(), C_STORAGE, conf_dname(&id));
+		char *storage = conf_abs_path(&val, NULL);
+		if (storage == NULL) {
+			conf_iter_finish(conf(), &iter);
+			return KNOT_ENOMEM;
+		}
+
+		char *lfile = sprintf_alloc("%s/%s", storage, LOCK_FILE);
 		assert(lfile != NULL);
-		FILE* fp = fopen(lfile, "w");
+		FILE *fp = fopen(lfile, "w");
 		if (fp == NULL) {
-			log_warning("storage directory '%s' is not writable",
-				    zone->storage);
-			ret = KNOT_EACCES;
+			log_error("storage directory '%s' is not writable",
+			          storage);
+			free(lfile);
+			free(storage);
+			conf_iter_finish(conf(), &iter);
+			return KNOT_EACCES;
 		} else {
 			fclose(fp);
 			unlink(lfile);
 		}
 		free(lfile);
+		free(storage);
 
-		if (ret != KNOT_EOK) {
-			break;
-		}
+		conf_iter_next(conf(), &iter);
 	}
-	hattrie_iter_free(z_iter);
+	conf_iter_finish(conf(), &iter);
 
-	return ret;
+	return KNOT_EOK;
 }
 
 char *pid_check_and_create()
diff --git a/src/knot/ctl/remote.c b/src/knot/ctl/remote.c
index b4d7b0a86c..ad4cef1d54 100644
--- a/src/knot/ctl/remote.c
+++ b/src/knot/ctl/remote.c
@@ -16,6 +16,7 @@
 
 #include <assert.h>
 #include <sys/stat.h>
+#include <urcu.h>
 
 #include "dnssec/random.h"
 #include "knot/common/debug.h"
@@ -172,7 +173,11 @@ static int remote_zone_refresh(zone_t *zone, remote_cmdargs_t *a)
 {
 	UNUSED(a);
 
-	if (zone_master(zone) == NULL) {
+	rcu_read_lock();
+	bool is_master = zone_is_master(zone);
+	rcu_read_unlock();
+
+	if (is_master) {
 		return KNOT_EINVAL;
 	}
 
@@ -194,7 +199,11 @@ static int remote_zone_retransfer(zone_t *zone, remote_cmdargs_t *a)
 {
 	UNUSED(a);
 
-	if (zone_master(zone) == NULL) {
+	rcu_read_lock();
+	bool is_master = zone_is_master(zone);
+	rcu_read_unlock();
+
+	if (is_master) {
 		return KNOT_EINVAL;
 	}
 
@@ -221,7 +230,12 @@ static int remote_zone_sign(zone_t *zone, remote_cmdargs_t *a)
 {
 	UNUSED(a);
 
-	if (zone == NULL || !zone->conf->dnssec_enable) {
+	rcu_read_lock();
+	conf_val_t val = conf_zone_get(conf(), C_DNSSEC_ENABLE, zone->name);
+	bool dnssec_enable = conf_bool(&val);
+	rcu_read_unlock();
+
+	if (zone == NULL || !dnssec_enable) {
 		return KNOT_EINVAL;
 	}
 
@@ -342,15 +356,22 @@ static int remote_zonestatus(zone_t *zone, remote_cmdargs_t *a)
 	/* Prepare zone info. */
 	char buf[512] = { '\0' };
 	char dnssec_buf[128] = { '\0' };
+	char *zone_name = knot_dname_to_str_alloc(zone->name);
+
+	conf_val_t val = conf_zone_get(conf(), C_DNSSEC_ENABLE, zone->name);
+	bool dnssec_enable = conf_bool(&val);
+	bool is_master = zone_is_master(zone);
+
 	int n = snprintf(buf, sizeof(buf),
 	                 "%s\ttype=%s | serial=%u | %s %s | %s %s\n",
-	                 zone->conf->name,
-	                 zone_master(zone) ? "slave" : "master",
+	                 zone_name,
+	                 is_master ? "master" : "slave",
 	                 serial,
 	                 next_name,
 	                 when,
-	                 zone->conf->dnssec_enable ? "automatic DNSSEC, resigning at:" : "DNSSEC signing disabled",
-	                 zone->conf->dnssec_enable ? dnssec_info(zone, dnssec_buf, sizeof(dnssec_buf)) : "");
+	                 dnssec_enable ? "automatic DNSSEC, resigning at:" : "DNSSEC signing disabled",
+	                 dnssec_enable ? dnssec_info(zone, dnssec_buf, sizeof(dnssec_buf)) : "");
+	free(zone_name);
 	if (n < 0 || n >= sizeof(buf)) {
 		return KNOT_ESPACE;
 	}
@@ -495,21 +516,21 @@ static int remote_senderr(int c, uint8_t *qbuf, size_t buflen)
 
 /* Public APIs. */
 
-int remote_bind(conf_iface_t *desc)
+int remote_bind(struct sockaddr_storage *addr)
 {
-	if (desc == NULL) {
+	if (addr == NULL) {
 		return KNOT_EINVAL;
 	}
 
-	char addr_str[SOCKADDR_STRLEN] = {0};
-	sockaddr_tostr(addr_str, sizeof(addr_str), &desc->addr);
+	char addr_str[SOCKADDR_STRLEN] = { 0 };
+	sockaddr_tostr(addr_str, sizeof(addr_str), addr);
 	log_info("remote control, binding to '%s'", addr_str);
 
 	/* Create new socket. */
 	mode_t old_umask = umask(KNOT_CTL_SOCKET_UMASK);
-	int sock = net_bound_socket(SOCK_STREAM, &desc->addr, 0);
+	int sock = net_bound_socket(SOCK_STREAM, addr, 0);
 	if (sock == KNOT_EADDRNOTAVAIL) {
-		sock = net_bound_socket(SOCK_STREAM, &desc->addr, NET_BIND_NONLOCAL);
+		sock = net_bound_socket(SOCK_STREAM, addr, NET_BIND_NONLOCAL);
 		if (sock >= 0) {
 			log_warning("remote control, address '%s' is not available",
 			            addr_str);
@@ -533,16 +554,16 @@ int remote_bind(conf_iface_t *desc)
 	return sock;
 }
 
-int remote_unbind(conf_iface_t *desc, int sock)
+int remote_unbind(struct sockaddr_storage *addr, int sock)
 {
-	if (desc == NULL || sock < 0) {
+	if (addr == NULL || sock < 0) {
 		return KNOT_EINVAL;
 	}
 
 	/* Remove control socket file.  */
-	if (desc->addr.ss_family == AF_UNIX) {
-		char addr_str[SOCKADDR_STRLEN] = {0};
-		sockaddr_tostr(addr_str, sizeof(addr_str), &desc->addr);
+	if (addr->ss_family == AF_UNIX) {
+		char addr_str[SOCKADDR_STRLEN] = { 0 };
+		sockaddr_tostr(addr_str, sizeof(addr_str), addr);
 		unlink(addr_str);
 	}
 
@@ -852,7 +873,7 @@ static int zones_verify_tsig_query(const knot_pkt_t *query,
 	return ret;
 }
 
-int remote_process(server_t *s, conf_iface_t *ctl_if, int sock,
+int remote_process(server_t *s, struct sockaddr_storage *ctl_addr, int sock,
                    uint8_t* buf, size_t buflen)
 {
 	knot_pkt_t *pkt =  knot_pkt_new(buf, buflen, NULL);
@@ -876,40 +897,38 @@ int remote_process(server_t *s, conf_iface_t *ctl_if, int sock,
 
 	/* Parse packet and answer if OK. */
 	int ret = remote_parse(pkt);
-	if (ret == KNOT_EOK && ctl_if->addr.ss_family != AF_UNIX) {
-
-		/* Check ACL list. */
-		char addr_str[SOCKADDR_STRLEN] = {0};
+	if (ret == KNOT_EOK && ctl_addr->ss_family != AF_UNIX) {
+		char addr_str[SOCKADDR_STRLEN] = { 0 };
 		sockaddr_tostr(addr_str, sizeof(addr_str), &ss);
-		knot_tsig_key_t *tsig_key = NULL;
-		const knot_dname_t *tsig_name = NULL;
+
+		/* Prepare tsig parameters. */
+		knot_tsig_key_t tsig = { NULL };
 		if (pkt->tsig_rr) {
-			tsig_name = pkt->tsig_rr->owner;
+			tsig.name = pkt->tsig_rr->owner;
+			tsig.algorithm = knot_tsig_rdata_alg(pkt->tsig_rr);
 		}
-		conf_iface_t *match = acl_find(&conf()->ctl.allow, &ss, tsig_name);
-		uint16_t ts_rc = 0;
-		uint16_t ts_trc = 0;
-		uint64_t ts_tmsigned = 0;
-		if (match == NULL) {
+
+		/* Check ACL. */
+		rcu_read_lock();
+		conf_val_t acl = conf_get(conf(), C_CTL, C_ACL);
+		bool allowed = acl_allowed(&acl, ACL_ACTION_CNTL, &ss, &tsig);
+		rcu_read_unlock();
+
+		if (!allowed) {
 			log_warning("remote control, denied '%s', "
 			            "no matching ACL", addr_str);
 			remote_senderr(client, pkt->wire, pkt->size);
 			ret = KNOT_EACCES;
 			goto finish;
-		} else {
-			tsig_key = match->key;
 		}
 
 		/* Check TSIG. */
-		if (tsig_key) {
-			if (pkt->tsig_rr == NULL) {
-				log_warning("remote control, denied '%s', "
-				            "key required", addr_str);
-				remote_senderr(client, pkt->wire, pkt->size);
-				ret = KNOT_EACCES;
-				goto finish;
-			}
-			ret = zones_verify_tsig_query(pkt, tsig_key, &ts_rc,
+		if (tsig.name != NULL) {
+			uint16_t ts_rc = 0;
+			uint16_t ts_trc = 0;
+			uint64_t ts_tmsigned = 0;
+
+			ret = zones_verify_tsig_query(pkt, &tsig, &ts_rc,
 			                              &ts_trc, &ts_tmsigned);
 			if (ret != KNOT_EOK) {
 				log_warning("remote control, denied '%s', "
diff --git a/src/knot/ctl/remote.h b/src/knot/ctl/remote.h
index 19ecdc535d..b0981c9fd4 100644
--- a/src/knot/ctl/remote.h
+++ b/src/knot/ctl/remote.h
@@ -26,37 +26,37 @@
 
 #pragma once
 
-#include "knot/conf/conf.h"
 #include "libknot/packet/pkt.h"
 #include "libknot/rrset.h"
 #include "libknot/dnssec/key.h"
 #include "knot/server/server.h"
 
-/*! \brief Default remote control tool port. */
-#define REMOTE_DPORT 5533
+/*! \brief Default connection control parameters. */
+#define REMOTE_PORT	5533
+#define REMOTE_SOCKET	"knot.sock"
 
 /*!
- * \brief Bind RC interface according to configuration.
+ * \brief Bind RC interface.
  *
- * \param desc Interface descriptor (address, port).
+ * \param addr Interface descriptor (address, port).
  *
  * \retval socket if passed.
  * \retval knot_error else.
  */
-int remote_bind(conf_iface_t *desc);
+int remote_bind(struct sockaddr_storage *addr);
 
 /*!
  * \brief Unbind from RC interface and close socket.
  *
  * \note Breaks all pending connections.
  *
- * \param desc Interface descriptor (address, port).
+ * \param addr Interface descriptor (address, port).
  * \param socket Interface socket
  *
  * \retval KNOT_EOK on success.
  * \retval knot_error else.
  */
-int remote_unbind(conf_iface_t *desc, int sock);
+int remote_unbind(struct sockaddr_storage *addr, int sock);
 
 /*!
  * \brief Poll new events on RC socket.
@@ -112,7 +112,7 @@ int remote_answer(int sock, server_t *s, knot_pkt_t *pkt);
  * \note This should be used as a high-level API for workers.
  *
  * \param s Server instance.
- * \param ctl_if Control interface.
+ * \param ctl_addr Control interface address.
  * \param sock RC interface socket.
  * \param buf Buffer for commands/responses.
  * \param buflen Maximum buffer size.
@@ -120,7 +120,7 @@ int remote_answer(int sock, server_t *s, knot_pkt_t *pkt);
  * \retval KNOT_EOK on success.
  * \retval knot_error else.
  */
-int remote_process(server_t *s, conf_iface_t *ctl_if, int sock,
+int remote_process(server_t *s, struct sockaddr_storage *ctl_addr, int sock,
                    uint8_t* buf, size_t buflen);
 
 /* Functions for creating RC packets. */
diff --git a/src/knot/dnssec/policy.c b/src/knot/dnssec/policy.c
index ad8883e6bb..3974c50c5f 100644
--- a/src/knot/dnssec/policy.c
+++ b/src/knot/dnssec/policy.c
@@ -21,9 +21,6 @@
 #include "knot/zone/contents.h"
 #include "libknot/rrtype/soa.h"
 
-#define MINIMAL_RRSIG_LIFETIME (3 * 60 * 60)
-#define DEFAULT_RRSIG_LIFETIME (30 * 24 * 60 * 60)
-
 static uint32_t zone_soa_min_ttl(const zone_contents_t *zone)
 {
 	knot_rrset_t soa = node_rrset(zone->apex, KNOT_RRTYPE_SOA);
@@ -47,16 +44,11 @@ void update_policy_from_zone(dnssec_kasp_policy_t *policy,
 	policy->zone_maximal_ttl = 0; // TODO
 }
 
-void set_default_policy(dnssec_kasp_policy_t *policy, const conf_zone_t *config,
+void set_default_policy(dnssec_kasp_policy_t *policy,
                         const zone_contents_t *zone)
 {
-	if (config->sig_lifetime <= 0) {
-		policy->rrsig_lifetime = DEFAULT_RRSIG_LIFETIME;
-	} else if (config->sig_lifetime < MINIMAL_RRSIG_LIFETIME) {
-		policy->rrsig_lifetime = MINIMAL_RRSIG_LIFETIME;
-	} else {
-		policy->rrsig_lifetime = config->sig_lifetime;
-	}
+	conf_val_t val = conf_zone_get(conf(), C_SIG_LIFETIME, zone->apex->owner);
+	policy->rrsig_lifetime = conf_int(&val);
 	policy->rrsig_refresh_before = policy->rrsig_lifetime / 10;
 	policy->algorithm = 0;
 	policy->propagation_delay = 0;
diff --git a/src/knot/dnssec/policy.h b/src/knot/dnssec/policy.h
index 1dbec19a8e..fe33649033 100644
--- a/src/knot/dnssec/policy.h
+++ b/src/knot/dnssec/policy.h
@@ -29,5 +29,5 @@ void update_policy_from_zone(dnssec_kasp_policy_t *policy,
 /*!
  * \brief Set default DNSSEC policy for zone without assigned policy.
  */
-void set_default_policy(dnssec_kasp_policy_t *policy, const conf_zone_t *config,
+void set_default_policy(dnssec_kasp_policy_t *policy,
                         const zone_contents_t *zone);
diff --git a/src/knot/dnssec/zone-events.c b/src/knot/dnssec/zone-events.c
index 660908c6d2..fc3aaf845c 100644
--- a/src/knot/dnssec/zone-events.c
+++ b/src/knot/dnssec/zone-events.c
@@ -38,14 +38,28 @@ static bool has_policy(const kdnssec_ctx_t *ctx)
 	return ctx && ctx->policy && ctx->policy->name;
 }
 
-static int sign_init(const zone_contents_t *zone, const conf_zone_t *config,
-                     int flags, kdnssec_ctx_t *ctx)
+static int sign_init(const zone_contents_t *zone, int flags, kdnssec_ctx_t *ctx)
 {
 	assert(zone);
-	assert(config);
 	assert(ctx);
 
-	int r = kdnssec_ctx_init(ctx, config->dnssec_keydir, config->name);
+	const knot_dname_t *zone_name = zone->apex->owner;
+
+	conf_val_t val = conf_zone_get(conf(), C_STORAGE, zone_name);
+	char *storage = conf_abs_path(&val, NULL);
+	val = conf_zone_get(conf(), C_DNSSEC_KEYDIR, zone_name);
+	char *keydir = conf_abs_path(&val, storage);
+	free(storage);
+
+	char *zone_name_str = knot_dname_to_str_alloc(zone_name);
+	if (zone_name_str == NULL) {
+		free(keydir);
+		return KNOT_ENOMEM;
+	}
+
+	int r = kdnssec_ctx_init(ctx, keydir, zone_name_str);
+	free(zone_name_str);
+	free(keydir);
 	if (r != KNOT_EOK) {
 		return r;
 	}
@@ -55,7 +69,7 @@ static int sign_init(const zone_contents_t *zone, const conf_zone_t *config,
 	if (has_policy(ctx)) {
 		update_policy_from_zone(ctx->policy, zone);
 	} else {
-		set_default_policy(ctx->policy, config, zone);
+		set_default_policy(ctx->policy, zone);
 	}
 
 	ctx->policy->nsec3_enabled = knot_is_nsec3_enabled(zone); // TODO: temporary
@@ -70,7 +84,8 @@ static int sign_init(const zone_contents_t *zone, const conf_zone_t *config,
 	if (flags & ZONE_SIGN_KEEP_SOA_SERIAL) {
 		ctx->new_serial = ctx->old_serial;
 	} else {
-		ctx->new_serial = serial_next(ctx->old_serial, config->serial_policy);
+		val = conf_zone_get(conf(), C_SERIAL_POLICY, zone_name);
+		ctx->new_serial = serial_next(ctx->old_serial, conf_opt(&val));
 	}
 
 	return KNOT_EOK;
@@ -162,11 +177,10 @@ static uint32_t schedule_next(kdnssec_ctx_t *kctx, const zone_keyset_t *keyset,
 	return next;
 }
 
-int knot_dnssec_zone_sign(zone_contents_t *zone, const conf_zone_t *config,
-                          changeset_t *out_ch, zone_sign_flags_t flags,
-                          uint32_t *refresh_at)
+int knot_dnssec_zone_sign(zone_contents_t *zone, changeset_t *out_ch,
+                          zone_sign_flags_t flags, uint32_t *refresh_at)
 {
-	if (!zone || !config || !out_ch || !refresh_at) {
+	if (!zone || !out_ch || !refresh_at) {
 		return KNOT_EINVAL;
 	}
 
@@ -177,7 +191,7 @@ int knot_dnssec_zone_sign(zone_contents_t *zone, const conf_zone_t *config,
 
 	// signing pipeline
 
-	result = sign_init(zone, config, flags, &ctx);
+	result = sign_init(zone, flags, &ctx);
 	if (result != KNOT_EOK) {
 		log_zone_error(zone_name, "DNSSEC, failed to initialize (%s)",
 		knot_strerror(result));
@@ -245,7 +259,6 @@ done:
 }
 
 int knot_dnssec_sign_changeset(const zone_contents_t *zone,
-                               conf_zone_t *config,
                                const changeset_t *in_ch,
                                changeset_t *out_ch,
                                uint32_t *refresh_at)
@@ -261,7 +274,7 @@ int knot_dnssec_sign_changeset(const zone_contents_t *zone,
 
 	// signing pipeline
 
-	result = sign_init(zone, config, ZONE_SIGN_KEEP_SOA_SERIAL, &ctx);
+	result = sign_init(zone, ZONE_SIGN_KEEP_SOA_SERIAL, &ctx);
 	if (result != KNOT_EOK) {
 		log_zone_error(zone_name, "DNSSEC, failed to initialize (%s)",
 		knot_strerror(result));
diff --git a/src/knot/dnssec/zone-events.h b/src/knot/dnssec/zone-events.h
index 5f2445c756..e163eb8be8 100644
--- a/src/knot/dnssec/zone-events.h
+++ b/src/knot/dnssec/zone-events.h
@@ -44,22 +44,19 @@ typedef enum zone_sign_flags zone_sign_flags_t;
  *        and NSEC(3) records will not be changed.
  *
  * \param zone         Zone contents to be signed.
- * \param zone_config  Zone/DNSSEC configuration.
  * \param out_ch       New records will be added to this changeset.
  * \param flags        Zone signing flags.
  * \param refresh_at   Signature refresh time of the oldest signature in zone.
  *
  * \return Error code, KNOT_EOK if successful.
  */
-int knot_dnssec_zone_sign(zone_contents_t *zone, const conf_zone_t *zone_config,
-                          changeset_t *out_ch, zone_sign_flags_t flags,
-                          uint32_t *refresh_at);
+int knot_dnssec_zone_sign(zone_contents_t *zone, changeset_t *out_ch,
+                          zone_sign_flags_t flags, uint32_t *refresh_at);
 
 /*!
  * \brief Sign changeset created by DDNS or zone-diff.
  *
  * \param zone            Zone contents to be signed.
- * \param zone_config     Zone/DNSSEC configuration.
  * \param in_ch           Changeset created bvy DDNS or zone-diff
  * \param out_ch          New records will be added to this changeset.
  * \param refresh_at      Signature refresh time of the new signatures.
@@ -67,7 +64,6 @@ int knot_dnssec_zone_sign(zone_contents_t *zone, const conf_zone_t *zone_config,
  * \return Error code, KNOT_EOK if successful.
  */
 int knot_dnssec_sign_changeset(const zone_contents_t *zone,
-                               conf_zone_t *zone_config,
                                const changeset_t *in_ch,
                                changeset_t *out_ch,
                                uint32_t *refresh_at);
diff --git a/src/knot/main.c b/src/knot/main.c
index 8bbcac1742..44a72b1e75 100644
--- a/src/knot/main.c
+++ b/src/knot/main.c
@@ -14,13 +14,14 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <dirent.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <getopt.h>
 #include <sys/stat.h>
-#include <limits.h>
+#include <urcu.h>
 
 #ifdef HAVE_CAP_NG_H
 #include <cap-ng.h>
@@ -35,6 +36,7 @@
 #include "knot/ctl/process.h"
 #include "knot/ctl/remote.h"
 #include "knot/conf/conf.h"
+#include "knot/common/log.h"
 #include "knot/server/server.h"
 #include "knot/server/tcp-handler.h"
 #include "knot/zone/timers.h"
@@ -142,7 +144,19 @@ static void event_loop(server_t *server)
 	/* Bind to control interface. */
 	uint8_t buf[KNOT_WIRE_MAX_PKTSIZE];
 	size_t buflen = sizeof(buf);
-	int remote = remote_bind(conf()->ctl.iface);
+
+	conf_val_t listen_val = conf_get(conf(), C_CTL, C_LISTEN);
+	conf_val_t rundir_val = conf_get(conf(), C_SRV, C_RUNDIR);
+	char *rundir = conf_abs_path(&rundir_val, NULL);
+	struct sockaddr_storage addr = conf_addr(&listen_val, rundir);
+	free(rundir);
+
+	int remote = remote_bind(&addr);
+	if (remote < 0) {
+		log_fatal("failed to bind control socket (%s)",
+		          knot_strerror(remote));
+		return;
+	}
 
 	sigset_t empty;
 	sigemptyset(&empty);
@@ -153,8 +167,7 @@ static void event_loop(server_t *server)
 
 		/* Events. */
 		if (ret > 0) {
-			ret = remote_process(server, conf()->ctl.iface,
-			                     remote, buf, buflen);
+			ret = remote_process(server, &addr, remote, buf, buflen);
 			if (ret == KNOT_CTL_STOP) {
 				break;
 			}
@@ -173,7 +186,7 @@ static void event_loop(server_t *server)
 	server_stop(server);
 
 	/* Close remote control interface. */
-	remote_unbind(conf()->ctl.iface, remote);
+	remote_unbind(&addr, remote);
 
 	/* Wait for server to finish. */
 	server_wait(server);
@@ -185,9 +198,12 @@ static void help(void)
 	       PACKAGE_NAME);
 	printf("\nParameters:\n"
 	       " -c, --config <file>     Select configuration file.\n"
+	       "                           (default %s)\n"
+	       " -C, --confdb <dir>      Select configuration database directory.\n"
 	       " -d, --daemonize=[dir]   Run server as a daemon.\n"
 	       " -V, --version           Print version of the server.\n"
-	       " -h, --help              Print help and usage.\n");
+	       " -h, --help              Print help and usage.\n",
+	       CONF_DEFAULT_FILE);
 }
 
 int main(int argc, char **argv)
@@ -195,24 +211,28 @@ int main(int argc, char **argv)
 	/* Parse command line arguments. */
 	int c = 0, li = 0;
 	int daemonize = 0;
-	const char *config_fn = conf_find_default();
+	const char *config_fn = CONF_DEFAULT_FILE;
+	const char *config_db = NULL;
 	const char *daemon_root = "/";
 
 	/* Long options. */
 	struct option opts[] = {
-		{"config",    required_argument, 0, 'c'},
+		{"config",    required_argument, 0, 'c' },
+		{"confdb",    required_argument, 0, 'C' },
 		{"daemonize", optional_argument, 0, 'd'},
 		{"version",   no_argument,       0, 'V'},
 		{"help",      no_argument,       0, 'h'},
 		{0, 0, 0, 0}
 	};
 
-	while ((c = getopt_long(argc, argv, "c:dVh", opts, &li)) != -1) {
-		switch (c)
-		{
+	while ((c = getopt_long(argc, argv, "c:C:dVh", opts, &li)) != -1) {
+		switch (c) {
 		case 'c':
 			config_fn = optarg;
 			break;
+		case 'C':
+			config_db = optarg;
+			break;
 		case 'd':
 			daemonize = 1;
 			if (optarg) {
@@ -266,41 +286,70 @@ int main(int argc, char **argv)
 	log_init();
 
 	/* Open configuration. */
-	int res = conf_open(config_fn);
-	conf_t *config = conf();
+	conf_t *new_conf = NULL;
+	if (config_db == NULL) {
+		int ret = conf_new(&new_conf, conf_scheme, NULL);
+		if (ret != KNOT_EOK) {
+			log_fatal("failed to initialize configuration database "
+			          "(%s)", knot_strerror(ret));
+			return EXIT_FAILURE;
+		}
+
+		/* Import the configuration file. */
+		ret = conf_import(new_conf, config_fn, true);
+		if (ret != KNOT_EOK) {
+			log_fatal("failed to load configuration file '%s' (%s)",
+			          config_fn, knot_strerror(ret));
+			conf_free(new_conf, false);
+			return EXIT_FAILURE;
+		}
+
+		new_conf->filename = strdup(config_fn);
+	} else {
+		/* Open configuration database. */
+		int ret = conf_new(&new_conf, conf_scheme, config_db);
+		if (ret != KNOT_EOK) {
+			log_fatal("failed to open configuration database '%s' "
+			          "(%s)", config_db, knot_strerror(ret));
+			return EXIT_FAILURE;
+		}
+	}
+
+	/* Run post-open config operations. */
+	int res = conf_post_open(new_conf);
 	if (res != KNOT_EOK) {
-		log_fatal("failed to load configuration file '%s' (%s)",
-		          config_fn, knot_strerror(res));
+		log_fatal("failed to use configuration (%s)", knot_strerror(res));
+		conf_free(new_conf, false);
 		return EXIT_FAILURE;
 	}
 
-	/* Initialize logging subsystem.
-	 * @note We're logging since now. */
-	conf_log_reconfigure(config, NULL);
-	conf_add_hook(config, CONF_LOG, conf_log_reconfigure, NULL);
+	conf_update(new_conf);
+
+	/* Initialize logging subsystem. */
+	log_reconfigure(conf(), NULL);
 
 	/* Initialize server. */
 	server_t server;
-	res = server_init(&server, conf_bg_threads(config));
+	res = server_init(&server, conf_bg_threads(conf()));
 	if (res != KNOT_EOK) {
 		log_fatal("failed to initialize server (%s)", knot_strerror(res));
-		conf_free(conf());
+		conf_free(conf(), false);
 		log_close();
 		return EXIT_FAILURE;
 	}
 
 	/* Reconfigure server interfaces.
 	 * @note This MUST be done before we drop privileges. */
-	server_reconfigure(config, &server);
-	conf_add_hook(config, CONF_ALL, server_reconfigure, &server);
-	log_info("configured %zu interfaces and %zu zones",
-	         list_size(&config->ifaces), hattrie_weight(config->zones));
+	server_reconfigure(conf(), &server);
+	log_info("configured %zu zones", conf_id_count(conf(), C_ZONE));
 
 	/* Alter privileges. */
-	log_update_privileges(config->uid, config->gid);
-	if (proc_update_privileges(config->uid, config->gid) != KNOT_EOK) {
+	int uid, gid;
+	conf_user(conf(), &uid, &gid);
+	log_update_privileges(uid, gid);
+	if (proc_update_privileges(uid, gid) != KNOT_EOK) {
 		server_deinit(&server);
-		conf_free(conf());
+		conf_free(conf(), false);
 		log_close();
 		return EXIT_FAILURE;
 	}
@@ -312,7 +361,7 @@ int main(int argc, char **argv)
 		pidfile = pid_check_and_create();
 		if (pidfile == NULL) {
 			server_deinit(&server);
-			conf_free(conf());
+			conf_free(conf(), false);
 			log_close();
 			return EXIT_FAILURE;
 		}
@@ -329,10 +378,9 @@ int main(int argc, char **argv)
 	/* Now we're going multithreaded. */
 	rcu_register_thread();
 
-	/* Populate zone database and add reconfiguration hook. */
+	/* Populate zone database. */
 	log_info("loading zones");
-	server_update_zones(config, &server);
-	conf_add_hook(config, CONF_ALL, server_update_zones, &server);
+	server_update_zones(conf(), &server);
 
 	/* Check number of loaded zones. */
 	if (knot_zonedb_size(server.zone_db) == 0) {
@@ -341,14 +389,15 @@ int main(int argc, char **argv)
 
 	/* Start it up. */
 	log_info("starting server");
-	res = server_start(&server, config->async_start);
+	conf_val_t async_val = conf_get(conf(), C_SRV, C_ASYNC_START);
+	res = server_start(&server, conf_bool(&async_val));
 	if (res != KNOT_EOK) {
 		log_fatal("failed to start server (%s)", knot_strerror(res));
 		server_deinit(&server);
 		rcu_unregister_thread();
 		pid_cleanup(pidfile);
 		log_close();
-		conf_free(conf());
+		conf_free(conf(), false);
 		return EXIT_FAILURE;
 	}
 
@@ -360,14 +409,13 @@ int main(int argc, char **argv)
 	}
 
 	/* Start the event loop. */
-	config = NULL; /* @note Invalidate pointer, as it may change now. */
 	event_loop(&server);
 
 	/* Teardown server and configuration. */
 	server_deinit(&server);
 
 	/* Free configuration. */
-	conf_free(conf());
+	conf_free(conf(), false);
 
 	/* Unhook from RCU. */
 	rcu_unregister_thread();
diff --git a/src/knot/nameserver/axfr.c b/src/knot/nameserver/axfr.c
index 2ef6af3492..dcf59a66a1 100644
--- a/src/knot/nameserver/axfr.c
+++ b/src/knot/nameserver/axfr.c
@@ -14,6 +14,8 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <urcu.h>
+
 #include "knot/nameserver/axfr.h"
 #include "knot/nameserver/internet.h"
 #include "knot/nameserver/process_query.h"
@@ -108,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->zone->conf->acl.xfr_out, qdata);
+	NS_NEED_AUTH(qdata, qdata->zone->name, ACL_ACTION_XFER);
 	/* Check expiration. */
 	NS_NEED_ZONE_CONTENTS(qdata, KNOT_RCODE_SERVFAIL);
 
@@ -240,7 +242,7 @@ int axfr_query_process(knot_pkt_t *pkt, struct query_data *qdata)
 	}
 
 	/* Reserve space for TSIG. */
-	knot_pkt_reserve(pkt, knot_tsig_wire_maxsize(qdata->sign.tsig_key));
+	knot_pkt_reserve(pkt, knot_tsig_wire_maxsize(&qdata->sign.tsig_key));
 
 	/* Answer current packet (or continue). */
 	struct axfr_proc *axfr = (struct axfr_proc *)qdata->ext;
diff --git a/src/knot/nameserver/chaos.c b/src/knot/nameserver/chaos.c
index 086ca8e7af..2c8ff893b4 100644
--- a/src/knot/nameserver/chaos.c
+++ b/src/knot/nameserver/chaos.c
@@ -19,6 +19,7 @@
 #include "knot/nameserver/chaos.h"
 #include "knot/conf/conf.h"
 #include "libknot/descriptor.h"
+#include "libknot/errcode.h"
 #include "libknot/packet/pkt.h"
 
 /*!
@@ -29,14 +30,24 @@ static const char *get_txt_response_string(const knot_dname_t *qname)
 	char *qname_str = knot_dname_to_str_alloc(qname);
 	const char *response = NULL;
 
-	/* id.server and hostname.bind should have similar meaning */
+	/* id.server and hostname.bind should have similar meaning. */
 	if (strcasecmp("id.server.",     qname_str) == 0 ||
 	    strcasecmp("hostname.bind.", qname_str) == 0) {
-		response = conf()->identity;
-	/* allow both version version.{server, bind}. for compatibility */
+		conf_val_t val = conf_get(conf(), C_SRV, C_IDENT);
+		response = conf_str(&val);
+		/* Empty string data (including '\0') means auto. */
+		if (val.code == KNOT_EOK && val.len <= 1) {
+			response = conf()->hostname;
+		}
+	/* Allow both version version.{server, bind}. for compatibility. */
 	} else if (strcasecmp("version.server.", qname_str) == 0 ||
 	           strcasecmp("version.bind.",   qname_str) == 0) {
-		response = conf()->version;
+		conf_val_t val = conf_get(conf(), C_SRV, C_VERSION);
+		response = conf_str(&val);
+		/* Empty string data (including '\0') means auto. */
+		if (val.code == KNOT_EOK && val.len <= 1) {
+			response = "Knot DNS " PACKAGE_VERSION;
+		}
 	}
 
 	free(qname_str);
@@ -57,7 +68,7 @@ static const char *get_txt_response_string(const knot_dname_t *qname)
 static int create_txt_rrset(knot_rrset_t *rrset, const knot_dname_t *owner,
                             const char *response, mm_ctx_t *mm)
 {
-	// truncate response to one TXT label
+	/* Truncate response to one TXT label. */
 	size_t response_len = strlen(response);
 	if (response_len > KNOT_DNAME_MAXLEN) {
 		response_len = KNOT_DNAME_MAXLEN;
diff --git a/src/knot/nameserver/internet.c b/src/knot/nameserver/internet.c
index 1f0c43d68f..f383364a52 100644
--- a/src/knot/nameserver/internet.c
+++ b/src/knot/nameserver/internet.c
@@ -24,6 +24,7 @@
 #include "knot/nameserver/nsec_proofs.h"
 #include "knot/nameserver/process_query.h"
 #include "knot/nameserver/process_answer.h"
+#include "knot/nameserver/query_module.h"
 #include "knot/zone/serial.h"
 #include "knot/zone/zonedb.h"
 
@@ -187,9 +188,11 @@ static int put_answer(knot_pkt_t *pkt, uint16_t type, struct query_data *qdata)
 	int ret = KNOT_EOK;
 	switch (type) {
 	case KNOT_RRTYPE_ANY: /* Append all RRSets. */ {
+		conf_val_t val = conf_zone_get(conf(), C_DISABLE_ANY,
+		                               qdata->zone->name);
 		/* If ANY not allowed, set TC bit. */
 		if ((qdata->param->proc_flags & NS_QUERY_LIMIT_ANY) &&
-		    (qdata->zone->conf->disable_any)) {
+		    conf_bool(&val)) {
 			dbg_ns("%s: ANY/UDP disabled for this zone TC=1\n", __func__);
 			knot_wire_set_tc(pkt->wire);
 			return KNOT_ESPACE;
@@ -829,10 +832,10 @@ 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->zone->conf->acl.xfr_out, qdata);
+		NS_NEED_AUTH(qdata, qdata->zone->name, ACL_ACTION_XFER);
 
 		/* Reserve space for TSIG. */
-		knot_pkt_reserve(response, knot_tsig_wire_maxsize(qdata->sign.tsig_key));
+		knot_pkt_reserve(response, knot_tsig_wire_maxsize(&qdata->sign.tsig_key));
 	}
 
 	NS_NEED_ZONE_CONTENTS(qdata, KNOT_RCODE_SERVFAIL); /* Expired */
@@ -841,12 +844,11 @@ int internet_query(knot_pkt_t *response, struct query_data *qdata)
 	qdata->name = knot_pkt_qname(qdata->query);
 
 	/* If the zone doesn't have a query plan, go for fast default. */
-	conf_zone_t *zone_config = qdata->zone->conf;
-	if (zone_config->query_plan == NULL) {
+	if (qdata->zone->query_plan == NULL) {
 		return default_answer(response, qdata);
 	}
 
-	return planned_answer(zone_config->query_plan, response, qdata);
+	return planned_answer(qdata->zone->query_plan, response, qdata);
 }
 
 int internet_query_plan(struct query_plan *plan)
diff --git a/src/knot/nameserver/internet.h b/src/knot/nameserver/internet.h
index 91a364229f..a9c87be5b9 100644
--- a/src/knot/nameserver/internet.h
+++ b/src/knot/nameserver/internet.h
@@ -118,8 +118,8 @@ int ns_put_rr(knot_pkt_t *pkt, const knot_rrset_t *rr,
 	}
 
 /*! \brief Require authentication. */
-#define NS_NEED_AUTH(acl, qdata) \
-	if (!process_query_acl_check((acl), (qdata))) { \
+#define NS_NEED_AUTH(qdata, zone_name, action) \
+	if (!process_query_acl_check((zone_name), (action), (qdata))) { \
 		return KNOT_STATE_FAIL; \
 	} else { \
 		if (process_query_verify(qdata) != KNOT_EOK) { \
diff --git a/src/knot/nameserver/ixfr.c b/src/knot/nameserver/ixfr.c
index 98a5be7b1a..28fb3eb440 100644
--- a/src/knot/nameserver/ixfr.c
+++ b/src/knot/nameserver/ixfr.c
@@ -14,6 +14,8 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#include <urcu.h>
+
 #include "knot/nameserver/ixfr.h"
 #include "knot/nameserver/axfr.h"
 #include "knot/nameserver/internet.h"
@@ -164,7 +166,7 @@ static int ixfr_process_changeset(knot_pkt_t *pkt, const void *item,
 #undef IXFR_SAFE_PUT
 
 /*! \brief Loads IXFRs from journal. */
-static int ixfr_load_chsets(list_t *chgsets, zone_t *zone,
+static int ixfr_load_chsets(list_t *chgsets, const zone_t *zone,
                             const knot_rrset_t *their_soa)
 {
 	assert(chgsets);
@@ -178,9 +180,11 @@ static int ixfr_load_chsets(list_t *chgsets, zone_t *zone,
 		return KNOT_EUPTODATE;
 	}
 
-	pthread_mutex_lock(&zone->journal_lock);
-	ret = journal_load_changesets(zone, chgsets, serial_from, serial_to);
-	pthread_mutex_unlock(&zone->journal_lock);
+	char *path = conf_journalfile(conf(), zone->name);
+	pthread_mutex_lock((pthread_mutex_t *)&zone->journal_lock);
+	ret = journal_load_changesets(path, zone, chgsets, serial_from, serial_to);
+	pthread_mutex_unlock((pthread_mutex_t *)&zone->journal_lock);
+	free(path);
 
 	if (ret != KNOT_EOK) {
 		changesets_free(chgsets);
@@ -208,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->zone->conf->acl.xfr_out, qdata);
+	NS_NEED_AUTH(qdata, qdata->zone->name, ACL_ACTION_XFER);
 	NS_NEED_ZONE_CONTENTS(qdata, KNOT_RCODE_SERVFAIL); /* Check expiration. */
 
 	return KNOT_STATE_DONE;
@@ -307,7 +311,7 @@ static int ixfr_answer_soa(knot_pkt_t *pkt, struct query_data *qdata)
 	}
 
 	/* Reserve space for TSIG. */
-	knot_pkt_reserve(pkt, knot_tsig_wire_maxsize(qdata->sign.tsig_key));
+	knot_pkt_reserve(pkt, knot_tsig_wire_maxsize(&qdata->sign.tsig_key));
 
 	/* Guaranteed to have zone contents. */
 	const zone_node_t *apex = qdata->zone->contents->apex;
@@ -649,7 +653,7 @@ int ixfr_query(knot_pkt_t *pkt, struct query_data *qdata)
 	}
 
 	/* Reserve space for TSIG. */
-	knot_pkt_reserve(pkt, knot_tsig_wire_maxsize(qdata->sign.tsig_key));
+	knot_pkt_reserve(pkt, knot_tsig_wire_maxsize(&qdata->sign.tsig_key));
 
 	/* Answer current packet (or continue). */
 	ret = xfr_process_list(pkt, &ixfr_process_changeset, qdata);
diff --git a/src/knot/nameserver/notify.c b/src/knot/nameserver/notify.c
index 9b0a1c2f86..e2a6b2cc39 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(&zone->conf->acl.notify_in, qdata);
+	NS_NEED_AUTH(qdata, zone->name, ACL_ACTION_NOTF);
 
 	return KNOT_STATE_DONE;
 }
@@ -80,7 +80,7 @@ int notify_process_query(knot_pkt_t *pkt, struct query_data *qdata)
 	}
 
 	/* Reserve space for TSIG. */
-	knot_pkt_reserve(pkt, knot_tsig_wire_maxsize(qdata->sign.tsig_key));
+	knot_pkt_reserve(pkt, knot_tsig_wire_maxsize(&qdata->sign.tsig_key));
 
 	/* SOA RR in answer may be included, recover serial. */
 	const knot_pktsection_t *answer = knot_pkt_section(qdata->query, KNOT_ANSWER);
diff --git a/src/knot/nameserver/process_answer.c b/src/knot/nameserver/process_answer.c
index 4bbec51723..a837190284 100644
--- a/src/knot/nameserver/process_answer.c
+++ b/src/knot/nameserver/process_answer.c
@@ -15,6 +15,7 @@
  */
 
 #include "libknot/libknot.h"
+#include "knot/common/log.h"
 #include "knot/nameserver/process_answer.h"
 #include "knot/nameserver/internet.h"
 #include "knot/nameserver/notify.h"
diff --git a/src/knot/nameserver/process_query.c b/src/knot/nameserver/process_query.c
index a9e5c3c91a..02e55c01e0 100644
--- a/src/knot/nameserver/process_query.c
+++ b/src/knot/nameserver/process_query.c
@@ -3,6 +3,7 @@
 
 #include "dnssec/tsig.h"
 #include "knot/nameserver/process_query.h"
+#include "knot/nameserver/query_module.h"
 #include "knot/nameserver/chaos.h"
 #include "knot/nameserver/internet.h"
 #include "knot/nameserver/axfr.h"
@@ -219,7 +220,8 @@ static int answer_edns_init(const knot_pkt_t *query, knot_pkt_t *resp,
 	}
 
 	/* Initialize OPT record. */
-	int ret = knot_edns_init(&qdata->opt_rr, conf()->max_udp_payload, 0,
+	conf_val_t val = conf_get(conf(), C_SRV, C_MAX_UDP_PAYLOAD);
+	int ret = knot_edns_init(&qdata->opt_rr, conf_int(&val), 0,
 	                     KNOT_EDNS_VERSION, qdata->mm);
 	if (ret != KNOT_EOK) {
 		return ret;
@@ -236,10 +238,21 @@ static int answer_edns_init(const knot_pkt_t *query, knot_pkt_t *resp,
 	}
 
 	/* Append NSID if requested and available. */
-	if (knot_edns_has_nsid(query->opt_rr) && conf()->nsid_len > 0) {
+	val = conf_get(conf(), C_SRV, C_NSID);
+	if (knot_edns_has_nsid(query->opt_rr) && val.code == KNOT_EOK) {
+		conf_data(&val);
+		const uint8_t *data = val.data;
+		uint16_t len = val.len;
+
+		/* Empty data means automatic value. */
+		if (val.len == 0) {
+			data = (uint8_t *)conf()->hostname;
+			len = strlen(conf()->hostname);
+		}
+
 		ret = knot_edns_add_option(&qdata->opt_rr,
-		                           KNOT_EDNS_OPTION_NSID, conf()->nsid_len,
-		                           (const uint8_t*)conf()->nsid, qdata->mm);
+		                           KNOT_EDNS_OPTION_NSID, len, data,
+		                           qdata->mm);
 		if (ret != KNOT_EOK) {
 			return ret;
 		}
@@ -317,8 +330,9 @@ static int prepare_answer(const knot_pkt_t *query, knot_pkt_t *resp, knot_layer_
 	if (has_limit) {
 		resp->max_size = KNOT_WIRE_MIN_PKTSIZE;
 		if (knot_pkt_has_edns(query)) {
+			conf_val_t val = conf_get(conf(), C_SRV, C_MAX_UDP_PAYLOAD);
 			uint16_t client = knot_edns_get_payload(query->opt_rr);
-			uint16_t server = conf()->max_udp_payload;
+			uint16_t server = conf_int(&val);
 			uint16_t transfer = MIN(client, server);
 			resp->max_size = MAX(resp->max_size, transfer);
 		}
@@ -408,7 +422,8 @@ static int ratelimit_apply(int state, knot_pkt_t *pkt, knot_layer_t *ctx)
 	}
 
 	/* Now it is slip or drop. */
-	if (rrl_slip_roll(conf()->rrl_slip)) {
+	conf_val_t val = conf_get(conf(), C_SRV, C_RATE_LIMIT_SLIP);
+	if (rrl_slip_roll(conf_int(&val))) {
 		/* Answer slips. */
 		if (process_query_err(ctx, pkt) != KNOT_EOK) {
 			return KNOT_STATE_FAIL;
@@ -532,27 +547,27 @@ finish:
 	return next_state;
 }
 
-bool process_query_acl_check(list_t *acl, struct query_data *qdata)
+bool process_query_acl_check(const knot_dname_t *zone_name, acl_action_t action,
+                             struct query_data *qdata)
 {
 	knot_pkt_t *query = qdata->query;
 	const struct sockaddr_storage *query_source = qdata->param->remote;
-	const knot_dname_t *key_name = NULL;
-	dnssec_tsig_algorithm_t key_alg = DNSSEC_TSIG_UNKNOWN;
+	knot_tsig_key_t tsig = { NULL };
 
 	/* Skip if already checked and valid. */
-	if (qdata->sign.tsig_key != NULL) {
+	if (qdata->sign.tsig_key.name != NULL) {
 		return true;
 	}
 
 	/* Authenticate with NOKEY if the packet isn't signed. */
 	if (query->tsig_rr) {
-		key_name = query->tsig_rr->owner;
-		key_alg = knot_tsig_rdata_alg(query->tsig_rr);
+		tsig.name = query->tsig_rr->owner;
+		tsig.algorithm = knot_tsig_rdata_alg(query->tsig_rr);
 	}
-	conf_iface_t *match = acl_find(acl, query_source, key_name);
 
-	/* Did not authenticate, no fitting rule found. */
-	if (match == NULL || (match->key && match->key->algorithm != key_alg)) {
+	/* Check if authenticated. */
+	conf_val_t acl = conf_zone_get(conf(), C_ACL, zone_name);
+	if (!acl_allowed(&acl, action, query_source, &tsig)) {
 		dbg_ns("%s: no ACL match => NOTAUTH\n", __func__);
 		qdata->rcode = KNOT_RCODE_NOTAUTH;
 		qdata->rcode_tsig = KNOT_TSIG_ERR_BADKEY;
@@ -560,7 +575,8 @@ bool process_query_acl_check(list_t *acl, struct query_data *qdata)
 	}
 
 	/* Remember used TSIG key. */
-	qdata->sign.tsig_key = match->key;
+	qdata->sign.tsig_key = tsig;
+
 	return true;
 }
 
@@ -582,7 +598,7 @@ int process_query_verify(struct query_data *qdata)
 	/* Checking query. */
 	process_query_qname_case_restore(qdata, query);
 	int ret = knot_tsig_server_check(query->tsig_rr, query->wire,
-	                                 query->size, ctx->tsig_key);
+	                                 query->size, &ctx->tsig_key);
 	process_query_qname_case_lower(query);
 
 	dbg_ns("%s: QUERY TSIG check result = %s\n", __func__, knot_strerror(ret));
@@ -628,22 +644,22 @@ int process_query_sign_response(knot_pkt_t *pkt, struct query_data *qdata)
 	knot_sign_context_t *ctx = &qdata->sign;
 
 	/* KEY provided and verified TSIG or BADTIME allows signing. */
-	if (ctx->tsig_key != NULL && knot_tsig_can_sign(qdata->rcode_tsig)) {
+	if (ctx->tsig_key.name != NULL && knot_tsig_can_sign(qdata->rcode_tsig)) {
 
 		/* Sign query response. */
-		dbg_ns("%s: signing response using key %p\n", __func__, ctx->tsig_key);
-		size_t new_digest_len = dnssec_tsig_algorithm_size(ctx->tsig_key->algorithm);
+		dbg_ns("%s: signing response using key %p\n", __func__, &ctx->tsig_key);
+		size_t new_digest_len = dnssec_tsig_algorithm_size(ctx->tsig_key.algorithm);
 		if (ctx->pkt_count == 0) {
 			ret = knot_tsig_sign(pkt->wire, &pkt->size, pkt->max_size,
 			                     ctx->tsig_digest, ctx->tsig_digestlen,
 			                     ctx->tsig_digest, &new_digest_len,
-			                     ctx->tsig_key, qdata->rcode_tsig,
+			                     &ctx->tsig_key, qdata->rcode_tsig,
 			                     ctx->tsig_time_signed);
 		} else {
 			ret = knot_tsig_sign_next(pkt->wire, &pkt->size, pkt->max_size,
 			                          ctx->tsig_digest, ctx->tsig_digestlen,
 			                          ctx->tsig_digest, &new_digest_len,
-			                          ctx->tsig_key,
+			                          &ctx->tsig_key,
 			                          pkt->wire, pkt->size);
 		}
 		if (ret != KNOT_EOK) {
diff --git a/src/knot/nameserver/process_query.h b/src/knot/nameserver/process_query.h
index 429d66ff3d..ef691b260d 100644
--- a/src/knot/nameserver/process_query.h
+++ b/src/knot/nameserver/process_query.h
@@ -113,11 +113,13 @@ struct rrsig_info {
 /*!
  * \brief Check current query against ACL.
  *
- * \param acl
- * \param qdata
+ * \param zone_name  Current zone name.
+ * \param action     ACL action.
+ * \param qdata      Query data.
  * \return true if accepted, false if denied.
  */
-bool process_query_acl_check(list_t *acl, struct query_data *qdata);
+bool process_query_acl_check(const knot_dname_t *zone_name, acl_action_t action,
+                             struct query_data *qdata);
 
 /*!
  * \brief Verify current query transaction security and update query data.
diff --git a/src/knot/nameserver/update.c b/src/knot/nameserver/update.c
index 769d2cf673..208eb13a23 100644
--- a/src/knot/nameserver/update.c
+++ b/src/knot/nameserver/update.c
@@ -14,6 +14,8 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#include <urcu.h>
+
 #include "dnssec/random.h"
 #include "knot/nameserver/update.h"
 #include "knot/nameserver/internet.h"
@@ -78,13 +80,12 @@ static int sign_update(zone_t *zone, const zone_contents_t *old_contents,
 	uint32_t refresh_at = 0;
 	if (apex_rr_changed(old_contents, new_contents, KNOT_RRTYPE_DNSKEY) ||
 	    apex_rr_changed(old_contents, new_contents, KNOT_RRTYPE_NSEC3PARAM)) {
-		ret = knot_dnssec_zone_sign(new_contents, zone->conf,
-		                            sec_ch, ZONE_SIGN_KEEP_SOA_SERIAL,
+		ret = knot_dnssec_zone_sign(new_contents, sec_ch,
+		                            ZONE_SIGN_KEEP_SOA_SERIAL,
 		                            &refresh_at);
 	} else {
 		// Sign the created changeset
-		ret = knot_dnssec_sign_changeset(new_contents, zone->conf,
-		                                 ddns_ch, sec_ch,
+		ret = knot_dnssec_sign_changeset(new_contents, ddns_ch, sec_ch,
 		                                 &refresh_at);
 	}
 	if (ret != KNOT_EOK) {
@@ -237,9 +238,12 @@ static int process_normal(zone_t *zone, list_t *requests)
 	}
 	assert(new_contents);
 
+	conf_val_t val = conf_zone_get(conf(), C_DNSSEC_ENABLE, zone->name);
+	bool dnssec_enable = conf_bool(&val);
+
 	// Sign the update.
 	changeset_t sec_ch;
-	if (zone->conf->dnssec_enable) {
+	if (dnssec_enable) {
 		ret = changeset_init(&sec_ch, zone->name);
 		if (ret != KNOT_EOK) {
 			set_rcodes(requests, KNOT_RCODE_SERVFAIL);
@@ -262,19 +266,24 @@ static int process_normal(zone_t *zone, list_t *requests)
 		update_rollback(&ddns_ch);
 		update_free_zone(&new_contents);
 		changeset_clear(&ddns_ch);
-		if (zone->conf->dnssec_enable) {
+		if (dnssec_enable) {
 			changeset_clear(&sec_ch);
 		}
 		set_rcodes(requests, KNOT_RCODE_SERVFAIL);
 		return ret;
 	}
 
+	/* Temporarily unlock locked configuration. */
+	rcu_read_unlock();
+
 	// Switch zone contents.
 	zone_contents_t *old_contents = zone_switch_contents(zone, new_contents);
 	synchronize_rcu();
 
+	rcu_read_lock();
+
 	// Clear DNSSEC changes
-	if (zone->conf->dnssec_enable) {
+	if (dnssec_enable) {
 		update_cleanup(&sec_ch);
 		changeset_clear(&sec_ch);
 	}
@@ -286,7 +295,8 @@ static int process_normal(zone_t *zone, list_t *requests)
 	changeset_clear(&ddns_ch);
 
 	/* Sync zonefile immediately if configured. */
-	if (zone->conf->dbsync_timeout == 0) {
+	val = conf_zone_get(conf(), C_ZONEFILE_SYNC, zone->name);
+	if (conf_int(&val) == 0) {
 		zone_events_schedule(zone, ZONE_EVENT_FLUSH, ZONE_EVENT_NOW);
 	}
 
@@ -331,12 +341,20 @@ static int process_requests(zone_t *zone, list_t *requests)
 
 static int forward_request(zone_t *zone, struct knot_request *request)
 {
+	/* Ignore if DNSSEC enabled. */
+	conf_val_t val = conf_zone_get(conf(), C_DNSSEC_ENABLE, zone->name);
+	if (conf_bool(&val)) {
+		log_zone_notice(zone->name, "ignoring ddns forward due to "
+		                            "enabled automatic DNSSEC signing.");
+		return KNOT_EOK;
+	}
+
 	/* Create requestor instance. */
 	struct knot_requestor re;
 	knot_requestor_init(&re, NULL);
 
 	/* Fetch primary master. */
-	const conf_iface_t *master = zone_master(zone);
+	const conf_remote_t master = zone_master(zone);
 
 	/* Copy request and assign new ID. */
 	knot_pkt_t *query = knot_pkt_new(NULL, request->query->max_size, NULL);
@@ -350,8 +368,8 @@ static int forward_request(zone_t *zone, struct knot_request *request)
 	knot_tsig_append(query->wire, &query->size, query->max_size, query->tsig_rr);
 
 	/* Create a request. */
-	const struct sockaddr *dst = (const struct sockaddr *)&master->addr;
-	const struct sockaddr *src = (const struct sockaddr *)&master->via;
+	const struct sockaddr *dst = (const struct sockaddr *)&master.addr;
+	const struct sockaddr *src = (const struct sockaddr *)&master.via;
 	struct knot_request *req = knot_request_make(re.mm, dst, src, query, 0);
 	if (req == NULL) {
 		knot_pkt_free(&query);
@@ -366,7 +384,8 @@ static int forward_request(zone_t *zone, struct knot_request *request)
 	/* Enqueue and execute request. */
 	ret = knot_requestor_enqueue(&re, req);
 	if (ret == KNOT_EOK) {
-		struct timeval tv = { conf()->max_conn_reply, 0 };
+		conf_val_t val = conf_get(conf(), C_SRV, C_MAX_CONN_REPLY);
+		struct timeval tv = { conf_int(&val), 0 };
 		ret = knot_requestor_exec(&re, &tv);
 	}
 
@@ -400,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->conf->acl.update_in, qdata)) {
+	if (!process_query_acl_check(qdata->zone->name, ACL_ACTION_DDNS, qdata)) {
 		UPDATE_LOG(LOG_WARNING, "ACL check failed");
 		knot_wire_set_rcode(req->resp->wire, qdata->rcode);
 		return false;
@@ -426,7 +445,7 @@ static bool update_tsig_check(struct query_data *qdata, struct knot_request *req
 static void send_update_response(const zone_t *zone, struct knot_request *req)
 {
 	if (req->resp) {
-		if (!zone_master(zone)) {
+		if (zone_is_master(zone)) {
 			// Sign the response with TSIG where applicable
 			struct query_data qdata;
 			init_qdata_from_request(&qdata, zone, req, NULL);
@@ -475,7 +494,7 @@ static int init_update_responses(const zone_t *zone, list_t *updates,
 
 		assert(req->query);
 		knot_pkt_init_response(req->resp, req->query);
-		if (zone_master(zone)) {
+		if (!zone_is_master(zone)) {
 			// Don't check TSIG for forwards.
 			continue;
 		}
@@ -507,7 +526,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(&zone->conf->acl.update_in, qdata);
+	NS_NEED_AUTH(qdata, zone->name, ACL_ACTION_DDNS);
 	/* Check expiration. */
 	NS_NEED_ZONE_CONTENTS(qdata, KNOT_RCODE_SERVFAIL);
 
@@ -533,22 +552,27 @@ int updates_execute(zone_t *zone)
 		return KNOT_EOK;
 	}
 
+	/* Block config changes. */
+	rcu_read_lock();
+
 	/* Init updates respones. */
 	int ret = init_update_responses(zone, &updates, &update_count);
 	if (ret != KNOT_EOK) {
 		/* Send what responses we can. */
 		set_rcodes(&updates, KNOT_RCODE_SERVFAIL);
 		send_update_responses(zone, &updates);
+		rcu_read_unlock();
 		return ret;
 	}
 
 	if (update_count == 0) {
 		/* All updates failed their ACL checks. */
+		rcu_read_unlock();
 		return KNOT_EOK;
 	}
 
 	/* Process update list - forward if zone has master, or execute. */
-	if (zone_master(zone)) {
+	if (!zone_is_master(zone)) {
 		log_zone_info(zone->name,
 		              "DDNS, forwarding %zu updates", update_count);
 		forward_requests(zone, &updates);
@@ -562,5 +586,6 @@ int updates_execute(zone_t *zone)
 	/* Send responses. */
 	send_update_responses(zone, &updates);
 
+	rcu_read_unlock();
 	return KNOT_EOK;
 }
diff --git a/src/knot/server/journal.c b/src/knot/server/journal.c
index cf7658e68b..dc7167a07d 100644
--- a/src/knot/server/journal.c
+++ b/src/knot/server/journal.c
@@ -998,9 +998,10 @@ static int load_changeset(journal_t *journal, journal_node_t *n, const zone_t *z
 	return KNOT_EOK;
 }
 
-int journal_load_changesets(const zone_t *zone, list_t *dst, uint32_t from, uint32_t to)
+int journal_load_changesets(const char *path, const zone_t *zone, list_t *dst,
+                            uint32_t from, uint32_t to)
 {
-	int ret = journal_walk(zone->conf->ixfr_db, from, to, &load_changeset, zone, dst);
+	int ret = journal_walk(path, from, to, &load_changeset, zone, dst);
 	if (ret != KNOT_EOK) {
 		return ret;
 	}
diff --git a/src/knot/server/journal.h b/src/knot/server/journal.h
index adf5cc1ebe..1e502dc065 100644
--- a/src/knot/server/journal.h
+++ b/src/knot/server/journal.h
@@ -169,6 +169,7 @@ bool journal_exists(const char *path);
  * \brief Load changesets from journal.
  *
  * \param path Path to journal file.
+ * \param zone Corresponding zone.
  * \param dst Store changesets here.
  * \param from Start serial.
  * \param to End serial.
@@ -177,7 +178,7 @@ bool journal_exists(const char *path);
  * \retval KNOT_ERANGE if given entry was not found.
  * \return < KNOT_EOK on error.
  */
-int journal_load_changesets(const struct zone *zone, list_t *dst,
+int journal_load_changesets(const char *path, const struct zone *zone, list_t *dst,
                             uint32_t from, uint32_t to);
 
 /*!
diff --git a/src/knot/server/server.c b/src/knot/server/server.c
index 30ba3af7d2..9b76dbacd7 100644
--- a/src/knot/server/server.c
+++ b/src/knot/server/server.c
@@ -14,6 +14,8 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <urcu.h>
+
 #include <unistd.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -93,23 +95,23 @@ static void server_remove_iface(iface_t *iface)
  * \retval 0 if successful (EOK).
  * \retval <0 on errors (EACCES, EINVAL, ENOMEM, EADDRINUSE).
  */
-static int server_init_iface(iface_t *new_if, conf_iface_t *cfg_if)
+static int server_init_iface(iface_t *new_if, struct sockaddr_storage *addr)
 {
 	/* Initialize interface. */
 	int ret = 0;
 	memset(new_if, 0, sizeof(iface_t));
-	memcpy(&new_if->addr, &cfg_if->addr, sizeof(struct sockaddr_storage));
+	memcpy(&new_if->addr, addr, sizeof(struct sockaddr_storage));
 
 	/* Convert to string address format. */
-	char addr_str[SOCKADDR_STRLEN] = {0};
-	sockaddr_tostr(addr_str, sizeof(addr_str), &cfg_if->addr);
+	char addr_str[SOCKADDR_STRLEN] = { 0 };
+	sockaddr_tostr(addr_str, sizeof(addr_str), addr);
 
 	/* Create bound UDP socket. */
 	int bind_flags = 0;
-	int sock = net_bound_socket(SOCK_DGRAM, &cfg_if->addr, bind_flags);
+	int sock = net_bound_socket(SOCK_DGRAM, addr, bind_flags);
 	if (sock == KNOT_EADDRNOTAVAIL) {
 		bind_flags |= NET_BIND_NONLOCAL;
-		sock = net_bound_socket(SOCK_DGRAM, &cfg_if->addr, bind_flags);
+		sock = net_bound_socket(SOCK_DGRAM, addr, bind_flags);
 		if (sock >= 0) {
 			log_warning("address '%s' is not available", addr_str);
 		}
@@ -126,7 +128,7 @@ static int server_init_iface(iface_t *new_if, conf_iface_t *cfg_if)
 	new_if->fd[IO_UDP] = sock;
 
 	/* Create bound TCP socket. */
-	sock = net_bound_socket(SOCK_STREAM, &cfg_if->addr, bind_flags);
+	sock = net_bound_socket(SOCK_STREAM, addr, bind_flags);
 	if (sock < 0) {
 		close(new_if->fd[IO_UDP]);
 		return sock;
@@ -180,12 +182,10 @@ static void remove_ifacelist(struct ref *p)
  * \param server Server instance.
  * \return number of added sockets.
  */
-static int reconfigure_sockets(const struct conf *conf, server_t *s)
+static int reconfigure_sockets(conf_t *conf, server_t *s)
 {
 	/* Prepare helper lists. */
-	char addr_str[SOCKADDR_STRLEN] = {0};
 	int bound = 0;
-	iface_t *m = 0;
 	ifacelist_t *oldlist = s->ifaces;
 	ifacelist_t *newlist = malloc(sizeof(ifacelist_t));
 	ref_init(&newlist->ref, &remove_ifacelist);
@@ -200,16 +200,19 @@ static int reconfigure_sockets(const struct conf *conf, server_t *s)
 	}
 
 	/* Update bound interfaces. */
-	node_t *n = 0;
-	WALK_LIST(n, conf->ifaces) {
+	conf_val_t listen_val = conf_get(conf, C_SRV, C_LISTEN);
+	conf_val_t rundir_val = conf_get(conf, C_SRV, C_RUNDIR);
+	char *rundir = conf_abs_path(&rundir_val, NULL);
+	while (listen_val.code == KNOT_EOK) {
+		iface_t *m = NULL;
 
 		/* Find already matching interface. */
 		int found_match = 0;
-		conf_iface_t *cfg_if = (conf_iface_t*)n;
+		struct sockaddr_storage addr = conf_addr(&listen_val, rundir);
 		if (s->ifaces) {
 			WALK_LIST(m, s->ifaces->u) {
 				/* Matching port and address. */
-				if (sockaddr_cmp(&cfg_if->addr, &m->addr) == 0) {
+				if (sockaddr_cmp(&addr, &m->addr) == 0) {
 					found_match = 1;
 					break;
 				}
@@ -220,12 +223,13 @@ static int reconfigure_sockets(const struct conf *conf, server_t *s)
 		if (found_match) {
 			rem_node((node_t *)m);
 		} else {
-			sockaddr_tostr(addr_str, sizeof(addr_str), &cfg_if->addr);
+			char addr_str[SOCKADDR_STRLEN] = { 0 };
+			sockaddr_tostr(addr_str, sizeof(addr_str), &addr);
 			log_info("binding to interface '%s'", addr_str);
 
 			/* Create new interface. */
 			m = malloc(sizeof(iface_t));
-			if (server_init_iface(m, cfg_if) < 0) {
+			if (server_init_iface(m, &addr) < 0) {
 				free(m);
 				m = 0;
 			}
@@ -236,7 +240,10 @@ static int reconfigure_sockets(const struct conf *conf, server_t *s)
 			add_tail(&newlist->l, (node_t *)m);
 			++bound;
 		}
+
+		conf_val_next(&listen_val);
 	}
+	free(rundir);
 
 	/* Wait for readers that are reconfiguring right now. */
 	/*! \note This subsystem will be reworked in #239 */
@@ -436,27 +443,52 @@ void server_wait(server_t *s)
 
 int server_reload(server_t *server, const char *cf)
 {
-	if (!server || !cf) {
+	if (server == NULL) {
 		return KNOT_EINVAL;
 	}
 
-	log_info("reloading configuration");
-	int cf_ret = conf_open(cf);
-	switch (cf_ret) {
-	case KNOT_EOK:
-		log_info("configuration reloaded");
-		break;
-	case KNOT_ENOENT:
-		log_error("configuration file '%s' not found",
-			  conf()->filename);
-		break;
-	default:
-		log_error("failed to reload the configuration");
-		break;
+	int ret = KNOT_EOK;
+
+	if (cf != NULL) {
+		log_info("reloading configuration file '%s'", cf);
+		conf_t *new_conf = NULL;
+		ret = conf_clone(&new_conf);
+		if (ret != KNOT_EOK) {
+			log_fatal("failed to initialize configuration (%s)",
+			          knot_strerror(ret));
+			return EXIT_FAILURE;
+		}
+
+		/* Import the configuration file. */
+		ret = conf_import(new_conf, cf, true);
+		if (ret != KNOT_EOK) {
+			log_fatal("failed to load configuration file '%s' (%s)",
+			          cf, knot_strerror(ret));
+			conf_free(new_conf, false);
+			return EXIT_FAILURE;
+		}
+
+		/* Run post-open config operations. */
+		int res = conf_post_open(new_conf);
+		if (res != KNOT_EOK) {
+			log_fatal("failed to use configuration (%s)",
+			          knot_strerror(res));
+			conf_free(new_conf, false);
+			return EXIT_FAILURE;
+		}
+
+		conf_update(new_conf);
+	} else {
+		log_info("reloading configuration database");
 	}
 
-	/*! \todo Close and bind to new remote control. */
-	return cf_ret;
+	log_reconfigure(conf(), NULL);
+	server_reconfigure(conf(), server);
+	server_update_zones(conf(), server);
+
+	log_info("configuration reloaded");
+
+	return ret;
 }
 
 void server_stop(server_t *server)
@@ -476,7 +508,7 @@ void server_stop(server_t *server)
 }
 
 /*! \brief Reconfigure UDP and TCP query processing threads. */
-static int reconfigure_threads(const struct conf *conf, server_t *server)
+static int reconfigure_threads(conf_t *conf, server_t *server)
 {
 	/* Estimate number of threads/manager. */
 	int ret = KNOT_EOK;
@@ -520,11 +552,15 @@ static int reconfigure_threads(const struct conf *conf, server_t *server)
 	return ret;
 }
 
-static int reconfigure_rate_limits(const struct conf *conf, server_t *server)
+static int reconfigure_rate_limits(conf_t *conf, server_t *server)
 {
+	conf_val_t val = conf_get(conf, C_SRV, C_RATE_LIMIT);
+	int64_t rrl = conf_int(&val);
+
 	/* Rate limiting. */
-	if (!server->rrl && conf->rrl > 0) {
-		server->rrl = rrl_create(conf->rrl_size);
+	if (!server->rrl && rrl > 0) {
+		val = conf_get(conf, C_SRV, C_RATE_LIMIT_SIZE);
+		server->rrl = rrl_create(conf_int(&val));
 		if (!server->rrl) {
 			log_error("failed to initialize rate limiting table");
 		} else {
@@ -532,16 +568,16 @@ static int reconfigure_rate_limits(const struct conf *conf, server_t *server)
 		}
 	}
 	if (server->rrl) {
-		if (rrl_rate(server->rrl) != (uint32_t)conf->rrl) {
+		if (rrl_rate(server->rrl) != rrl) {
 			/* We cannot free it, threads may use it.
 			 * Setting it to <1 will disable rate limiting. */
-			if (conf->rrl < 1) {
+			if (rrl < 1) {
 				log_info("rate limiting, disabled");
 			} else {
-				log_info("rate limiting, enabled with %u responses/second",
-					 conf->rrl);
+				log_info("rate limiting, enabled with %i responses/second",
+					 (int)rrl);
 			}
-			rrl_setrate(server->rrl, conf->rrl);
+			rrl_setrate(server->rrl, rrl);
 
 		} /* At this point, old buckets will converge to new rate. */
 	}
@@ -549,7 +585,7 @@ static int reconfigure_rate_limits(const struct conf *conf, server_t *server)
 	return KNOT_EOK;
 }
 
-int server_reconfigure(const struct conf *conf, void *data)
+int server_reconfigure(conf_t *conf, void *data)
 {
 	server_t *server = (server_t *)data;
 	dbg_server("%s(%p, %p)\n", __func__, conf, server);
@@ -584,19 +620,22 @@ int server_reconfigure(const struct conf *conf, void *data)
 	return ret;
 }
 
-static void reopen_timers_database(const conf_t *conf, server_t *server)
+static void reopen_timers_database(conf_t *conf, server_t *server)
 {
 	close_timers_db(server->timers_db);
 	server->timers_db = NULL;
 
-	int ret = open_timers_db(conf->storage, &server->timers_db);
+	conf_val_t val = conf_default_get(conf, C_STORAGE);
+	char *storage = conf_abs_path(&val, NULL);
+	int ret = open_timers_db(storage, &server->timers_db);
+	free(storage);
 	if (ret != KNOT_EOK && ret != KNOT_ENOTSUP) {
 		log_warning("cannot open persistent timers DB (%s)",
 		            knot_strerror(ret));
 	}
 }
 
-int server_update_zones(const conf_t *conf, void *data)
+int server_update_zones(conf_t *conf, void *data)
 {
 	server_t *server = (server_t *)data;
 
diff --git a/src/knot/server/server.h b/src/knot/server/server.h
index 413e978cef..1ccff0dcc2 100644
--- a/src/knot/server/server.h
+++ b/src/knot/server/server.h
@@ -178,7 +178,7 @@ void server_stop(server_t *server);
  * \retval KNOT_EINVAL on invalid parameters.
  * \retval KNOT_ERROR unspecified error.
  */
-int server_reconfigure(const struct conf *conf, void *data);
+int server_reconfigure(conf_t *conf, void *data);
 
 /*!
  * \brief Reconfigure zone database.
@@ -187,7 +187,7 @@ int server_reconfigure(const struct conf *conf, void *data);
  *
  * \return KNOT_EOK on success or KNOT_ error
  */
-int server_update_zones(const struct conf *conf, void *data);
+int server_update_zones(conf_t *conf, void *data);
 
 /*!
  * \brief Update fdsets from current interfaces list.
diff --git a/src/knot/server/tcp-handler.c b/src/knot/server/tcp-handler.c
index 3f848e7130..330745b408 100644
--- a/src/knot/server/tcp-handler.c
+++ b/src/knot/server/tcp-handler.c
@@ -24,6 +24,7 @@
 #include <netinet/in.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <urcu.h>
 #ifdef HAVE_SYS_UIO_H			// struct iovec (OpenBSD)
 #include <sys/uio.h>
 #endif // HAVE_SYS_UIO_H
@@ -112,20 +113,20 @@ static int tcp_handle(tcp_context_t *tcp, int fd,
 
 	/* Timeout. */
 	rcu_read_lock();
-	struct timeval tmout = { conf()->max_conn_reply, 0 };
+	conf_val_t val = conf_get(conf(), C_SRV, C_MAX_CONN_REPLY);
+	int64_t max_conn_reply = conf_int(&val);
 	rcu_read_unlock();
+	struct timeval tmout = { max_conn_reply, 0 };
 
 	/* Receive data. */
 	int ret = tcp_recv_msg(fd, rx->iov_base, rx->iov_len, &tmout);
 	if (ret <= 0) {
 		dbg_net("tcp: client on fd=%d disconnected\n", fd);
 		if (ret == KNOT_EAGAIN) {
-			rcu_read_lock();
 			char addr_str[SOCKADDR_STRLEN] = {0};
 			sockaddr_tostr(addr_str, sizeof(addr_str), &ss);
 			log_warning("TCP, connection timed out, address '%s'",
 			            addr_str);
-			rcu_read_unlock();
 		}
 		return KNOT_ECONNREFUSED;
 	} else {
@@ -187,7 +188,8 @@ int tcp_accept(int fd)
 #ifdef SO_RCVTIMEO
 		struct timeval tv;
 		rcu_read_lock();
-		tv.tv_sec = conf()->max_conn_idle;
+		conf_val_t val = conf_get(conf(), C_SRV, C_MAX_CONN_IDLE);
+		tv.tv_sec = conf_int(&val);
 		rcu_read_unlock();
 		tv.tv_usec = 0;
 		if (setsockopt(incoming, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
@@ -215,7 +217,8 @@ static int tcp_event_accept(tcp_context_t *tcp, unsigned i)
 
 		/* Update watchdog timer. */
 		rcu_read_lock();
-		fdset_set_watchdog(&tcp->set, next_id, conf()->max_conn_hs);
+		conf_val_t val = conf_get(conf(), C_SRV, C_MAX_CONN_HANDSHAKE);
+		fdset_set_watchdog(&tcp->set, next_id, conf_int(&val));
 		rcu_read_unlock();
 
 		return KNOT_EOK;
@@ -235,7 +238,8 @@ static int tcp_event_serve(tcp_context_t *tcp, unsigned i)
 	if (ret == KNOT_EOK) {
 		/* Update socket activity timer. */
 		rcu_read_lock();
-		fdset_set_watchdog(&tcp->set, i, conf()->max_conn_idle);
+		conf_val_t val = conf_get(conf(), C_SRV, C_MAX_CONN_IDLE);
+		fdset_set_watchdog(&tcp->set, i, conf_int(&val));
 		rcu_read_unlock();
 	}
 
@@ -254,7 +258,8 @@ static int tcp_wait_for_events(tcp_context_t *tcp)
 	if (!is_throttled) {
 		/* Configuration limit, infer maximal pool size. */
 		rcu_read_lock();
-		unsigned max_per_set = MAX(conf()->max_tcp_clients / conf_tcp_threads(conf()), 1);
+		conf_val_t val = conf_get(conf(), C_SRV, C_MAX_TCP_CLIENTS);
+		unsigned max_per_set = MAX(conf_int(&val) / conf_tcp_threads(conf()), 1);
 		rcu_read_unlock();
 		/* Subtract master sockets check limits. */
 		is_throttled = (set->n - tcp->client_threshold) >= max_per_set;
@@ -320,7 +325,8 @@ int tcp_master(dthread_t *thread)
 	tcp.overlay.mm = &mm;
 
 	/* Prepare structures for bound sockets. */
-	fdset_init(&tcp.set, list_size(&conf()->ifaces) + CONFIG_XFERS);
+	conf_val_t val = conf_get(conf(), C_SRV, C_LISTEN);
+	fdset_init(&tcp.set, conf_val_count(&val) + CONF_XFERS);
 
 	/* Create iovec abstraction. */
 	for (unsigned i = 0; i < 2; ++i) {
diff --git a/src/knot/server/udp-handler.c b/src/knot/server/udp-handler.c
index 4bc6e484c7..77fcc64a68 100644
--- a/src/knot/server/udp-handler.c
+++ b/src/knot/server/udp-handler.c
@@ -29,6 +29,7 @@
 #include <errno.h>
 #include <limits.h>
 #include <sys/param.h>
+#include <urcu.h>
 #ifdef HAVE_SYS_UIO_H /* 'struct iovec' for OpenBSD */
 #include <sys/uio.h>
 #endif /* HAVE_SYS_UIO_H */
diff --git a/src/knot/updates/acl.c b/src/knot/updates/acl.c
index e8fb837bd3..3894835acc 100644
--- a/src/knot/updates/acl.c
+++ b/src/knot/updates/acl.c
@@ -22,135 +22,155 @@
 #include <limits.h>
 #include <stdbool.h>
 
-#include "libknot/libknot.h"
 #include "knot/updates/acl.h"
 #include "knot/conf/conf.h"
+#include "libknot/libknot.h"
 #include "libknot/internal/endian.h"
-#include "libknot/rrtype/tsig.h"
+#include "libknot/internal/sockaddr.h"
 
-static inline uint32_t ipv4_chunk(const struct sockaddr_in *ipv4)
-{
-	/* Stored as big end first. */
-	return ipv4->sin_addr.s_addr;
+static const uint8_t* ipv4_addr(const struct sockaddr_storage *ss) {
+	struct sockaddr_in *ipv4 = (struct sockaddr_in *)ss;
+	return (uint8_t *)&ipv4->sin_addr.s_addr;
 }
 
-static inline uint32_t ipv6_chunk(const struct sockaddr_in6 *ipv6, uint8_t idx)
-{
-	/* Is byte array, 4x 32bit value. */
-	return ((uint32_t *)&ipv6->sin6_addr)[idx];
+static const uint8_t* ipv6_addr(const struct sockaddr_storage *ss) {
+	struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)ss;
+	return (uint8_t *)&ipv6->sin6_addr.s6_addr;
 }
 
-static inline uint32_t ip_chunk(const struct sockaddr_storage *ss, uint8_t idx)
+bool netblock_match(const struct sockaddr_storage *ss1,
+                    const struct sockaddr_storage *ss2,
+                    unsigned prefix)
 {
-	if (ss->ss_family == AF_INET6) {
-		return ipv6_chunk((const struct sockaddr_in6 *)ss, idx);
-	} else {
-		return ipv4_chunk((const struct sockaddr_in *)ss);
+	if (ss1 == NULL || ss2 == NULL) {
+		return false;
 	}
-}
-
-/*! \brief Compare chunks using given mask. */
-static int cmp_chunk(const conf_iface_t *a1, const struct sockaddr_storage *a2,
-                     uint8_t idx, uint32_t mask)
-{
-	const uint32_t c1 = ip_chunk(&a1->addr, idx) & mask;
-	const uint32_t c2 = ip_chunk(a2, idx) & mask;
-
-	if (c1 > c2)
-		return  1;
-	if (c1 < c2)
-		return -1;
-	return 0;
-}
 
-/*!
- * \brief Calculate bitmask for byte array from the MSB.
- *
- * \note i.e. 8 means top 8 bits set, 11111111000000000000000000000000
- *
- * \param nbits number of bits set to 1
- * \return mask
- */
-static uint32_t acl_fill_mask32(short nbits)
-{
-	assert(nbits >= 0 && nbits <= 32);
-	uint32_t r = 0;
-	for (char i = 0; i < nbits; ++i) {
-		r |= 1 << (31 - i);
+	if (ss1->ss_family != ss2->ss_family) {
+		return false;
 	}
 
-	/* Make sure the mask is in network byte order. */
-	return htonl(r);
-}
-
-int netblock_match(struct conf_iface *a1, const struct sockaddr_storage *a2)
-{
-	int ret = 0;
-	uint32_t mask = 0xffffffff;
-	short mask_bits = a1->prefix;
-	const short chunk_bits = sizeof(mask) * CHAR_BIT;
-
-	/* Check different length, IPv4 goes first. */
-	if (a1->addr.ss_family != a2->ss_family) {
-		if (a1->addr.ss_family < a2->ss_family) {
-			return -1;
-		} else {
-			return 1;
-		}
+	const uint8_t *addr1, *addr2;
+	switch (ss1->ss_family) {
+	case AF_INET:
+		addr1 = ipv4_addr(ss1);
+		addr2 = ipv4_addr(ss2);
+		prefix = prefix > IPV4_PREFIXLEN ? IPV4_PREFIXLEN : prefix;
+		break;
+	case AF_INET6:
+		addr1 = ipv6_addr(ss1);
+		addr2 = ipv6_addr(ss2);
+		prefix = prefix > IPV6_PREFIXLEN ? IPV6_PREFIXLEN : prefix;
+		break;
+	default:
+		return false;
 	}
 
-	/* At most 4xchunk_bits for IPv6 */
-	unsigned i = 0;
-	while (ret == 0 && mask_bits > 0) {
-		/* Compute mask for current chunk. */
-		if (mask_bits <= chunk_bits) {
-			mask = acl_fill_mask32(mask_bits);
-			mask_bits = 0; /* Last chunk */
-		} else {
-			mask_bits -= chunk_bits;
+	/* Compare full bytes address block. */
+	uint8_t full_bytes = prefix / 8;
+	for (int i = 0; i < full_bytes; i++) {
+		if (addr1[i] != addr2[i]) {
+			return false;
 		}
+	}
 
-		/* Empty mask - shortcut, we're done. */
-		if (mask > 0) {
-			ret = cmp_chunk(a1, a2, i, mask);
+	/* Compare last partial byte address block. */
+	uint8_t rest_bits = prefix % 8;
+	if (rest_bits > 0) {
+		uint8_t rest1 = addr1[full_bytes] >> (8 - rest_bits);
+		uint8_t rest2 = addr2[full_bytes] >> (8 - rest_bits);
+		if (rest1 != rest2) {
+			return false;
 		}
-		++i;
 	}
 
-	return ret;
+	return true;
 }
 
-struct conf_iface* acl_find(list_t *acl, const struct sockaddr_storage *addr,
-                              const knot_dname_t *key_name)
+bool acl_allowed(conf_val_t *acl, acl_action_t action,
+                 const struct sockaddr_storage *addr,
+                 knot_tsig_key_t *tsig)
 {
-	if (acl == NULL || addr == NULL) {
+	if (acl == NULL || addr == NULL || tsig == NULL) {
 		return NULL;
 	}
 
-	conf_remote_t *remote = NULL;
-	WALK_LIST(remote, *acl) {
-		conf_iface_t *cur = remote->remote;
-		if (netblock_match(cur, addr) == 0) {
-			/* NOKEY entry. */
-			if (cur->key == NULL) {
-				if (key_name == NULL) {
-					return cur;
-				}
-				/* NOKEY entry, but key provided. */
+	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;
+		}
+
+		/* 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) {
+			unsigned prefix;
+			struct sockaddr_storage ss;
+			ss = conf_net(&addr_val, &prefix);
+			if (!netblock_match(addr, &ss, prefix)) {
+				conf_val_next(acl);
 				continue;
 			}
+		}
 
-			/* NOKEY provided, but key required. */
-			if (key_name == NULL) {
+		/* Check if the key matches. */
+		conf_val_t key_val = conf_id_get(conf(), C_ACL, C_KEY, acl);
+		if (key_val.code == KNOT_EOK) {
+			/* No key provided, but required. */
+			if (tsig->name == NULL) {
+				conf_val_next(acl);
 				continue;
 			}
 
-			/* Key name match. */
-			if (knot_dname_is_equal(cur->key->name, key_name)) {
-				return cur;
+			/* 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);
+				continue;
 			}
+
+			/* Compare key algorithms. */
+			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);
+				continue;
+			}
+		/* No key required, but provided. */
+		} else if (tsig->name != NULL) {
+			conf_val_next(acl);
+			continue;
+		}
+
+		if (deny) {
+			conf_val_next(acl);
+			continue;
 		}
+
+		/* Fill the output with tsig secret. */
+		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;
+		}
+
+		return true;
 	}
 
-	return NULL;
+	return false;
 }
diff --git a/src/knot/updates/acl.h b/src/knot/updates/acl.h
index 30885fce12..f271729d27 100644
--- a/src/knot/updates/acl.h
+++ b/src/knot/updates/acl.h
@@ -14,16 +14,11 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 /*!
- * \file acl.h
+ * \file
  *
- * \author Marek Vavrusa <marek.vavrusa@nic.cz>
+ * Access control list.
  *
- * \brief Access control lists.
- *
- * Simple access control list is implemented as a linked list, sorted by
- * prefix length. This way, longest prefix match is always found first.
- *
- * \addtogroup common_lib
+ * \addtogroup server
  * @{
  */
 
@@ -33,23 +28,44 @@
 #include "libknot/internal/sockaddr.h"
 #include "libknot/internal/mempattern.h"
 #include "libknot/rrtype/tsig.h"
+#include "knot/conf/conf.h"
 
-struct conf_iface;
+/*! \brief ACL actions. */
+typedef enum {
+	ACL_ACTION_DENY = 0,
+	ACL_ACTION_XFER = 1,
+	ACL_ACTION_NOTF = 2,
+	ACL_ACTION_DDNS = 3,
+	ACL_ACTION_CNTL = 4
+} acl_action_t;
 
-/*! \brief Match address against netblock. */
-int netblock_match(struct conf_iface *a1, const struct sockaddr_storage *a2);
+/*!
+ * \brief Checks if two netblocks match.
+ *
+ * \param ss1     First address storage.
+ * \param ss2     Second address storage.
+ * \param prefix  Netblock length.
+ */
+bool netblock_match(const struct sockaddr_storage *ss1,
+                    const struct sockaddr_storage *ss2,
+                    unsigned prefix);
 
 /*!
- * \brief Match address against ACL.
+ * \brief Checks if the address and/or tsig key matches given ACL list.
+ *
+ * If a proper ACL rule is found and tsig.name is not empty,
+ * tsig.secret is filled.
  *
- * \param acl Pointer to ACL instance.
- * \param addr IP address.
- * \param key_name TSIG key name (optional)
+ * \param acl      Pointer to ACL config multivalued identifier.
+ * \param action   ACL action.
+ * \param addr     IP address.
+ * \param tsig     TSIG parameters.
  *
- * \retval Matching rule instance if found.
- * \retval NULL if it didn't find a match.
+ * \retval true  if authenticated.
+ * \retval false if not authenticated.
  */
-struct conf_iface* acl_find(list_t *acl, const struct sockaddr_storage *addr,
-                              const knot_dname_t *key_name);
+bool acl_allowed(conf_val_t *acl, acl_action_t action,
+                 const struct sockaddr_storage *addr,
+                 knot_tsig_key_t *tsig);
 
 /*! @} */
diff --git a/src/knot/updates/apply.c b/src/knot/updates/apply.c
index a2f1a3d020..d372403c47 100644
--- a/src/knot/updates/apply.c
+++ b/src/knot/updates/apply.c
@@ -15,6 +15,7 @@
  */
 
 #include <assert.h>
+#include <urcu.h>
 
 #include "knot/updates/apply.h"
 
@@ -453,10 +454,13 @@ int apply_changesets(zone_t *zone, list_t *chsets, zone_contents_t **new_content
 	/*
 	 * Apply the changesets.
 	 */
+	rcu_read_lock();
+	bool is_master = zone_is_master(zone);
+	rcu_read_unlock();
+
 	changeset_t *set = NULL;
-	const bool master = (zone_master(zone) == NULL);
 	WALK_LIST(set, *chsets) {
-		ret = apply_single(contents_copy, set, master);
+		ret = apply_single(contents_copy, set, is_master);
 		if (ret != KNOT_EOK) {
 			updates_rollback(chsets);
 			update_free_zone(&contents_copy);
@@ -495,8 +499,11 @@ int apply_changeset(zone_t *zone, changeset_t *change, zone_contents_t **new_con
 		return ret;
 	}
 
-	const bool master = (zone_master(zone) == NULL);
-	ret = apply_single(contents_copy, change, master);
+	rcu_read_lock();
+	bool is_master = zone_is_master(zone);
+	rcu_read_unlock();
+
+	ret = apply_single(contents_copy, change, is_master);
 	if (ret != KNOT_EOK) {
 		update_rollback(change);
 		update_free_zone(&contents_copy);
diff --git a/src/knot/updates/ddns.c b/src/knot/updates/ddns.c
index 418bebbe34..ed90723dbb 100644
--- a/src/knot/updates/ddns.c
+++ b/src/knot/updates/ddns.c
@@ -17,7 +17,9 @@
 #include <assert.h>
 #include <stdlib.h>
 #include <inttypes.h>
+#include <urcu.h>
 
+#include "knot/common/log.h"
 #include "knot/updates/ddns.h"
 #include "knot/updates/changesets.h"
 #include "knot/updates/zone-update.h"
@@ -971,8 +973,9 @@ int ddns_process_update(const zone_t *zone, const knot_pkt_t *query,
 			return KNOT_ENOMEM;
 		}
 
+		conf_val_t val = conf_zone_get(conf(), C_SERIAL_POLICY, zone->name);
 		uint32_t old_serial = knot_soa_serial(&soa_cpy->rrs);
-		uint32_t new_serial = serial_next(old_serial, zone->conf->serial_policy);
+		uint32_t new_serial = serial_next(old_serial, conf_opt(&val));
 		if (serial_compare(old_serial, new_serial) >= 0) {
 			log_zone_warning(zone->name, "updated serial is lower "
 			                 "than current, serial %u -> %u",
diff --git a/src/knot/zone/events/events.c b/src/knot/zone/events/events.c
index d1d03e523c..37ec9dd109 100644
--- a/src/knot/zone/events/events.c
+++ b/src/knot/zone/events/events.c
@@ -18,6 +18,7 @@
 #include <time.h>
 
 #include "knot/common/evsched.h"
+#include "knot/common/log.h"
 #include "libknot/libknot.h"
 #include "libknot/internal/namedb/namedb.h"
 #include "knot/server/server.h"
diff --git a/src/knot/zone/events/handlers.c b/src/knot/zone/events/handlers.c
index 1a43c96737..f1278d0244 100644
--- a/src/knot/zone/events/handlers.c
+++ b/src/knot/zone/events/handlers.c
@@ -14,15 +14,16 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#include <urcu.h>
+
 #include "dnssec/random.h"
 #include "libknot/libknot.h"
-#include "libknot/processing/requestor.h"
-#include "libknot/rrtype/soa.h"
-
-#include "knot/common/trim.h"
 #include "libknot/internal/mempool.h"
 #include "libknot/internal/macros.h"
+#include "libknot/processing/requestor.h"
 
+#include "knot/common/log.h"
+#include "knot/common/trim.h"
 #include "knot/server/udp-handler.h"
 #include "knot/server/tcp-handler.h"
 #include "knot/updates/changesets.h"
@@ -46,7 +47,7 @@
 
 /*! \brief Zone event logging. */
 #define ZONE_QUERY_LOG(severity, zone, remote, operation, msg...) \
-	NS_PROC_LOG(severity, &remote->addr, zone->name, operation, msg)
+	NS_PROC_LOG(severity, &(remote)->addr, zone->name, operation, msg)
 
 /*! \brief Create zone query packet. */
 static knot_pkt_t *zone_query(const zone_t *zone, uint16_t pkt_type, mm_ctx_t *mm)
@@ -91,7 +92,7 @@ static knot_pkt_t *zone_query(const zone_t *zone, uint16_t pkt_type, mm_ctx_t *m
  * \note Everything in this function is executed synchronously, returns when
  *       the query processing is either complete or an error occurs.
  */
-static int zone_query_execute(zone_t *zone, uint16_t pkt_type, const conf_iface_t *remote)
+static int zone_query_execute(zone_t *zone, uint16_t pkt_type, const conf_remote_t *remote)
 {
 	/* Create a memory pool for this task. */
 	int ret = KNOT_EOK;
@@ -115,7 +116,9 @@ static int zone_query_execute(zone_t *zone, uint16_t pkt_type, const conf_iface_
 	knot_requestor_init(&re, &mm);
 	knot_requestor_overlay(&re, KNOT_STATE_ANSWER, &param);
 
-	tsig_init(&param.tsig_ctx, remote->key);
+	const knot_tsig_key_t *key = remote->key.name != NULL ?
+	                             &remote->key : NULL;
+	tsig_init(&param.tsig_ctx, key);
 
 	ret = tsig_sign_packet(&param.tsig_ctx, query);
 	if (ret != KNOT_EOK) {
@@ -134,7 +137,8 @@ static int zone_query_execute(zone_t *zone, uint16_t pkt_type, const conf_iface_
 	/* Send the queries and process responses. */
 	ret = knot_requestor_enqueue(&re, req);
 	if (ret == KNOT_EOK) {
-		struct timeval tv = { conf()->max_conn_reply, 0 };
+		conf_val_t val = conf_get(conf(), C_SRV, C_MAX_CONN_REPLY);
+		struct timeval tv = { conf_int(&val), 0 };
 		ret = knot_requestor_exec(&re, &tv);
 	}
 
@@ -156,7 +160,7 @@ fail:
 	}
 
 /*! \brief Execute zone transfer request. */
-static int zone_query_transfer(zone_t *zone, const conf_iface_t *master, uint16_t pkt_type)
+static int zone_query_transfer(zone_t *zone, const conf_remote_t *master, uint16_t pkt_type)
 {
 	assert(zone);
 	assert(master);
@@ -215,7 +219,8 @@ static uint32_t soa_graceful_expire(const knot_rdataset_t *soa)
 {
 	// Allow for timeouts.  Otherwise zones with very short
 	// expiry may expire before the timeout is reached.
-	return knot_soa_expire(soa) + 2 * conf()->max_conn_idle;
+	conf_val_t val = conf_get(conf(), C_SRV, C_MAX_CONN_IDLE);
+	return knot_soa_expire(soa) + 2 * conf_int(&val);
 }
 
 /*! \brief Schedule expire event, unless it is already scheduled. */
@@ -235,24 +240,25 @@ int event_reload(zone_t *zone)
 	assert(zone);
 
 	/* Take zone file mtime and load it. */
-	time_t mtime = zonefile_mtime(zone->conf->file);
+	char *filename = conf_zonefile(conf(), zone->name);
+	time_t mtime = zonefile_mtime(filename);
+	free(filename);
 	uint32_t dnssec_refresh = time(NULL);
-	conf_zone_t *zone_config = zone->conf;
-	zone_contents_t *contents = zone_load_contents(zone_config);
+	zone_contents_t *contents = zone_load_contents(conf(), zone->name);
 	if (!contents) {
 		return KNOT_ERROR;
 	}
 
 	/* Store zonefile serial and apply changes from the journal. */
 	zone->zonefile_serial = zone_contents_serial(contents);
-	int result = zone_load_journal(zone, contents);
+	int result = zone_load_journal(conf(), zone, contents);
 	if (result != KNOT_EOK) {
 		goto fail;
 	}
 
 	/* Post load actions - calculate delta, sign with DNSSEC... */
 	/*! \todo issue #242 dnssec signing should occur in the special event */
-	result = zone_load_post(contents, zone, &dnssec_refresh);
+	result = zone_load_post(conf(), contents, zone, &dnssec_refresh);
 	if (result != KNOT_EOK) {
 		if (result == KNOT_ESPACE) {
 			log_zone_error(zone->name, "journal size is too small "
@@ -265,7 +271,7 @@ int event_reload(zone_t *zone)
 	}
 
 	/* Check zone contents consistency. */
-	result = zone_load_check(contents, zone_config);
+	result = zone_load_check(conf(), contents);
 	if (result != KNOT_EOK) {
 		goto fail;
 	}
@@ -280,7 +286,7 @@ int event_reload(zone_t *zone)
 	}
 
 	/* Schedule notify and refresh after load. */
-	if (zone_master(zone)) {
+	if (!zone_is_master(zone)) {
 		zone_events_schedule(zone, ZONE_EVENT_REFRESH, ZONE_EVENT_NOW);
 	}
 	if (!zone_contents_is_empty(contents)) {
@@ -289,12 +295,14 @@ int event_reload(zone_t *zone)
 	}
 
 	/* Schedule zone resign. */
-	if (zone->conf->dnssec_enable) {
+	conf_val_t val = conf_zone_get(conf(), C_DNSSEC_ENABLE, zone->name);
+	if (conf_bool(&val)) {
 		schedule_dnssec(zone, dnssec_refresh);
 	}
 
 	/* Periodic execution. */
-	zone_events_schedule(zone, ZONE_EVENT_FLUSH, zone_config->dbsync_timeout);
+	val = conf_zone_get(conf(), C_ZONEFILE_SYNC, zone->name);
+	zone_events_schedule(zone, ZONE_EVENT_FLUSH, conf_int(&val));
 
 	uint32_t current_serial = zone_contents_serial(zone->contents);
 	log_zone_info(zone->name, "loaded, serial %u -> %u",
@@ -311,9 +319,16 @@ int event_refresh(zone_t *zone)
 {
 	assert(zone);
 
-	const conf_iface_t *master = zone_master(zone);
-	if (master == NULL) {
-		/* If not slave zone, ignore. */
+	/* Ignore if master zone. */
+	if (zone_is_master(zone)) {
+		return KNOT_EOK;
+	}
+
+	/* Ignore if DNSSEC enabled. */
+	conf_val_t val = conf_zone_get(conf(), C_DNSSEC_ENABLE, zone->name);
+	if (conf_bool(&val)) {
+		log_zone_notice(zone->name, "ignoring zone refresh due to "
+		                            "enabled automatic DNSSEC signing.");
 		return KNOT_EOK;
 	}
 
@@ -323,11 +338,12 @@ int event_refresh(zone_t *zone)
 		return KNOT_EOK;
 	}
 
-	int ret = zone_query_execute(zone, KNOT_QUERY_NORMAL, master);
+	const conf_remote_t master = zone_master(zone);
+	int ret = zone_query_execute(zone, KNOT_QUERY_NORMAL, &master);
 	const knot_rdataset_t *soa = zone_soa(zone);
 	if (ret != KNOT_EOK) {
 		/* Log connection errors. */
-		ZONE_QUERY_LOG(LOG_WARNING, zone, master, "SOA query, outgoing",
+		ZONE_QUERY_LOG(LOG_WARNING, zone, &master, "SOA query, outgoing",
 		               "failed (%s)", knot_strerror(ret));
 		/* Rotate masters if current failed. */
 		zone_master_rotate(zone);
@@ -346,9 +362,16 @@ int event_xfer(zone_t *zone)
 {
 	assert(zone);
 
-	const conf_iface_t *master = zone_master(zone);
-	if (master == NULL) {
-		/* If not slave zone, ignore. */
+	/* Ignore if master zone. */
+	if (zone_is_master(zone)) {
+		return KNOT_EOK;
+	}
+
+	/* Ignore if DNSSEC enabled. */
+	conf_val_t val = conf_zone_get(conf(), C_DNSSEC_ENABLE, zone->name);
+	if (conf_bool(&val)) {
+		log_zone_notice(zone->name, "ignoring slave transfer due to "
+		                            "enabled automatic DNSSEC signing.");
 		return KNOT_EOK;
 	}
 
@@ -360,7 +383,8 @@ int event_xfer(zone_t *zone)
 	}
 
 	/* Execute zone transfer and reschedule timers. */
-	int ret = zone_query_transfer(zone, master, pkt_type);
+	const conf_remote_t master = zone_master(zone);
+	int ret = zone_query_transfer(zone, &master, pkt_type);
 
 	/* Handle failure during transfer. */
 	if (ret != KNOT_EOK) {
@@ -383,10 +407,12 @@ int event_xfer(zone_t *zone)
 	zone_events_schedule(zone, ZONE_EVENT_REFRESH, knot_soa_refresh(soa));
 	zone_events_schedule(zone, ZONE_EVENT_NOTIFY,  ZONE_EVENT_NOW);
 	zone_events_cancel(zone, ZONE_EVENT_EXPIRE);
-	if (zone->conf->dbsync_timeout == 0) {
+	val = conf_zone_get(conf(), C_ZONEFILE_SYNC, zone->name);
+	int64_t dbsync_timeout = conf_int(&val);
+	if (dbsync_timeout == 0) {
 		zone_events_schedule(zone, ZONE_EVENT_FLUSH, ZONE_EVENT_NOW);
 	} else if (!zone_events_is_scheduled(zone, ZONE_EVENT_FLUSH)) {
-		zone_events_schedule(zone, ZONE_EVENT_FLUSH, zone->conf->dbsync_timeout);
+		zone_events_schedule(zone, ZONE_EVENT_FLUSH, dbsync_timeout);
 	}
 
 	/* Transfer cleanup. */
@@ -451,7 +477,8 @@ int event_flush(zone_t *zone)
 	assert(zone);
 
 	/* Reschedule. */
-	int next_timeout = zone->conf->dbsync_timeout;
+	conf_val_t val = conf_zone_get(conf(), C_ZONEFILE_SYNC, zone->name);
+	int64_t next_timeout = conf_int(&val);
 	if (next_timeout > 0) {
 		zone_events_schedule(zone, ZONE_EVENT_FLUSH, next_timeout);
 	}
@@ -474,19 +501,22 @@ int event_notify(zone_t *zone)
 	}
 
 	/* Walk through configured remotes and send messages. */
-	conf_remote_t *remote = 0;
-	WALK_LIST(remote, zone->conf->acl.notify_out) {
-		conf_iface_t *iface = remote->remote;
+	conf_val_t val = conf_zone_get(conf(), C_NOTIFY, zone->name);
+	while (val.code == KNOT_EOK) {
+		conf_remote_t remote = conf_remote(conf(), &val);
 
-		int ret = zone_query_execute(zone, KNOT_QUERY_NOTIFY, iface);
+		int ret = zone_query_execute(zone, KNOT_QUERY_NOTIFY, &remote);
 		if (ret == KNOT_EOK) {
-			ZONE_QUERY_LOG(LOG_INFO, zone, iface, "NOTIFY, outgoing",
-			               "serial %u",
+			ZONE_QUERY_LOG(LOG_INFO, zone, &remote,
+			               "NOTIFY, outgoing", "serial %u",
 			               zone_contents_serial(zone->contents));
 		} else {
-			ZONE_QUERY_LOG(LOG_WARNING, zone, iface, "NOTIFY, outgoing",
-			               "failed (%s)", knot_strerror(ret));
+			ZONE_QUERY_LOG(LOG_WARNING, zone, &remote,
+			               "NOTIFY, outgoing", "failed (%s)",
+			               knot_strerror(ret));
 		}
+
+		conf_val_next(&val);
 	}
 
 	return KNOT_EOK;
@@ -515,7 +545,7 @@ int event_dnssec(zone_t *zone)
 		sign_flags = 0;
 	}
 
-	ret = knot_dnssec_zone_sign(zone->contents, zone->conf, &ch, sign_flags, &refresh_at);
+	ret = knot_dnssec_zone_sign(zone->contents, &ch, sign_flags, &refresh_at);
 	if (ret != KNOT_EOK) {
 		goto done;
 	}
@@ -552,7 +582,8 @@ int event_dnssec(zone_t *zone)
 
 	schedule_dnssec(zone, refresh_at);
 	zone_events_schedule(zone, ZONE_EVENT_NOTIFY, ZONE_EVENT_NOW);
-	if (zone->conf->dbsync_timeout == 0) {
+	conf_val_t val = conf_zone_get(conf(), C_ZONEFILE_SYNC, zone->name);
+	if (conf_int(&val) == 0) {
 		zone_events_schedule(zone, ZONE_EVENT_FLUSH, ZONE_EVENT_NOW);
 	}
 
@@ -573,3 +604,4 @@ uint32_t bootstrap_next(uint32_t timer)
 	}
 	return timer;
 }
+
diff --git a/src/knot/zone/events/replan.c b/src/knot/zone/events/replan.c
index 6823badd65..7147e64a6d 100644
--- a/src/knot/zone/events/replan.c
+++ b/src/knot/zone/events/replan.c
@@ -35,12 +35,12 @@ static void replan_event(zone_t *zone, const zone_t *old_zone, zone_event_type_t
 /*!< \brief Replans events that are dependent on the SOA record. */
 static void replan_soa_events(zone_t *zone, const zone_t *old_zone)
 {
-	if (!zone_master(zone)) {
+	if (zone_is_master(zone)) {
 		// Events only valid for slaves.
 		return;
 	}
 
-	if (zone_master(old_zone)) {
+	if (!zone_is_master(old_zone)) {
 		// Replan SOA events.
 		replan_event(zone, old_zone, ZONE_EVENT_REFRESH);
 		replan_event(zone, old_zone, ZONE_EVENT_EXPIRE);
@@ -58,12 +58,12 @@ static void replan_soa_events(zone_t *zone, const zone_t *old_zone)
 /*!< \brief Replans transfer event. */
 static void replan_xfer(zone_t *zone, const zone_t *old_zone)
 {
-	if (!zone_master(zone)) {
+	if (zone_is_master(zone)) {
 		// Only valid for slaves.
 		return;
 	}
 
-	if (zone_master(old_zone)) {
+	if (!zone_is_master(old_zone)) {
 		// Replan the transfer from old zone.
 		replan_event(zone, old_zone, ZONE_EVENT_XFER);
 	} else if (zone_contents_is_empty(zone->contents)) {
@@ -76,7 +76,9 @@ static void replan_xfer(zone_t *zone, const zone_t *old_zone)
 /*!< \brief Replans flush event. */
 static void replan_flush(zone_t *zone, const zone_t *old_zone)
 {
-	if (zone->conf->dbsync_timeout <= 0) {
+	conf_val_t val = conf_zone_get(conf(), C_ZONEFILE_SYNC, zone->name);
+	int64_t dbsync_timeout = conf_int(&val);
+	if (dbsync_timeout <= 0) {
 		// Immediate sync scheduled after events.
 		return;
 	}
@@ -84,12 +86,12 @@ static void replan_flush(zone_t *zone, const zone_t *old_zone)
 	const time_t flush_time = zone_events_get_time(old_zone, ZONE_EVENT_FLUSH);
 	if (flush_time <= ZONE_EVENT_NOW) {
 		// Not scheduled previously.
-		zone_events_schedule(zone, ZONE_EVENT_FLUSH, zone->conf->dbsync_timeout);
+		zone_events_schedule(zone, ZONE_EVENT_FLUSH, dbsync_timeout);
 		return;
 	}
 
 	// Pick time to schedule: either reuse or schedule sooner than old event.
-	const time_t schedule_at = MIN(time(NULL) + zone->conf->dbsync_timeout, flush_time);
+	const time_t schedule_at = MIN(time(NULL) + dbsync_timeout, flush_time);
 	zone_events_schedule_at(zone, ZONE_EVENT_FLUSH, schedule_at);
 }
 
@@ -109,7 +111,8 @@ static void duplicate_ddns_q(zone_t *zone, zone_t *old_zone)
 /*!< Replans DNSSEC event. Not whole resign needed, \todo #247 */
 static void replan_dnssec(zone_t *zone)
 {
-	if (zone->conf->dnssec_enable) {
+	conf_val_t val = conf_zone_get(conf(), C_DNSSEC_ENABLE, zone->name);
+	if (conf_bool(&val)) {
 		/* Keys could have changed, force resign. */
 		zone_events_schedule(zone, ZONE_EVENT_DNSSEC, ZONE_EVENT_NOW);
 	}
diff --git a/src/knot/zone/serial.c b/src/knot/zone/serial.c
index e622b307b2..271d7016a6 100644
--- a/src/knot/zone/serial.c
+++ b/src/knot/zone/serial.c
@@ -38,9 +38,9 @@ int serial_compare(uint32_t s1, uint32_t s2)
 int serial_next(uint32_t current, int policy)
 {
 	switch (policy) {
-	case CONF_SERIAL_INCREMENT:
+	case SERIAL_POLICY_INCREMENT:
 		return current + 1;
-	case CONF_SERIAL_UNIXTIME:
+	case SERIAL_POLICY_UNIXTIME:
 		return time(NULL);
 	default:
 		assert(0);
diff --git a/src/knot/zone/serial.h b/src/knot/zone/serial.h
index 2fd3a4f7ac..3db76bb9bd 100644
--- a/src/knot/zone/serial.h
+++ b/src/knot/zone/serial.h
@@ -31,7 +31,7 @@ int serial_compare(uint32_t s1, uint32_t s2);
  * \brief Get next serial for given serial update policy.
  *
  * \param current  Current SOA serial.
- * \param policy   CONF_SERIAL_INCREMENT or CONF_SERIAL_UNIXTIME.
+ * \param policy   SERIAL_POLICY_INCREMENT or SERIAL_POLICY_UNIXTIME.
  *
  * \return New serial.
  */
diff --git a/src/knot/zone/zone-load.c b/src/knot/zone/zone-load.c
index d52ab2ae27..b087f59926 100644
--- a/src/knot/zone/zone-load.c
+++ b/src/knot/zone/zone-load.c
@@ -14,7 +14,6 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-#include "libknot/libknot.h"
 #include "knot/common/log.h"
 #include "knot/server/journal.h"
 #include "knot/zone/zone-diff.h"
@@ -23,15 +22,18 @@
 #include "knot/zone/zonefile.h"
 #include "knot/dnssec/zone-events.h"
 #include "knot/updates/apply.h"
-#include "libknot/rdata.h"
+#include "libknot/libknot.h"
 
-zone_contents_t *zone_load_contents(conf_zone_t *zone_config)
+zone_contents_t *zone_load_contents(conf_t *conf, const knot_dname_t *zone_name)
 {
-	assert(zone_config);
+	assert(conf);
+	assert(zone_name);
 
 	zloader_t zl;
-	int ret = zonefile_open(&zl, zone_config->file, zone_config->name,
-	                        zone_config->enable_checks);
+	char *zonefile = conf_zonefile(conf, zone_name);
+	conf_val_t val = conf_zone_get(conf, C_SEM_CHECKS, zone_name);
+	int ret = zonefile_open(&zl, zonefile, zone_name, conf_bool(&val));
+	free(zonefile);
 	if (ret != KNOT_EOK) {
 		return NULL;
 	}
@@ -39,7 +41,7 @@ zone_contents_t *zone_load_contents(conf_zone_t *zone_config)
 	/* Set the zone type (master/slave). If zone has no master set, we
 	 * are the primary master for this zone (i.e. zone type = master).
 	 */
-	zl.creator->master = !zone_load_can_bootstrap(zone_config);
+	zl.creator->master = !zone_load_can_bootstrap(conf, zone_name);
 
 	zone_contents_t *zone_contents = zonefile_load(&zl);
 	zonefile_close(&zl);
@@ -51,7 +53,7 @@ zone_contents_t *zone_load_contents(conf_zone_t *zone_config)
 }
 
 /*! \brief Check zone configuration constraints. */
-int zone_load_check(zone_contents_t *contents, conf_zone_t *zone_config)
+int zone_load_check(conf_t *conf, zone_contents_t *contents)
 {
 	/* Bootstrapped zone, no checks apply. */
 	if (contents == NULL) {
@@ -62,11 +64,12 @@ int zone_load_check(zone_contents_t *contents, conf_zone_t *zone_config)
 
 	/* Check minimum EDNS0 payload if signed. (RFC4035/sec. 3) */
 	if (zone_contents_is_signed(contents)) {
-		if (conf()->max_udp_payload < KNOT_EDNS_MIN_DNSSEC_PAYLOAD) {
-			log_zone_warning(zone_name, "EDNS payload size is "
-			                 "lower than %u bytes for DNSSEC zone",
-					 KNOT_EDNS_MIN_DNSSEC_PAYLOAD);
-			conf()->max_udp_payload = KNOT_EDNS_MIN_DNSSEC_PAYLOAD;
+		conf_val_t val = conf_get(conf, C_SRV, C_MAX_UDP_PAYLOAD);
+		if (conf_int(&val) < KNOT_EDNS_MIN_DNSSEC_PAYLOAD) {
+			log_zone_error(zone_name, "EDNS payload size is "
+			               "lower than %u bytes for DNSSEC zone",
+			               KNOT_EDNS_MIN_DNSSEC_PAYLOAD);
+			return KNOT_EPAYLOAD;
 		}
 	}
 
@@ -84,11 +87,13 @@ int zone_load_check(zone_contents_t *contents, conf_zone_t *zone_config)
 /*!
  * \brief Apply changesets to zone from journal.
  */
-int zone_load_journal(zone_t *zone, zone_contents_t *contents)
+int zone_load_journal(conf_t *conf, zone_t *zone, zone_contents_t *contents)
 {
 	/* Check if journal is used and zone is not empty. */
-	if (!journal_exists(zone->conf->ixfr_db) ||
+	char *journal_name = conf_journalfile(conf, zone->name);
+	if (!journal_exists(journal_name) ||
 	    zone_contents_is_empty(contents)) {
+		free(journal_name);
 		return KNOT_EOK;
 	}
 
@@ -100,8 +105,10 @@ int zone_load_journal(zone_t *zone, zone_contents_t *contents)
 	init_list(&chgs);
 
 	pthread_mutex_lock(&zone->journal_lock);
-	int ret = journal_load_changesets(zone, &chgs, serial, serial - 1);
+	int ret = journal_load_changesets(journal_name, zone, &chgs, serial,
+	                                  serial - 1);
 	pthread_mutex_unlock(&zone->journal_lock);
+	free(journal_name);
 
 	if ((ret != KNOT_EOK && ret != KNOT_ERANGE) || EMPTY_LIST(chgs)) {
 		changesets_free(&chgs);
@@ -124,14 +131,14 @@ int zone_load_journal(zone_t *zone, zone_contents_t *contents)
 	return ret;
 }
 
-int zone_load_post(zone_contents_t *contents, zone_t *zone, uint32_t *dnssec_refresh)
+int zone_load_post(conf_t *conf, zone_contents_t *contents, zone_t *zone,
+                   uint32_t *dnssec_refresh)
 {
 	if (zone == NULL) {
 		return KNOT_EINVAL;
 	}
 
 	int ret = KNOT_EOK;
-	const conf_zone_t *conf = zone->conf;
 	changeset_t change;
 	ret = changeset_init(&change, zone->name);
 	if (ret != KNOT_EOK) {
@@ -139,9 +146,12 @@ int zone_load_post(zone_contents_t *contents, zone_t *zone, uint32_t *dnssec_ref
 	}
 
 	/* Sign zone using DNSSEC (if configured). */
-	if (conf->dnssec_enable) {
-		assert(conf->build_diffs);
-		ret = knot_dnssec_zone_sign(contents, conf, &change, 0, dnssec_refresh);
+	conf_val_t val = conf_zone_get(conf, C_DNSSEC_ENABLE, zone->name);
+	bool dnssec_enable = conf_bool(&val);
+	val = conf_zone_get(conf, C_IXFR_DIFF, zone->name);
+	bool build_diffs = conf_bool(&val);
+	if (dnssec_enable) {
+		ret = knot_dnssec_zone_sign(contents, &change, 0, dnssec_refresh);
 		if (ret != KNOT_EOK) {
 			changeset_clear(&change);
 			return ret;
@@ -160,9 +170,9 @@ int zone_load_post(zone_contents_t *contents, zone_t *zone, uint32_t *dnssec_ref
 		}
 	}
 
-	/* Calculate IXFR from differences (if configured). */
+	/* Calculate IXFR from differences (if configured or auto DNSSEC). */
 	const bool contents_changed = zone->contents && (contents != zone->contents);
-	if (contents_changed && conf->build_diffs) {
+	if (contents_changed && (build_diffs || dnssec_enable)) {
 		/* Replace changes from zone signing, the resulting diff will cover
 		 * those changes as well. */
 		changeset_clear(&change);
@@ -197,7 +207,10 @@ int zone_load_post(zone_contents_t *contents, zone_t *zone, uint32_t *dnssec_ref
 	return ret;
 }
 
-bool zone_load_can_bootstrap(const conf_zone_t *zone_config)
+bool zone_load_can_bootstrap(conf_t *conf, const knot_dname_t *zone_name)
 {
-	return zone_config && !EMPTY_LIST(zone_config->acl.xfr_in);
+	conf_val_t val = conf_zone_get(conf, C_MASTER, zone_name);
+	size_t count = conf_val_count(&val);
+
+	return count > 0;
 }
diff --git a/src/knot/zone/zone-load.h b/src/knot/zone/zone-load.h
index dca148b990..75020a1a17 100644
--- a/src/knot/zone/zone-load.h
+++ b/src/knot/zone/zone-load.h
@@ -24,40 +24,47 @@
 /*!
  * \brief Load zone contents according to the configuration.
  *
- * \param zone_config
+ * \param conf
+ * \param zone_name
  * \return new zone contents or NULL
  */
-zone_contents_t *zone_load_contents(conf_zone_t *zone_config);
+zone_contents_t *zone_load_contents(conf_t *conf, const knot_dname_t *zone_name);
 
 /*!
  * \brief Check loaded zone contents validity.
  *
+ * \param conf
  * \param contents
- * \param zone_config
  * \return KNOT_EOK or an error
  */
-int zone_load_check(zone_contents_t *contents, conf_zone_t *zone_config);
+int zone_load_check(conf_t *conf, zone_contents_t *contents);
 
 /*!
  * \brief Update zone contents from the journal.
  *
+ * \param conf
  * \param zone
  * \param contents
  * \return KNOT_EOK or an error
  */
-int zone_load_journal(zone_t *zone, zone_contents_t *contents);
+int zone_load_journal(conf_t *conf, zone_t *zone, zone_contents_t *contents);
 
 /*!
  * \brief Zone loading post-actions (zone resign, calculation of delta)
  *
+ * \param conf
  * \param contents
  * \param zone
  * \param dnssec_refresh
  * \return KNOT_EOK or an error
  */
-int zone_load_post(zone_contents_t *contents, zone_t *zone, uint32_t *dnssec_refresh);
+int zone_load_post(conf_t *conf, zone_contents_t *contents, zone_t *zone,
+                   uint32_t *dnssec_refresh);
 
 /*!
  * \brief Check if zone can be bootstrapped.
+ *
+ * \param conf
+ * \param zone_name
  */
-bool zone_load_can_bootstrap(const conf_zone_t *zone_config);
+bool zone_load_can_bootstrap(conf_t *conf, const knot_dname_t *zone_name);
diff --git a/src/knot/zone/zone.c b/src/knot/zone/zone.c
index 58ec417961..218f7e201b 100644
--- a/src/knot/zone/zone.c
+++ b/src/knot/zone/zone.c
@@ -18,11 +18,13 @@
 #include <assert.h>
 #include <string.h>
 #include <sys/stat.h>
+#include <urcu.h>
 
 #include "dnssec/random.h"
 #include "libknot/descriptor.h"
 #include "knot/common/evsched.h"
 #include "libknot/internal/lists.h"
+#include "knot/common/log.h"
 #include "knot/common/trim.h"
 #include "knot/zone/node.h"
 #include "knot/zone/serial.h"
@@ -35,8 +37,11 @@
 #include "libknot/libknot.h"
 #include "libknot/dname.h"
 #include "libknot/internal/utils.h"
+#include "libknot/internal/mem.h"
 #include "libknot/rrtype/soa.h"
 
+#define JOURNAL_SUFFIX	".diff.db"
+
 static void free_ddns_queue(zone_t *z)
 {
 	struct knot_request *n = NULL;
@@ -49,28 +54,20 @@ static void free_ddns_queue(zone_t *z)
 	}
 }
 
-zone_t* zone_new(conf_zone_t *conf)
+zone_t* zone_new(const knot_dname_t *name)
 {
-	if (!conf) {
-		return NULL;
-	}
-
 	zone_t *zone = malloc(sizeof(zone_t));
 	if (zone == NULL) {
 		return NULL;
 	}
 	memset(zone, 0, sizeof(zone_t));
 
-	zone->name = knot_dname_from_str_alloc(conf->name);
-	knot_dname_to_lower(zone->name);
+	zone->name = knot_dname_copy(name, NULL);
 	if (zone->name == NULL) {
 		free(zone);
 		return NULL;
 	}
 
-	// Configuration
-	zone->conf = conf;
-
 	// DDNS
 	pthread_mutex_init(&zone->ddns_lock, NULL);
 	zone->ddns_queue_size = 0;
@@ -101,9 +98,6 @@ void zone_free(zone_t **zone_ptr)
 	pthread_mutex_destroy(&zone->ddns_lock);
 	pthread_mutex_destroy(&zone->journal_lock);
 
-	/* Free assigned config. */
-	conf_free_zone(zone->conf);
-
 	/* Free zone contents. */
 	zone_contents_deep_free(&zone->contents);
 
@@ -116,21 +110,25 @@ int zone_change_store(zone_t *zone, changeset_t *change)
 	assert(zone);
 	assert(change);
 
-	conf_zone_t *conf = zone->conf;
+	conf_val_t val = conf_zone_get(conf(), C_IXFR_FSLIMIT, zone->name);
+	int64_t ixfr_fslimit = conf_int(&val);
+	char *journal_file = conf_journalfile(conf(), zone->name);
 
 	pthread_mutex_lock(&zone->journal_lock);
-	int ret = journal_store_changeset(change, conf->ixfr_db, conf->ixfr_fslimit);
+	int ret = journal_store_changeset(change, journal_file, ixfr_fslimit);
 	if (ret == KNOT_EBUSY) {
 		log_zone_notice(zone->name, "journal is full, flushing");
 
 		/* Transaction rolled back, journal released, we may flush. */
 		ret = zone_flush_journal(zone);
 		if (ret == KNOT_EOK) {
-			ret = journal_store_changeset(change, conf->ixfr_db, conf->ixfr_fslimit);
+			ret = journal_store_changeset(change, journal_file, ixfr_fslimit);
 		}
 	}
 	pthread_mutex_unlock(&zone->journal_lock);
 
+	free(journal_file);
+
 	return ret;
 }
 
@@ -139,22 +137,26 @@ int zone_changes_store(zone_t *zone, list_t *chgs)
 	assert(zone);
 	assert(chgs);
 
-	conf_zone_t *conf = zone->conf;
+	conf_val_t val = conf_zone_get(conf(), C_IXFR_FSLIMIT, zone->name);
+	int64_t ixfr_fslimit = conf_int(&val);
+	char *journal_file = conf_journalfile(conf(), zone->name);
 
 	pthread_mutex_lock(&zone->journal_lock);
-	int ret = journal_store_changesets(chgs, conf->ixfr_db, conf->ixfr_fslimit);
-
+	int ret = journal_store_changesets(chgs, journal_file, ixfr_fslimit);
 	if (ret == KNOT_EBUSY) {
 		log_zone_notice(zone->name, "journal is full, flushing");
 
 		/* Transaction rolled back, journal released, we may flush. */
 		ret = zone_flush_journal(zone);
 		if (ret == KNOT_EOK) {
-			ret = journal_store_changesets(chgs, conf->ixfr_db, conf->ixfr_fslimit);
+			ret = journal_store_changesets(chgs, journal_file, ixfr_fslimit);
 		}
+
 	}
 	pthread_mutex_unlock(&zone->journal_lock);
 
+	free(journal_file);
+
 	return ret;
 }
 
@@ -171,29 +173,35 @@ zone_contents_t *zone_switch_contents(zone_t *zone, zone_contents_t *new_content
 	return old_contents;
 }
 
-const conf_iface_t *zone_master(const zone_t *zone)
+bool zone_is_master(const zone_t *zone)
 {
-	if (zone == NULL) {
-		return NULL;
-	}
+	conf_val_t val = conf_zone_get(conf(), C_MASTER, zone->name);
+	return conf_val_count(&val) > 0 ? false : true;
+}
 
-	if (EMPTY_LIST(zone->conf->acl.xfr_in)) {
-		return NULL;
+conf_remote_t zone_master(const zone_t *zone)
+{
+	conf_val_t val = conf_zone_get(conf(), C_MASTER, zone->name);
+
+	/* Seek the current master if possible. */
+	if (zone->master_index < conf_val_count(&val)) {
+		for (size_t index = 0; index < zone->master_index; index++) {
+			conf_val_next(&val);
+		}
 	}
 
-	conf_remote_t *master = HEAD(zone->conf->acl.xfr_in);
-	return master->remote;
+	return conf_remote(conf(), &val);
 }
 
-void zone_master_rotate(const zone_t *zone)
+void zone_master_rotate(zone_t *zone)
 {
+	conf_val_t val = conf_zone_get(conf(), C_MASTER, zone->name);
 
-	list_t *master_list = &zone->conf->acl.xfr_in;
-	if (list_size(master_list) < 2) {
-		return;
+	if (zone->master_index + 2 <= conf_val_count(&val)) {
+		zone->master_index += 1;
+	} else {
+		zone->master_index = 0;
 	}
-
-	add_tail(master_list, HEAD(*master_list));
 }
 
 int zone_flush_journal(zone_t *zone)
@@ -213,35 +221,44 @@ int zone_flush_journal(zone_t *zone)
 
 	/* Fetch zone source (where it came from). */
 	const struct sockaddr_storage *from = NULL;
-	const conf_iface_t *master = zone_master(zone);
-	if (master != NULL) {
-		from = &master->addr;
+	if (!zone_is_master(zone)) {
+		const conf_remote_t master = zone_master(zone);
+		from = &master.addr;
 	}
 
+	char *zonefile = conf_zonefile(conf(), zone->name);
+
 	/* Synchronize journal. */
-	conf_zone_t *conf = zone->conf;
-	int ret = zonefile_write(conf->file, contents, from);
+	int ret = zonefile_write(zonefile, contents, from);
 	if (ret == KNOT_EOK) {
 		log_zone_info(zone->name, "zone file updated, serial %u -> %u",
 		              zone->zonefile_serial, serial_to);
 	} else {
 		log_zone_warning(zone->name, "failed to update zone file (%s)",
 		                 knot_strerror(ret));
+		free(zonefile);
 		return ret;
 	}
 
 	/* Update zone version. */
 	struct stat st;
-	if (stat(zone->conf->file, &st) < 0) {
+	if (stat(zonefile, &st) < 0) {
 		log_zone_warning(zone->name, "failed to update zone file (%s)",
 		                 knot_strerror(KNOT_EACCES));
+		free(zonefile);
 		return KNOT_EACCES;
 	}
 
+	free(zonefile);
+
+	char *journal_file = conf_journalfile(conf(), zone->name);
+
 	/* Update zone file serial and journal. */
 	zone->zonefile_mtime = st.st_mtime;
 	zone->zonefile_serial = serial_to;
-	journal_mark_synced(zone->conf->ixfr_db);
+	journal_mark_synced(journal_file);
+
+	free(journal_file);
 
 	/* Trim extra heap. */
 	mem_trim();
diff --git a/src/knot/zone/zone.h b/src/knot/zone/zone.h
index c7ce027d62..393b20164b 100644
--- a/src/knot/zone/zone.h
+++ b/src/knot/zone/zone.h
@@ -36,6 +36,7 @@
 #include "knot/zone/events/events.h"
 #include "knot/zone/contents.h"
 #include "libknot/dname.h"
+#include "libknot/packet/pkt.h"
 
 struct process_query_param;
 
@@ -54,7 +55,6 @@ typedef struct zone
 {
 	knot_dname_t *name;
 	zone_contents_t *contents;
-	conf_zone_t *conf;
 	zone_flag_t flags;
 
 	/*! \brief DDNS queue and lock. */
@@ -71,6 +71,8 @@ typedef struct zone
 	time_t zonefile_mtime;
 	uint32_t zonefile_serial;
 
+	/*! \brief Config master list index of the current master server. */
+	size_t master_index;
 } zone_t;
 
 /*----------------------------------------------------------------------------*/
@@ -78,11 +80,11 @@ typedef struct zone
 /*!
  * \brief Creates new zone with emtpy zone content.
  *
- * \param conf  Zone configuration.
+ * \param name  Zone name.
  *
  * \return The initialized zone structure or NULL if an error occured.
  */
-zone_t *zone_new(conf_zone_t *conf);
+zone_t* zone_new(const knot_dname_t *name);
 
 /*!
  * \brief Deallocates the zone structure.
@@ -106,11 +108,14 @@ int zone_change_store(zone_t *zone, changeset_t *change);
 zone_contents_t *zone_switch_contents(zone_t *zone,
 					   zone_contents_t *new_contents);
 
-/*! \brief Return zone master remote. */
-const conf_iface_t *zone_master(const zone_t *zone);
+/*! \brief Check if the zone has some masters. */
+bool zone_is_master(const zone_t *zone);
 
-/*! \brief Rotate list of master remotes for current zone. */
-void zone_master_rotate(const zone_t *zone);
+/*! \brief Return the current zone master. */
+conf_remote_t zone_master(const zone_t *zone);
+
+/*! \brief Set the next zone master as a current. */
+void zone_master_rotate(zone_t *zone);
 
 /*! \brief Synchronize zone file with journal. */
 int zone_flush_journal(zone_t *zone);
diff --git a/src/knot/zone/zonedb-load.c b/src/knot/zone/zonedb-load.c
index b9737f8e62..4584486493 100644
--- a/src/knot/zone/zonedb-load.c
+++ b/src/knot/zone/zonedb-load.c
@@ -16,18 +16,18 @@
 
 #include <assert.h>
 #include <sys/stat.h>
+#include <urcu.h>
 
-#include "libknot/libknot.h"
 #include "knot/zone/zonedb-load.h"
 #include "knot/zone/zone-load.h"
 #include "knot/conf/conf.h"
-#include "libknot/rrtype/soa.h"
 #include "knot/zone/zone.h"
 #include "knot/zone/zonefile.h"
 #include "knot/zone/zonedb.h"
 #include "knot/zone/timers.h"
 #include "knot/server/server.h"
-#include "libknot/dname.h"
+#include "knot/common/log.h"
+#include "libknot/libknot.h"
 
 /*- zone file status --------------------------------------------------------*/
 
@@ -50,12 +50,15 @@ typedef enum {
  *
  * \return Zone status.
  */
-static zone_status_t zone_file_status(const zone_t *old_zone,
-                                      const conf_zone_t *conf)
+static zone_status_t zone_file_status(const zone_t *old_zone, conf_t *conf,
+                                      const knot_dname_t *name)
 {
 	assert(conf);
+	assert(name);
 
-	time_t mtime = zonefile_mtime(conf->file);
+	char *zonefile = conf_zonefile(conf, name);
+	time_t mtime = zonefile_mtime(zonefile);
+	free(zonefile);
 
 	if (mtime < 0) {
 		// Zone file does not exist.
@@ -63,8 +66,8 @@ static zone_status_t zone_file_status(const zone_t *old_zone,
 			// Deferred flush.
 			return ZONE_STATUS_FOUND_CURRENT;
 		} else {
-			return zone_load_can_bootstrap(conf) ? ZONE_STATUS_BOOSTRAP \
-			                                     : ZONE_STATUS_NOT_FOUND;
+			return zone_load_can_bootstrap(conf, name) ?
+			       ZONE_STATUS_BOOSTRAP : ZONE_STATUS_NOT_FOUND;
 		}
 	} else {
 		// Zone file exists.
@@ -84,14 +87,11 @@ static zone_status_t zone_file_status(const zone_t *old_zone,
  * \brief Log message about loaded zone (name and status).
  *
  * \param zone       Zone structure.
- * \param zone_name  Printable name of the zone.
  * \param status     Zone file status.
  */
-static void log_zone_load_info(const zone_t *zone, const char *zone_name,
-                               zone_status_t status)
+static void log_zone_load_info(const zone_t *zone, zone_status_t status)
 {
 	assert(zone);
-	assert(zone_name);
 
 	const char *action = NULL;
 
@@ -114,9 +114,9 @@ static void log_zone_load_info(const zone_t *zone, const char *zone_name,
 	log_zone_info(zone->name, "zone %s, serial %u", action, serial);
 }
 
-static zone_t *create_zone_from(conf_zone_t *zone_conf, server_t *server)
+static zone_t *create_zone_from(const knot_dname_t *name, server_t *server)
 {
-	zone_t *zone = zone_new(zone_conf);
+	zone_t *zone = zone_new(name);
 	if (!zone) {
 		return NULL;
 	}
@@ -124,7 +124,6 @@ static zone_t *create_zone_from(conf_zone_t *zone_conf, server_t *server)
 	int result = zone_events_setup(zone, server->workers, &server->sched,
 	                               server->timers_db);
 	if (result != KNOT_EOK) {
-		zone->conf = NULL;
 		zone_free(&zone);
 		return NULL;
 	}
@@ -132,16 +131,16 @@ static zone_t *create_zone_from(conf_zone_t *zone_conf, server_t *server)
 	return zone;
 }
 
-static zone_t *create_zone_reload(conf_zone_t *zone_conf, server_t *server,
-                                  zone_t *old_zone)
+static zone_t *create_zone_reload(conf_t *conf, const knot_dname_t *name,
+                                  server_t *server, zone_t *old_zone)
 {
-	zone_t *zone = create_zone_from(zone_conf, server);
+	zone_t *zone = create_zone_from(name, server);
 	if (!zone) {
 		return NULL;
 	}
 	zone->contents = old_zone->contents;
 
-	const zone_status_t zstatus = zone_file_status(old_zone, zone_conf);
+	const zone_status_t zstatus = zone_file_status(old_zone, conf, name);
 
 	switch (zstatus) {
 	case ZONE_STATUS_FOUND_UPDATED:
@@ -177,7 +176,7 @@ static void reuse_events(zone_t *zone, const time_t *timers)
 			// Timer unset.
 			continue;
 		}
-		if (slave_event(event) && !zone_master(zone)) {
+		if (slave_event(event) && zone_is_master(zone)) {
 			// Slave-only event.
 			continue;
 		}
@@ -192,9 +191,10 @@ static bool zone_expired(const time_t *timers)
 	return now <= timers[ZONE_EVENT_EXPIRE];
 }
 
-static zone_t *create_zone_new(conf_zone_t *zone_conf, server_t *server)
+static zone_t *create_zone_new(conf_t * conf, const knot_dname_t *name,
+                               server_t *server)
 {
-	zone_t *zone = create_zone_from(zone_conf, server);
+	zone_t *zone = create_zone_from(name, server);
 	if (!zone) {
 		return NULL;
 	}
@@ -207,14 +207,13 @@ static zone_t *create_zone_new(conf_zone_t *zone_conf, server_t *server)
 	if (ret != KNOT_EOK) {
 		log_zone_error(zone->name, "cannot read zone timers (%s)",
 		               knot_strerror(ret));
-		zone->conf = NULL;
 		zone_free(&zone);
 		return NULL;
 	}
 
 	reuse_events(zone, timers);
 
-	const zone_status_t zstatus = zone_file_status(NULL, zone_conf);
+	const zone_status_t zstatus = zone_file_status(NULL, conf, name);
 
 	switch (zstatus) {
 	case ZONE_STATUS_FOUND_NEW:
@@ -235,7 +234,7 @@ static zone_t *create_zone_new(conf_zone_t *zone_conf, server_t *server)
 		assert(0);
 	}
 
-	log_zone_load_info(zone, zone_conf->name, zstatus);
+	log_zone_load_info(zone, zstatus);
 
 	return zone;
 }
@@ -243,22 +242,23 @@ static zone_t *create_zone_new(conf_zone_t *zone_conf, server_t *server)
 /*!
  * \brief Load or reload the zone.
  *
- * \param zone_conf  Zone configuration.
+ * \param conf       Configuration.
  * \param server     Server.
  * \param old_zone   Already loaded zone (can be NULL).
  *
  * \return Error code, KNOT_EOK if successful.
  */
-static zone_t *create_zone(conf_zone_t *zone_conf, server_t *server,
+static zone_t *create_zone(conf_t *conf, const knot_dname_t *name, server_t *server,
                            zone_t *old_zone)
 {
-	assert(zone_conf);
+	assert(conf);
+	assert(name);
 	assert(server);
 
 	if (old_zone) {
-		return create_zone_reload(zone_conf, server, old_zone);
+		return create_zone_reload(conf, name, server, old_zone);
 	} else {
-		return create_zone_new(zone_conf, server);
+		return create_zone_new(conf, name, server);
 	}
 }
 
@@ -273,37 +273,31 @@ static zone_t *create_zone(conf_zone_t *zone_conf, server_t *server,
  *
  * \return New zone database.
  */
-static knot_zonedb_t *create_zonedb(const conf_t *conf, server_t *server)
+static knot_zonedb_t *create_zonedb(conf_t *conf, server_t *server)
 {
 	assert(conf);
 	assert(server);
 
 	knot_zonedb_t *db_old = server->zone_db;
-	knot_zonedb_t *db_new = knot_zonedb_new(hattrie_weight(conf->zones));
+	knot_zonedb_t *db_new = knot_zonedb_new(conf_id_count(conf, C_ZONE));
 	if (!db_new) {
 		return NULL;
 	}
 
-	hattrie_iter_t *it = hattrie_iter_begin(conf->zones, false);
-	for (; !hattrie_iter_finished(it); hattrie_iter_next(it)) {
-
-		conf_zone_t *zone_config = (conf_zone_t *)*hattrie_iter_val(it);
-
-		knot_dname_t *apex = knot_dname_from_str_alloc(zone_config->name);
-		zone_t *old_zone = knot_zonedb_find(db_old, apex);
-		knot_dname_free(&apex, NULL);
-
-		zone_t *zone = create_zone(zone_config, server, old_zone);
+	conf_iter_t iter = conf_iter(conf, C_ZONE);
+	while (iter.code == KNOT_EOK) {
+		conf_val_t id = conf_iter_id(conf, &iter);
+		zone_t *old_zone = knot_zonedb_find(db_old, conf_dname(&id));
+		zone_t *zone = create_zone(conf, conf_dname(&id), server, old_zone);
 		if (!zone) {
-			log_zone_str_error(zone_config->name,
-					   "zone cannot be created");
-			conf_free_zone(zone_config);
+			log_zone_error(id.data, "zone cannot be created");
 			continue;
 		}
-
 		knot_zonedb_insert(db_new, zone);
+
+		conf_iter_next(conf, &iter);
 	}
-	hattrie_iter_free(it);
+	conf_iter_finish(conf, &iter);
 
 	return db_new;
 }
@@ -347,7 +341,7 @@ static int remove_old_zonedb(const knot_zonedb_t *db_new, knot_zonedb_t *db_old)
 /*!
  * \brief Update zone database according to configuration.
  */
-int zonedb_reload(const conf_t *conf, struct server *server)
+int zonedb_reload(conf_t *conf, struct server *server)
 {
 	/* Check parameters */
 	if (conf == NULL || server == NULL) {
diff --git a/src/knot/zone/zonedb-load.h b/src/knot/zone/zonedb-load.h
index 03ad505a8d..af83bb0d09 100644
--- a/src/knot/zone/zonedb-load.h
+++ b/src/knot/zone/zonedb-load.h
@@ -31,4 +31,4 @@ struct server;
  * \retval KNOT_EINVAL
  * \retval KNOT_ERROR
  */
-int zonedb_reload(const conf_t *conf, struct server *server);
+int zonedb_reload(conf_t *conf, struct server *server);
diff --git a/src/knot/zone/zonefile.c b/src/knot/zone/zonefile.c
index 27115258c6..caf5c84d5c 100644
--- a/src/knot/zone/zonefile.c
+++ b/src/knot/zone/zonefile.c
@@ -187,23 +187,8 @@ static void scanner_process(zs_scanner_t *scanner)
 	knot_rdataset_clear(&rr.rrs, NULL);
 }
 
-static zone_contents_t *create_zone_from_name(const char *origin)
-{
-	if (origin == NULL) {
-		return NULL;
-	}
-	knot_dname_t *owner = knot_dname_from_str_alloc(origin);
-	if (owner == NULL) {
-		return NULL;
-	}
-	knot_dname_to_lower(owner);
-	zone_contents_t *z = zone_contents_new(owner);
-	knot_dname_free(&owner, NULL);
-	return z;
-}
-
-int zonefile_open(zloader_t *loader, const char *source, const char *origin,
-		  bool semantic_checks)
+int zonefile_open(zloader_t *loader, const char *source,
+                  const knot_dname_t *origin, bool semantic_checks)
 {
 	if (!loader) {
 		return KNOT_EINVAL;
@@ -221,24 +206,32 @@ int zonefile_open(zloader_t *loader, const char *source, const char *origin,
 	}
 	memset(zc, 0, sizeof(zcreator_t));
 
-	zc->z = create_zone_from_name(origin);
+	zc->z = zone_contents_new(origin);
 	if (zc->z == NULL) {
 		free(zc);
 		return KNOT_ENOMEM;
 	}
 
+	/* Prepare textual owner for zone scanner. */
+	char *origin_str = knot_dname_to_str_alloc(origin);
+	if (origin_str == NULL) {
+		free(zc);
+		return KNOT_ENOMEM;
+	}
+
 	/* Create file loader. */
 	memset(loader, 0, sizeof(zloader_t));
-	loader->scanner = zs_scanner_create(origin, KNOT_CLASS_IN, 3600,
+	loader->scanner = zs_scanner_create(origin_str, KNOT_CLASS_IN, 3600,
 	                                    scanner_process, process_error,
 	                                    zc);
 	if (loader->scanner == NULL) {
+		free(origin_str);
 		free(zc);
 		return KNOT_ERROR;
 	}
 
 	loader->source = strdup(source);
-	loader->origin = strdup(origin);
+	loader->origin = origin_str;
 	loader->creator = zc;
 	loader->semantic_checks = semantic_checks;
 
diff --git a/src/knot/zone/zonefile.h b/src/knot/zone/zonefile.h
index 2834a20cd4..d181f8fd6d 100644
--- a/src/knot/zone/zonefile.h
+++ b/src/knot/zone/zonefile.h
@@ -65,8 +65,8 @@ typedef struct zloader {
  * \retval Initialized loader on success.
  * \retval NULL on error.
  */
-int zonefile_open(zloader_t *loader, const char *source, const char *origin,
-		  bool semantic_checks);
+int zonefile_open(zloader_t *loader, const char *source,
+                  const knot_dname_t *origin, bool semantic_checks);
 
 /*!
  * \brief Loads zone from a zone file.
diff --git a/src/libknot/errcode.c b/src/libknot/errcode.c
index df9f908f9f..5bb51dd264 100644
--- a/src/libknot/errcode.c
+++ b/src/libknot/errcode.c
@@ -69,7 +69,7 @@ static const struct error errors[] = {
 	{ KNOT_ENOZONE,      "no such zone found" },
 	{ KNOT_ENONODE,      "no such node in zone found" },
 	{ KNOT_EDNAMEPTR,    "domain name pointer larger than allowed" },
-	{ KNOT_EPAYLOAD,     "payload in OPT RR larger than max wire size" },
+	{ KNOT_EPAYLOAD,     "invalid EDNS payload size" },
 	{ KNOT_EPREREQ,      "UPDATE prerequisity not met" },
 	{ KNOT_ETTL,         "TTL mismatch" },
 	{ KNOT_ENOXFR,       "transfer was not sent" },
diff --git a/src/libknot/rrtype/tsig.c b/src/libknot/rrtype/tsig.c
index fe71ca5cc2..9994189f76 100644
--- a/src/libknot/rrtype/tsig.c
+++ b/src/libknot/rrtype/tsig.c
@@ -358,7 +358,7 @@ size_t knot_tsig_rdata_tsig_timers_length()
 _public_
 size_t knot_tsig_wire_maxsize(const knot_tsig_key_t *key)
 {
-	if (key == NULL) {
+	if (key == NULL || key->name == NULL) {
 		return 0;
 	}
 
diff --git a/src/libknot/rrtype/tsig.h b/src/libknot/rrtype/tsig.h
index e83feee366..4464e58276 100644
--- a/src/libknot/rrtype/tsig.h
+++ b/src/libknot/rrtype/tsig.h
@@ -58,7 +58,7 @@ enum tsig_consts {
 /*! \brief Packet signing context.
  *  \todo This should be later moved to TSIG files when refactoring. */
 typedef struct knot_sign_context {
-	knot_tsig_key_t *tsig_key;
+	knot_tsig_key_t tsig_key;
 	uint8_t *tsig_buf;
 	uint8_t *tsig_digest;
 	size_t tsig_buflen;
diff --git a/src/libknot/yparser/ypformat.c b/src/libknot/yparser/ypformat.c
index 2dd97352d8..8e88e4daeb 100644
--- a/src/libknot/yparser/ypformat.c
+++ b/src/libknot/yparser/ypformat.c
@@ -14,6 +14,7 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#include "libknot/internal/macros.h"
 #include "libknot/yparser/yptrafo.h"
 #include "libknot/libknot.h"
 
@@ -73,6 +74,7 @@ static int format_item(
 	return KNOT_EOK;
 }
 
+_public_
 int yp_format_key0(
 	const yp_item_t *item,
 	const uint8_t *data,
@@ -87,6 +89,7 @@ int yp_format_key0(
 	                   first_value, last_value);
 }
 
+_public_
 int yp_format_id(
 	const yp_item_t *item,
 	const uint8_t *data,
@@ -103,6 +106,7 @@ int yp_format_id(
 	                   true, true);
 }
 
+_public_
 int yp_format_key1(
 	const yp_item_t *item,
 	const uint8_t *data,
diff --git a/tests-extra/tests/events/soa/test.py b/tests-extra/tests/events/soa/test.py
index e8b35ae799..807bda81c2 100644
--- a/tests-extra/tests/events/soa/test.py
+++ b/tests-extra/tests/events/soa/test.py
@@ -1,12 +1,12 @@
 #!/usr/bin/env python3
 
-'''Test for SOA events and planning thereof'''
+'''Test for SOA events and planning thereof without NOTIFY'''
 
 from dnstest.utils import *
 from dnstest.test import Test
 import random
 
-EXPIRE_SLEEP = 4
+EXPIRE_SLEEP = 6
 
 def test_refresh(slave):
     resp = slave.dig("example.", "SOA")
@@ -14,7 +14,7 @@ def test_refresh(slave):
     t.sleep(EXPIRE_SLEEP)
     resp = slave.dig("example.", "SOA")
     resp.check(rcode="NOERROR")
-    
+
 def test_expire(slave):
     resp = slave.dig("example.", "SOA")
     resp.check(rcode="NOERROR")
@@ -27,6 +27,7 @@ def create_servers(t):
     for _ in range(3):
         master = t.server("bind")
         master.disable_notify = True
+        master.max_conn_idle = "1s"
 
         slave = t.server("knot")
         slave.disable_notify = True
@@ -112,14 +113,14 @@ t = Test()
 
 random.seed()
 
-# this zone has refresh = 1s, retry = 1s and expire = 1s + 2s for connection timeouts
+# This zone has refresh = 1s, retry = 1s and expire = 2s
 zone = t.zone("example.", storage=".")
 
 servers = create_servers(t)
 
 t.start()
 
-#stop the servers so that the zone does not expire
+# Stop the servers so that the zone does not expire
 for server_pair in servers:
     server_pair[0].stop()
     server_pair[1].stop()
diff --git a/tests-extra/tests/zone/expire/test.py b/tests-extra/tests/zone/expire/test.py
index 4a1469bbe7..164eafa878 100644
--- a/tests-extra/tests/zone/expire/test.py
+++ b/tests-extra/tests/zone/expire/test.py
@@ -13,12 +13,12 @@ def break_xfrout(server):
         config = f.read()
         f.seek(0)
         f.truncate()
-        config = config.replace("xfr-out ", "#xfr-out ")
+        config = config.replace(" acl:", " #acl:")
         f.write(config)
 
 t = Test(tsig=False)
 
-# this zone has refresh = 1s, retry = 1s and expire = 10s + 2s for connection timeouts
+# this zone has refresh = 1s, retry = 1s and expire = 8s
 zone = t.zone("example.", storage=".")
 EXPIRE_SLEEP = 15
 
diff --git a/tests-extra/tests/zone/timers/test.py b/tests-extra/tests/zone/timers/test.py
index b11740bafe..01f39c0ad5 100644
--- a/tests-extra/tests/zone/timers/test.py
+++ b/tests-extra/tests/zone/timers/test.py
@@ -5,7 +5,7 @@
 from dnstest.test import Test
 import random
 
-EXPIRE_SLEEP = 17
+EXPIRE_SLEEP = 20
 RETRY_SLEEP = 10
 START_SLEEP = 5
 
@@ -104,7 +104,7 @@ def refresh_tests(t, zone, master, slave):
 
 t = Test()
 
-# this zone has refresh = 1s, retry = 1s and expire = 1s + 2s for connection timeouts
+# this zone has refresh = 1s, retry = 1s and expire = 16s
 zone = t.zone("example.", storage=".")
 
 master = t.server("knot")
diff --git a/tests-extra/tools/dnstest/server.py b/tests-extra/tools/dnstest/server.py
index 62a4c309be..352d287847 100644
--- a/tests-extra/tools/dnstest/server.py
+++ b/tests-extra/tools/dnstest/server.py
@@ -33,29 +33,27 @@ class KnotConf(object):
 
     def __init__(self):
         self.conf = ""
-        self.indent = ""
-
-    def sub(self):
-        self.indent += "\t"
-
-    def unsub(self):
-        self.indent = self.indent[:-1]
+        self.first_item = True
 
     def begin(self, name):
-        self.conf += "%s%s {\n" % (self.indent, name)
-        self.sub()
+        self.conf += "%s:\n" % name
+        self.first_item = True
 
     def end(self):
-        self.unsub()
-        self.conf += "%s}\n" % (self.indent)
-        if not self.indent:
-            self.conf += "\n"
+        self.conf += "\n"
 
     def item(self, name, value):
-        self.conf += "%s%s %s;\n" % (self.indent, name, value)
+        self.conf += "        %s: %s\n" % (name, value)
 
     def item_str(self, name, value):
-        self.conf += "%s%s \"%s\";\n" % (self.indent, name, value)
+        self.conf += "        %s: \"%s\"\n" % (name, value)
+
+    def id_item(self, name, value):
+        if not self.first_item:
+            self.conf += "\n"
+        else:
+            self.first_item = False
+        self.conf += "      - %s: \"%s\"\n" % (name, value)
 
 class BindConf(object):
     '''Bind server config generator'''
@@ -775,11 +773,13 @@ class Bind(Server):
                 s.item("type", "slave")
 
                 if self.tsig:
-                    s.item("allow-notify", "{ key %s; }" % z.master.tsig.name)
+                    if not z.master.disable_notify:
+                        s.item("allow-notify", "{ key %s; }" % z.master.tsig.name)
                     s.item("masters", "{ %s port %i key %s; }" \
                            % (z.master.addr, z.master.port, z.master.tsig.name))
                 else:
-                    s.item("allow-notify", "{ %s; }" % z.master.addr)
+                    if not z.master.disable_notify:
+                        s.item("allow-notify", "{ %s; }" % z.master.addr)
                     s.item("masters", "{ %s port %i; }" \
                            % (z.master.addr, z.master.port))
             else:
@@ -792,12 +792,15 @@ class Bind(Server):
             if z.slaves:
                 slaves = ""
                 for slave in z.slaves:
+                    if slave.disable_notify:
+                        continue
                     if self.tsig:
                         slaves += "%s port %i key %s; " \
                                   % (slave.addr, slave.port, self.tsig.name)
                     else:
                         slaves += "%s port %i; " % (slave.addr, slave.port)
-                s.item("also-notify", "{ %s}" % slaves)
+                if slaves:
+                    s.item("also-notify", "{ %s}" % slaves)
 
             if z.ddns:
                 if self.tsig:
@@ -844,154 +847,174 @@ class Knot(Server):
 
     def _on_str_hex(self, conf, name, value):
         if value == True:
-            conf.item(name, "on")
+            conf.item_str(name, "")
         elif value == False:
-            conf.item(name, "off")
+            return
         elif value:
-            if isinstance(value, int) or value[:2] == "0x":
-                conf.item(name, value)
-            else:
-                conf.item_str(name, value)
+            conf.item_str(name, value)
+
+    def _key(self, conf, key):
+        conf.id_item("id", key.name)
+        conf.item_str("algorithm", key.alg)
+        conf.item_str("secret", key.key)
 
     def get_config(self):
         s = KnotConf()
-        s.begin("system")
+        s.begin("server")
         self._on_str_hex(s, "identity", self.ident)
         self._on_str_hex(s, "version", self.version)
         self._on_str_hex(s, "nsid", self.nsid)
-        self._on_str_hex(s, "rate-limit", self.ratelimit)
         s.item_str("rundir", self.dir)
+        s.item_str("listen", "%s@%s" % (self.addr, self.port))
         if (self.max_conn_idle):
-            s.item("max-conn-idle", self.max_conn_idle)
+            s.item_str("max-conn-idle", self.max_conn_idle)
+        if (self.ratelimit):
+            s.item_str("rate-limit", self.ratelimit)
         s.end()
 
         s.begin("control")
-        s.item_str("listen-on", "knot.sock")
-        s.end()
-
-        s.begin("interfaces")
-        if self.ip == 4:
-            s.begin("ipv4")
-        else:
-            s.begin("ipv6")
-        s.item("address", self.addr)
-        s.item("port", self.port)
-        s.end()
+        s.item_str("listen", "knot.sock")
         s.end()
 
         if self.tsig:
-            s.begin("keys")
-            t = self.tsig
-            s.item_str("\"%s\" %s" % (t.name, t.alg), t.key)
-            t = self.tsig_test
-            s.item_str("\"%s\" %s" % (t.name, t.alg), t.key)
+            s.begin("key")
+            self._key(s, self.tsig)
+            self._key(s, self.tsig_test)
 
             keys = set() # Duplicy check.
             for zone in sorted(self.zones):
                 z = self.zones[zone]
                 if z.master and z.master.tsig.name not in keys:
                     t = z.master.tsig
-                    s.item_str("\"%s\" %s" % (t.name, t.alg), t.key)
+                    self._key(s, t)
                     keys.add(t.name)
                 for slave in z.slaves:
                     if slave.tsig and slave.tsig.name not in keys:
                         t = slave.tsig
-                        s.item_str("\"%s\" %s" % (t.name, t.alg), t.key)
+                        self._key(s, t)
                         keys.add(t.name)
             s.end()
 
-        s.begin("remotes")
-        s.begin("local")
-        s.item("address", self.addr)
+        have_remote = False
+        servers = set() # Duplicity check.
+        for zone in sorted(self.zones):
+            z = self.zones[zone]
+            if z.master and z.master.name not in servers:
+                if not have_remote:
+                    s.begin("remote")
+                    have_remote = True
+                s.id_item("id", z.master.name)
+                s.item_str("address", "%s@%s" % (z.master.addr, z.master.port))
+                if z.master.tsig:
+                    s.item_str("key", z.master.tsig.name)
+                servers.add(z.master.name)
+            for slave in z.slaves:
+                if slave.name not in servers:
+                    if not have_remote:
+                        s.begin("remote")
+                        have_remote = True
+                    s.id_item("id", slave.name)
+                    s.item_str("address", "%s@%s" % (slave.addr, slave.port))
+                    if slave.tsig:
+                        s.item_str("key", slave.tsig.name)
+                    servers.add(slave.name)
+        if have_remote:
+            s.end()
+
+        s.begin("acl")
+        s.id_item("id", "acl_local")
+        s.item_str("address", self.addr)
         if self.tsig:
             s.item_str("key", self.tsig.name)
-        s.end()
-        s.begin("test")
-        s.item("address", self.addr)
+        s.item("action", "[xfer, notify, update]")
+
+        s.id_item("id", "acl_test")
+        s.item_str("address", self.addr)
         if self.tsig_test:
             s.item_str("key", self.tsig_test.name)
-        s.end()
+        s.item("action", "[xfer, notify, update]")
 
         servers = set() # Duplicity check.
         for zone in sorted(self.zones):
             z = self.zones[zone]
             if z.master and z.master.name not in servers:
-                s.begin(z.master.name)
-                s.item("address", z.master.addr)
-                s.item("port", z.master.port)
+                s.id_item("id", "acl_%s" % z.master.name)
+                s.item_str("address", z.master.addr)
                 if z.master.tsig:
                     s.item_str("key", z.master.tsig.name)
-                s.end()
+                s.item("action", "notify")
                 servers.add(z.master.name)
             for slave in z.slaves:
-                if slave.name not in servers:
-                    s.begin(slave.name)
-                    s.item("address", slave.addr)
-                    s.item("port", slave.port)
-                    if slave.tsig:
-                        s.item_str("key", self.tsig.name)
-                    s.end()
-                    servers.add(slave.name)
+                if slave.name in servers:
+                    continue
+                s.id_item("id", "acl_%s" % slave.name)
+                s.item_str("address", slave.addr)
+                if slave.tsig:
+                    s.item_str("key", slave.tsig.name)
+                s.item("action", "xfer")
+                servers.add(slave.name)
         s.end()
 
-        s.begin("zones")
+        s.begin("template")
+        s.id_item("id", "default")
         s.item_str("storage", self.dir)
         if self.zonefile_sync:
-            s.item("zonefile-sync", self.zonefile_sync)
+            s.item_str("zonefile-sync", self.zonefile_sync)
         else:
-            s.item("zonefile-sync", "1d")
+            s.item_str("zonefile-sync", "1d")
         if self.ixfr_fslimit:
-            s.item("ixfr-fslimit", self.ixfr_fslimit)
-        s.item("notify-timeout", "5")
-        s.item("notify-retries", "5")
-        s.item("semantic-checks", "on")
+            s.item_str("ixfr-fslimit", self.ixfr_fslimit)
+        s.item_str("notify-timeout", "5")
+        s.item_str("notify-retries", "5")
+        s.item_str("semantic-checks", "on")
         if self.disable_any:
-            s.item("disable-any", "on")
+            s.item_str("disable-any", "on")
         if self.dnssec_enable:
             s.item_str("dnssec-keydir", self.keydir)
-            s.item("dnssec-enable", "on")
+            s.item_str("dnssec-enable", "on")
+        s.end()
+
+        s.begin("zone")
         for zone in sorted(self.zones):
             z = self.zones[zone]
-            s.begin(z.name)
+            s.id_item("domain", z.name)
             s.item_str("file", z.zfile.path)
 
+            acl = ""
             if z.master:
-                if not self.disable_notify:
-                    s.item("notify-in", z.master.name)
-                s.item("xfr-in", z.master.name)
+                s.item("master", z.master.name)
+                if not z.master.disable_notify:
+                    acl = "acl_%s" % z.master.name
 
-            slaves = ""
             if z.slaves:
+                slaves = ""
                 for slave in z.slaves:
+                    if slave.disable_notify:
+                        continue
                     if slaves:
                         slaves += ", "
                     slaves += slave.name
-                s.item("notify-out", slaves)
-
-            s.item("xfr-out", "local, test")
+                if slaves:
+                    s.item("notify", "[%s]" % slaves)
 
-            if z.ddns:
-                s.item("update-in", "test")
+            if acl:
+                acl += ", "
+            acl += "acl_local, acl_test"
+            s.item("acl", "[%s]" % acl)
 
             if z.ixfr and not z.master:
-                s.item("ixfr-from-differences", "on")
+                s.item_str("ixfr-from-differences", "on")
 
             if len(z.query_modules) > 0:
                 s.begin("query_module")
                 for query_module in z.query_modules:
-                    s.item(query_module[0], '"' + query_module[1] + '"')
+                    s.item_str(query_module[0], '"' + query_module[1] + '"')
                 s.end()
-            s.end()
         s.end()
 
         s.begin("log")
-        s.begin("stdout")
-        s.item("any", "debug")
-        s.end()
-        s.begin("stderr")
-        s.end()
-        s.begin("syslog")
-        s.end()
+        s.id_item("to", "stdout")
+        s.item_str("any", "debug")
         s.end()
 
         self.start_params = ["-c", self.confile]
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 497bc4fe7f..7af9b85699 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -10,15 +10,14 @@ AM_LDFLAGS = \
 LDADD = \
 	$(top_builddir)/libtap/libtap.a \
 	$(top_builddir)/src/libknot-int.la \
-	$(top_builddir)/src/libknot-yparser.la \
-	$(top_builddir)/src/libknotd.la
+	$(top_builddir)/src/libknotd.la \
+	$(top_builddir)/src/libknot-yparser.la
 
 check_PROGRAMS = \
 	acl				\
 	base32hex			\
 	base64				\
 	changeset			\
-	changeset			\
 	descriptor			\
 	dname				\
 	dthreads			\
diff --git a/tests/acl.c b/tests/acl.c
index e2bebd62d5..5df8959613 100644
--- a/tests/acl.c
+++ b/tests/acl.c
@@ -14,6 +14,7 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <assert.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <tap/basic.h>
@@ -23,127 +24,78 @@
 #include "knot/updates/acl.h"
 #include "knot/conf/conf.h"
 
-static int acl_insert(list_t *acl, const struct sockaddr_storage *addr,
-                      uint8_t prefix, knot_tsig_key_t *key)
-{
-	assert(acl);
-	assert(addr);
-
-	conf_iface_t *iface = malloc(sizeof(conf_iface_t));
-	assert(iface);
-	conf_remote_t *remote = malloc(sizeof(conf_remote_t));
-	assert(remote);
-	remote->remote = iface;
-
-	memset(iface, 0, sizeof(conf_iface_t));
-	iface->prefix = prefix;
-	iface->key = key;
-	memcpy(&iface->addr, addr, sizeof(struct sockaddr_storage));
-
-	add_tail(acl, &remote->n);
-	return KNOT_EOK;
-}
-
 int main(int argc, char *argv[])
 {
-	plan(15);
-
-	conf_iface_t *match = NULL;
-	list_t acl;
-	init_list(&acl);
-
-	// Create IPv4 address
-	struct sockaddr_storage test_v4;
-	int ret = sockaddr_set(&test_v4, AF_INET, "127.0.0.1", 12345);
-	ok(ret == KNOT_EOK, "acl: new IPv4 address");
-
-	// Create IPv6 address
-	struct sockaddr_storage test_v6;
-	ret = sockaddr_set(&test_v6, AF_INET6, "::1", 54321);
-	ok(ret == KNOT_EOK, "acl: new IPv6 address");
-
-	// Create simple IPv4 rule
-	ret = acl_insert(&acl, &test_v4, IPV4_PREFIXLEN, NULL);
-	ok(ret == KNOT_EOK, "acl: inserted IPv4 rule");
-
-	// Create simple IPv6 rule
-	ret = acl_insert(&acl, &test_v6, IPV6_PREFIXLEN, NULL);
-	ok(ret == KNOT_EOK, "acl: inserted IPv6 rule");
-
-	// Attempt to match unmatching address
-	struct sockaddr_storage unmatch_v4;
-	sockaddr_set(&unmatch_v4, AF_INET, "10.10.10.10", 24424);
-	match = acl_find(&acl, &unmatch_v4, NULL);
-	ok(match == NULL, "acl: matching non-existing address");
-
-	// Attempt to match unmatching IPv6 address
-	struct sockaddr_storage unmatch_v6;
-	sockaddr_set(&unmatch_v6, AF_INET6, "2001:db8::1428:57ab", 24424);
-	match = acl_find(&acl, &unmatch_v6, NULL);
-	ok(match == NULL, "acl: matching non-existing IPv6 address");
-
-	// Attempt to match matching address
-	match = acl_find(&acl, &test_v4, NULL);
-	ok(match != NULL, "acl: matching existing address");
-
-	// Attempt to match matching address
-	match = acl_find(&acl, &test_v6, NULL);
-	ok(match != NULL, "acl: matching existing IPv6 address");
-
-	// Attempt to match subnet
-	struct sockaddr_storage match_pf4, test_pf4;
-	sockaddr_set(&match_pf4, AF_INET, "192.168.1.0", 0);
-	acl_insert(&acl, &match_pf4, 24, NULL);
-	sockaddr_set(&test_pf4, AF_INET, "192.168.1.20", 0);
-	match = acl_find(&acl, &test_pf4, NULL);
-	ok(match != NULL, "acl: searching address in matching prefix /24");
-
-	// Attempt to search non-matching subnet
-	sockaddr_set(&test_pf4, AF_INET, "192.168.2.20", 0);
-	match = acl_find(&acl, &test_pf4, NULL);
-	ok(match == NULL, "acl: searching address in non-matching prefix /24");
-
-	// Attempt to match v6 subnet
-	struct sockaddr_storage match_pf6, test_pf6;
-	sockaddr_set(&match_pf6, AF_INET6, "2001:0DB8:0400:000e:0:0:0:AB00", 0);
-	acl_insert(&acl, &match_pf6, 120, NULL);
-	sockaddr_set(&test_pf6, AF_INET6, "2001:0DB8:0400:000e:0:0:0:AB03", 0);
-	match = acl_find(&acl, &test_pf6, NULL);
-	ok(match != NULL, "acl: searching v6 address in matching prefix /120");
-
-	// Attempt to search non-matching subnet
-	sockaddr_set(&test_pf6, AF_INET6, "2001:0DB8:0400:000e:0:0:0:CCCC", 0);
-	match = acl_find(&acl, &test_pf6, NULL);
-	ok(match == NULL, "acl: searching v6 address in non-matching prefix /120");
-
-	// Attempt to search subnet with key (multiple keys)
-	knot_tsig_key_t key_a, key_b;
-	knot_tsig_create_key("tsig-key1", DNSSEC_TSIG_HMAC_MD5, "Wg==", &key_a);
-	knot_tsig_create_key("tsig-key2", DNSSEC_TSIG_HMAC_MD5, "Wg==", &key_b);
-	acl_insert(&acl, &match_pf6, 120, &key_a);
-	acl_insert(&acl, &match_pf6, 120, &key_b);
-	sockaddr_set(&test_pf6, AF_INET6, "2001:0DB8:0400:000e:0:0:0:AB03", 0);
-	match = acl_find(&acl, &test_pf6, key_a.name);
-	ok(match != NULL && match->key == &key_a, "acl: searching v6 address with TSIG key A");
-	match = acl_find(&acl, &test_pf6, key_b.name);
-	ok(match != NULL && match->key == &key_b, "acl: searching v6 address with TSIG key B");
-
-	// Attempt to search subnet with mismatching key
-	knot_tsig_key_t badkey;
-	knot_tsig_create_key("tsig-bad", DNSSEC_TSIG_HMAC_MD5, "Wg==", &badkey);
-	match = acl_find(&acl, &test_pf6, badkey.name);
-	ok(match == NULL, "acl: searching v6 address with bad TSIG key");
-	knot_tsig_key_free(&badkey);
-
-	knot_tsig_key_free(&key_a);
-	knot_tsig_key_free(&key_b);
-
-	conf_remote_t *remote = NULL, *next = NULL;
-	WALK_LIST_DELSAFE(remote, next, acl) {
-		free(remote->remote);
-		free(remote);
-	}
-
-	// Return
+	plan_lazy();
+
+	int ret;
+	struct sockaddr_storage t;
+
+	// 127 dec ~ 01111111 bin
+	// 170 dec ~ 10101010 bin
+	struct sockaddr_storage ref4;
+	assert(sockaddr_set(&ref4, AF_INET, "127.170.170.127", 0) == KNOT_EOK);
+
+	// 7F hex ~ 01111111 bin
+	// AA hex ~ 10101010 bin
+	struct sockaddr_storage ref6;
+	assert(sockaddr_set(&ref6, AF_INET6, "7FAA::AA7F", 0) == KNOT_EOK);
+
+	ret = netblock_match(&ref4, &ref6, 32);
+	ok(ret == false, "match: family mismatch");
+
+	ret = netblock_match(NULL, &ref4, 32);
+	ok(ret == false, "match: NULL first parameter");
+	ret = netblock_match(&ref4, NULL, 32);
+	ok(ret == false, "match: NULL second parameter");
+
+	ret = netblock_match(&ref4, &ref4, 31);
+	ok(ret == true, "match: ipv4 - identity, subnet");
+	ret = netblock_match(&ref4, &ref4, 32);
+	ok(ret == true, "match: ipv4 - identity, full prefix");
+	ret = netblock_match(&ref4, &ref4, 33);
+	ok(ret == true, "match: ipv4 - identity, prefix overflow");
+
+	ret = netblock_match(&ref6, &ref6, 127);
+	ok(ret == true, "match: ipv6 - identity, subnet");
+	ret = netblock_match(&ref6, &ref6, 128);
+	ok(ret == true, "match: ipv6 - identity, full prefix");
+	ret = netblock_match(&ref6, &ref6, 129);
+	ok(ret == true, "match: ipv6 - identity, prefix overflow");
+
+	// 124 dec ~ 01111100 bin
+	assert(sockaddr_set(&t, AF_INET, "124.0.0.0", 0) == KNOT_EOK);
+	ret = netblock_match(&t, &ref4, 5);
+	ok(ret == true, "match: ipv4 - first byte, shorter prefix");
+	ret = netblock_match(&t, &ref4, 6);
+	ok(ret == true, "match: ipv4 - first byte, precise prefix");
+	ret = netblock_match(&t, &ref4, 7);
+	ok(ret == false, "match: ipv4 - first byte, not match");
+
+	assert(sockaddr_set(&t, AF_INET, "127.170.170.124", 0) == KNOT_EOK);
+	ret = netblock_match(&t, &ref4, 29);
+	ok(ret == true, "match: ipv4 - last byte, shorter prefix");
+	ret = netblock_match(&t, &ref4, 30);
+	ok(ret == true, "match: ipv4 - last byte, precise prefix");
+	ret = netblock_match(&t, &ref4, 31);
+	ok(ret == false, "match: ipv4 - last byte, not match");
+
+	// 7C hex ~ 01111100 bin
+	assert(sockaddr_set(&t, AF_INET6, "7CAA::", 0) == KNOT_EOK);
+	ret = netblock_match(&t, &ref6, 5);
+	ok(ret == true, "match: ipv6 - first byte, shorter prefix");
+	ret = netblock_match(&t, &ref6, 6);
+	ok(ret == true, "match: ipv6 - first byte, precise prefix");
+	ret = netblock_match(&t, &ref6, 7);
+	ok(ret == false, "match: ipv6 - first byte, not match");
+
+	assert(sockaddr_set(&t, AF_INET6, "7FAA::AA7C", 0) == KNOT_EOK);
+	ret = netblock_match(&t, &ref6, 125);
+	ok(ret == true, "match: ipv6 - last byte, shorter prefix");
+	ret = netblock_match(&t, &ref6, 126);
+	ok(ret == true, "match: ipv6 - last byte, precise prefix");
+	ret = netblock_match(&t, &ref6, 127);
+	ok(ret == false, "match: ipv6 - last byte, not match");
+
 	return 0;
 }
diff --git a/tests/fake_server.h b/tests/fake_server.h
index 4e62fc2ad5..b12b3e456d 100644
--- a/tests/fake_server.h
+++ b/tests/fake_server.h
@@ -14,7 +14,7 @@ static inline void create_root_zone(server_t *server, mm_ctx_t *mm)
 	/* SOA RDATA. */
 	#define SOA_RDLEN 30
 	static const uint8_t SOA_RDATA[SOA_RDLEN] = {
-	        0x02, 0x6e, 0x73, 0x00,        /* ns. */
+	        0x02, 'n', 's', 0x00,          /* ns. */
 	        0x04, 'm', 'a', 'i', 'l', 0x00,/* mail. */
 	        0x77, 0xdf, 0x1e, 0x63,        /* serial */
 	        0x00, 0x01, 0x51, 0x80,        /* refresh */
@@ -24,11 +24,7 @@ static inline void create_root_zone(server_t *server, mm_ctx_t *mm)
 	};
 
 	/* Insert root zone. */
-	conf_zone_t *conf = malloc(sizeof(conf_zone_t));
-	conf_init_zone(conf);
-	conf->name = strdup(".");
-
-	zone_t *root = zone_new(conf);
+	zone_t *root = zone_new(ROOT_DNAME);
 	root->contents = zone_contents_new(root->name);
 
 	knot_rrset_t *soa = knot_rrset_new(root->name, KNOT_RRTYPE_SOA, KNOT_CLASS_IN, mm);
@@ -57,9 +53,23 @@ static inline int create_fake_server(server_t *server, mm_ctx_t *mm)
 	}
 
 	/* Create configuration. */
-	s_config = conf_new(strdup("rc:/noconf"));
-	conf()->identity = strdup("bogus.ns");
-	conf()->version = strdup("0.11");
+	const char *conf_str = 	"server:\n identity: bogus.ns\n version: 0.11\n";
+	conf_t *conf;
+	ret = conf_new(&conf, conf_scheme, NULL);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+	ret = conf_import(conf, conf_str, false);
+	if (ret != KNOT_EOK) {
+		conf_free(conf, false);
+		return ret;
+	}
+	ret = conf_post_open(conf);
+	if (ret != KNOT_EOK) {
+		conf_free(conf, false);
+		return ret;
+	}
+	conf_update(conf);
 
 	/* Insert root zone. */
 	create_root_zone(server, mm);
diff --git a/tests/journal.c b/tests/journal.c
index ffb7429ef0..06780dde3e 100644
--- a/tests/journal.c
+++ b/tests/journal.c
@@ -213,8 +213,7 @@ static void test_store_load(const char *jfilename)
 	uint8_t *apex = (uint8_t *)"\4test";
 
 	/* Create fake zone. */
-	conf_zone_t zconf = { .ixfr_db = (char *)jfilename, .ixfr_fslimit = filesize };
-	zone_t z = { .name = apex, .conf = &zconf };
+	zone_t z = { .name = apex };
 
 	/* Save and load changeset. */
 	changeset_t ch;
@@ -223,7 +222,7 @@ static void test_store_load(const char *jfilename)
 	ok(ret == KNOT_EOK, "journal: store changeset");
 	list_t l;
 	init_list(&l);
-	ret = journal_load_changesets(&z, &l, 0, 1);
+	ret = journal_load_changesets(jfilename, &z, &l, 0, 1);
 	ok(ret == KNOT_EOK && changesets_eq(TAIL(l), &ch), "journal: load changeset");
 	changeset_clear(&ch);
 	changesets_free(&l);
@@ -241,7 +240,7 @@ static void test_store_load(const char *jfilename)
 
 	/* Load all changesets stored until now. */
 	serial--;
-	ret = journal_load_changesets(&z, &l, 0, serial);
+	ret = journal_load_changesets(jfilename, &z, &l, 0, serial);
 	changesets_free(&l);
 	ok(ret == KNOT_EOK, "journal: load changesets");
 
@@ -257,7 +256,7 @@ static void test_store_load(const char *jfilename)
 
 	/* Load all changesets, except the first one that got evicted. */
 	init_list(&l);
-	ret = journal_load_changesets(&z, &l, 1, serial + 1);
+	ret = journal_load_changesets(jfilename, &z, &l, 1, serial + 1);
 	changesets_free(&l);
 	ok(ret == KNOT_EOK, "journal: load changesets after flush");
 }
diff --git a/tests/process_answer.c b/tests/process_answer.c
index 7797d87219..c353470497 100644
--- a/tests/process_answer.c
+++ b/tests/process_answer.c
@@ -157,7 +157,7 @@ int main(int argc, char *argv[])
 	/* Cleanup. */
 	mp_delete(mm.ctx);
 	server_deinit(&server);
-	conf_free(conf());
+	conf_free(conf(), false);
 
 	return 0;
 }
diff --git a/tests/process_query.c b/tests/process_query.c
index 095133e963..8c2bda070d 100644
--- a/tests/process_query.c
+++ b/tests/process_query.c
@@ -184,7 +184,7 @@ int main(int argc, char *argv[])
 	/* Cleanup. */
 	mp_delete((struct mempool *)mm.ctx);
 	server_deinit(&server);
-	conf_free(conf());
+	conf_free(conf(), false);
 
 	return 0;
 }
diff --git a/tests/requestor.c b/tests/requestor.c
index ab4a2c3a08..00601c2842 100644
--- a/tests/requestor.c
+++ b/tests/requestor.c
@@ -64,7 +64,7 @@ static void* responder_thread(void *arg)
 #define CONNECTED_TESTS    4
 #define TESTS_COUNT DISCONNECTED_TESTS + CONNECTED_TESTS
 
-static struct knot_request *make_query(struct knot_requestor *requestor,  conf_iface_t *remote)
+static struct knot_request *make_query(struct knot_requestor *requestor, conf_remote_t *remote)
 {
 	knot_pkt_t *pkt = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, requestor->mm);
 	assert(pkt);
@@ -75,7 +75,7 @@ static struct knot_request *make_query(struct knot_requestor *requestor,  conf_i
 	return knot_request_make(requestor->mm, dst, src, pkt, 0);
 }
 
-static void test_disconnected(struct knot_requestor *requestor, conf_iface_t *remote)
+static void test_disconnected(struct knot_requestor *requestor, conf_remote_t *remote)
 {
 	/* Enqueue packet. */
 	int ret = knot_requestor_enqueue(requestor, make_query(requestor, remote));
@@ -87,7 +87,7 @@ static void test_disconnected(struct knot_requestor *requestor, conf_iface_t *re
 	is_int(KNOT_ECONN, ret, "requestor: disconnected/wait");
 }
 
-static void test_connected(struct knot_requestor *requestor, conf_iface_t *remote)
+static void test_connected(struct knot_requestor *requestor, conf_remote_t *remote)
 {
 	/* Enqueue packet. */
 	int ret = knot_requestor_enqueue(requestor, make_query(requestor, remote));
@@ -121,8 +121,8 @@ int main(int argc, char *argv[])
 	mm_ctx_t mm;
 	mm_ctx_mempool(&mm, MM_DEFAULT_BLKSIZE);
 
-	conf_iface_t remote;
-	memset(&remote, 0, sizeof(conf_iface_t));
+	conf_remote_t remote;
+	memset(&remote, 0, sizeof(conf_remote_t));
 	sockaddr_set(&remote.addr, AF_INET, "127.0.0.1", 0);
 	sockaddr_set(&remote.via, AF_INET, "127.0.0.1", 0);
 
@@ -170,7 +170,7 @@ int main(int argc, char *argv[])
 	/* Cleanup. */
 	mp_delete((struct mempool *)mm.ctx);
 	server_deinit(&server);
-	conf_free(conf());
+	conf_free(conf(), false);
 
 	return 0;
 }
diff --git a/tests/rrl.c b/tests/rrl.c
index aa26114cb9..46be1dedb3 100644
--- a/tests/rrl.c
+++ b/tests/rrl.c
@@ -23,8 +23,8 @@
 #include "knot/conf/conf.h"
 #include "knot/server/rrl.h"
 #include "knot/zone/zone.h"
-#include "knot/conf/conf.h"
 #include "libknot/descriptor.h"
+#include "libknot/internal/sockaddr.h"
 
 /* Enable time-dependent tests. */
 //#define ENABLE_TIMED_TESTS
@@ -141,10 +141,9 @@ int main(int argc, char *argv[])
 	is_int(KNOT_EOK, ret, "rrl: setlocks");
 
 	/* 4. N unlimited requests. */
-	conf_zone_t *zone_conf = malloc(sizeof(conf_zone_t));
-	conf_init_zone(zone_conf);
-	zone_conf->name = strdup("rrl.");
-	zone_t *zone = zone_new(zone_conf);
+	knot_dname_t *zone_name = knot_dname_from_str_alloc("rrl.");
+	zone_t *zone = zone_new(zone_name);
+	knot_dname_free(&zone_name, NULL);
 
 	struct sockaddr_storage addr;
 	struct sockaddr_storage addr6;
diff --git a/tests/zone_timers.c b/tests/zone_timers.c
index edc5c617a4..38d83f92ab 100644
--- a/tests/zone_timers.c
+++ b/tests/zone_timers.c
@@ -46,10 +46,13 @@ int main(int argc, char *argv[])
 	const char *dbid = mkdtemp(dbid_buf);
 
 	// Mockup zones.
-	conf_zone_t zone_conf = { .name = "test1." };
-	zone_t *zone_1 = zone_new(&zone_conf);
-	zone_conf.name = "test2.";
-	zone_t *zone_2 = zone_new(&zone_conf);
+	knot_dname_t *zone_name;
+	zone_name = knot_dname_from_str_alloc("test1.");
+	zone_t *zone_1 = zone_new(zone_name);
+	knot_dname_free(&zone_name, NULL);
+	zone_name = knot_dname_from_str_alloc("test2.");
+	zone_t *zone_2 = zone_new(zone_name);
+	knot_dname_free(&zone_name, NULL);
 	assert(zone_1 && zone_2);
 
 	// Mockup zonedb.
@@ -115,8 +118,6 @@ int main(int argc, char *argv[])
 	   memcmp(timers, empty_timers, sizeof(timers)) == 0, "zone timers: sweep");
 
 	// Clean up.
-	zone_1->conf = NULL;
-	zone_2->conf = NULL;
 	zone_free(&zone_1);
 	zone_free(&zone_2);
 	close_timers_db(db);
diff --git a/tests/zonedb.c b/tests/zonedb.c
index 323952d355..f522ba478c 100644
--- a/tests/zonedb.c
+++ b/tests/zonedb.c
@@ -50,11 +50,10 @@ int main(int argc, char *argv[])
 
 	/* Populate. */
 	for (unsigned i = 0; i < ZONE_COUNT; ++i) {
-		conf_zone_t *zone_conf = malloc(sizeof(conf_zone_t));
-		conf_init_zone(zone_conf);
-		zone_conf->name = strdup(zone_list[i]);
+		knot_dname_t *zone_name = knot_dname_from_str_alloc(zone_list[i]);
+		zones[i] = zone_new(zone_name);
+		knot_dname_free(&zone_name, NULL);
 
-		zones[i] = zone_new(zone_conf);
 		if (zones[i] == NULL) {
 			goto cleanup;
 		}
-- 
GitLab