From f9af77fcee186041eb311b56cfd10a676db98d15 Mon Sep 17 00:00:00 2001
From: Daniel Salzman <daniel.salzman@nic.cz>
Date: Sat, 28 Feb 2015 14:14:08 +0100
Subject: [PATCH] conf: initial commit for lmdb based configuration

---
 Knot.files                     |   14 +-
 src/Makefile.am                |    8 +
 src/knot/conf/conf.c           | 1224 ++++++++++++++++++++++++++++++++
 src/knot/conf/conf.h           |  259 +++++++
 src/knot/conf/confdb.c         |  641 +++++++++++++++++
 src/knot/conf/confdb.h         |  118 +++
 src/knot/conf/scheme.c         |  178 +++++
 src/knot/conf/scheme.h         |   91 +++
 src/knot/conf/tools.c          |  241 +++++++
 src/knot/conf/tools.h          |   69 ++
 src/libknot/errcode.c          |    5 +
 src/libknot/internal/errcode.h |    5 +
 12 files changed, 2845 insertions(+), 8 deletions(-)
 create mode 100644 src/knot/conf/conf.c
 create mode 100644 src/knot/conf/conf.h
 create mode 100644 src/knot/conf/confdb.c
 create mode 100644 src/knot/conf/confdb.h
 create mode 100644 src/knot/conf/scheme.c
 create mode 100644 src/knot/conf/scheme.h
 create mode 100644 src/knot/conf/tools.c
 create mode 100644 src/knot/conf/tools.h

diff --git a/Knot.files b/Knot.files
index cdcc15ead4..41ae70ddcf 100644
--- a/Knot.files
+++ b/Knot.files
@@ -176,14 +176,14 @@ src/knot/common/ref.c
 src/knot/common/ref.h
 src/knot/common/time.h
 src/knot/common/trim.h
-src/knot/conf/cf-lex.l
-src/knot/conf/cf-parse.y
 src/knot/conf/conf.c
 src/knot/conf/conf.h
-src/knot/conf/extra.c
-src/knot/conf/extra.h
-src/knot/conf/includes.c
-src/knot/conf/includes.h
+src/knot/conf/confdb.c
+src/knot/conf/confdb.h
+src/knot/conf/scheme.c
+src/knot/conf/scheme.h
+src/knot/conf/tools.c
+src/knot/conf/tools.h
 src/knot/ctl/estimator.c
 src/knot/ctl/estimator.h
 src/knot/ctl/knotc_main.c
@@ -462,7 +462,6 @@ tests/acl.c
 tests/base32hex.c
 tests/base64.c
 tests/changeset.c
-tests/conf.c
 tests/descriptor.c
 tests/dname.c
 tests/dthreads.c
@@ -486,7 +485,6 @@ tests/requestor.c
 tests/rrl.c
 tests/rrset.c
 tests/rrset_wire.c
-tests/sample_conf.h
 tests/server.c
 tests/utils.c
 tests/wire.c
diff --git a/src/Makefile.am b/src/Makefile.am
index ef13810085..9c099938f2 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -194,6 +194,14 @@ knotd_SOURCES =					\
 
 # static: server shared
 libknotd_la_SOURCES =				\
+	knot/conf/conf.c			\
+	knot/conf/conf.h			\
+	knot/conf/confdb.c			\
+	knot/conf/confdb.h			\
+	knot/conf/scheme.c			\
+	knot/conf/scheme.h			\
+	knot/conf/tools.c			\
+	knot/conf/tools.h			\
 	knot/ctl/estimator.c			\
 	knot/ctl/estimator.h			\
 	knot/ctl/process.c			\
diff --git a/src/knot/conf/conf.c b/src/knot/conf/conf.c
new file mode 100644
index 0000000000..0a6de0ffae
--- /dev/null
+++ b/src/knot/conf/conf.c
@@ -0,0 +1,1224 @@
+/*  Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <assert.h>
+#include <dirent.h>
+#include <grp.h>
+#include <inttypes.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <urcu.h>
+
+#include "knot/conf/conf.h"
+#include "knot/conf/confdb.h"
+#include "knot/conf/tools.h"
+#include "knot/common/log.h"
+#include "knot/server/dthreads.h"
+#include "libknot/errcode.h"
+#include "libknot/internal/macros.h"
+#include "libknot/internal/mem.h"
+#include "libknot/internal/mempattern.h"
+#include "libknot/internal/mempool.h"
+#include "libknot/internal/namedb/namedb_lmdb.h"
+#include "libknot/internal/sockaddr.h"
+#include "libknot/yparser/ypformat.h"
+#include "libknot/yparser/yptrafo.h"
+
+#define MAX_INCLUDE_DEPTH	5
+
+conf_t *s_conf;
+
+int conf_new(
+	conf_t **conf,
+	const yp_item_t *scheme,
+	const char *db_dir)
+{
+	if (conf == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	conf_t *out = malloc(sizeof(conf_t));
+	if (out == NULL) {
+		return KNOT_ENOMEM;
+	}
+	memset(out, 0, sizeof(conf_t));
+
+	// Initialize config scheme.
+	int ret = yp_scheme_copy(&out->scheme, scheme);
+	if (ret != KNOT_EOK) {
+		free(out);
+		return ret;
+	}
+
+	// Prepare namedb api.
+	out->mm = malloc(sizeof(mm_ctx_t));
+	mm_ctx_mempool(out->mm, MM_DEFAULT_BLKSIZE);
+	struct namedb_lmdb_opts lmdb_opts = NAMEDB_LMDB_OPTS_INITIALIZER;
+	lmdb_opts.flags.env = NAMEDB_LMDB_NOTLS;
+
+	// A temporary solution until proper trie support in namedb is available.
+	if (db_dir == NULL) {
+		char tpl[] = "/tmp/knot-confdb.XXXXXX";
+		db_dir = mkdtemp(tpl);
+		if (db_dir == NULL) {
+			log_error("failed to create temporary directory");
+			return EXIT_FAILURE;
+		}
+		out->tmp_dir = strdup(db_dir);
+	}
+	lmdb_opts.path = db_dir;
+	out->api = namedb_lmdb_api();
+
+	// Open database.
+	ret = out->api->init(&out->db, out->mm, &lmdb_opts);
+	if (ret != KNOT_EOK) {
+		goto new_error;
+	}
+
+	// Initialize/check database.
+	namedb_txn_t txn;
+	ret = out->api->txn_begin(out->db, &txn, 0);
+	if (ret != KNOT_EOK) {
+		out->api->deinit(out->db);
+		goto new_error;
+	}
+
+	ret = conf_db_init(out, &txn);
+	if (ret != KNOT_EOK) {
+		out->api->txn_abort(&txn);
+		out->api->deinit(out->db);
+		goto new_error;
+	}
+
+	ret = out->api->txn_commit(&txn);
+	if (ret != KNOT_EOK) {
+		out->api->deinit(out->db);
+		goto new_error;
+	}
+
+	// Open common read-only transaction.
+	ret = out->api->txn_begin(out->db, &out->read_txn, NAMEDB_RDONLY);
+	if (ret != KNOT_EOK) {
+		out->api->deinit(out->db);
+		goto new_error;
+	}
+
+	*conf = out;
+
+	return KNOT_EOK;
+new_error:
+	yp_scheme_free(out->scheme);
+	free(out->mm);
+	free(out);
+
+	return ret;
+}
+
+int conf_clone(
+	conf_t **conf)
+{
+	if (conf == NULL || s_conf == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	conf_t *out = malloc(sizeof(conf_t));
+	if (out == NULL) {
+		return KNOT_ENOMEM;
+	}
+	memset(out, 0, sizeof(conf_t));
+
+	// Initialize config scheme.
+	int ret = yp_scheme_copy(&out->scheme, s_conf->scheme);
+	if (ret != KNOT_EOK) {
+		free(out);
+		return ret;
+	}
+
+	// Set shared items.
+	out->api = s_conf->api;
+	out->mm = s_conf->mm;
+	out->db = s_conf->db;
+	out->filename = s_conf->filename;
+	out->tmp_dir = s_conf->tmp_dir;
+
+	// Open common read-only transaction.
+	ret = out->api->txn_begin(out->db, &out->read_txn, NAMEDB_RDONLY);
+	if (ret != KNOT_EOK) {
+		yp_scheme_free(out->scheme);
+		free(out);
+		return ret;
+	}
+
+	*conf = out;
+
+	return KNOT_EOK;
+}
+
+int conf_post_open(
+	conf_t *conf)
+{
+	if (conf == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	conf->hostname = sockaddr_hostname();
+
+	return KNOT_EOK;
+}
+
+void conf_update(
+	conf_t *conf)
+{
+	if (conf == NULL) {
+		return;
+	}
+
+	conf_t **current_conf = &s_conf;
+	conf_t *old_conf = rcu_xchg_pointer(current_conf, conf);
+
+	synchronize_rcu();
+
+	if (old_conf) {
+		conf_free(old_conf, true);
+	}
+}
+
+void conf_free(
+	conf_t *conf,
+	bool is_clone)
+{
+	if (conf == NULL) {
+		return;
+	}
+
+	yp_scheme_free(conf->scheme);
+	conf->api->txn_abort(&conf->read_txn);
+	free(conf->hostname);
+
+	if (!is_clone) {
+		conf->api->deinit(conf->db);
+		free(conf->mm);
+		free(conf->filename);
+	}
+
+	// Remove temporary database.
+	DIR *dir;
+	if (!is_clone && conf->tmp_dir != NULL &&
+	    (dir = opendir(conf->tmp_dir)) != NULL) {
+		// Prepare own dirent structure (see NOTES in man readdir_r).
+		size_t len = offsetof(struct dirent, d_name) +
+		             fpathconf(dirfd(dir), _PC_NAME_MAX) + 1;
+
+		struct dirent *entry = malloc(len);
+		if (entry != NULL) {
+			memset(entry, 0, len);
+			struct dirent *result = NULL;
+			int ret;
+
+			while ((ret = readdir_r(dir, entry, &result)) == 0 &&
+			       result != NULL) {
+				if (entry->d_name[0] == '.') {
+					continue;
+				}
+				char *file = sprintf_alloc("%s/%s",
+							   conf->tmp_dir,
+							   entry->d_name);
+				remove(file);
+				free(file);
+			}
+
+			free(entry);
+			closedir(dir);
+			remove(conf->tmp_dir);
+			free(conf->tmp_dir);
+		}
+	}
+
+	free(conf);
+}
+
+static int parser_process(
+	conf_t *conf,
+	namedb_txn_t *txn,
+	yp_parser_t *parser,
+	yp_check_ctx_t *ctx,
+	size_t *incl_depth)
+{
+	int ret = yp_scheme_check_parser(ctx, parser);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	ret = conf_db_set(conf, txn, ctx);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	const yp_item_t *item = (ctx->event == YP_EKEY0) ? ctx->key0 : ctx->key1;
+	conf_call_f *sem_check = (conf_call_f *)item->misc[0];
+	conf_call_f *callback = (conf_call_f *)item->misc[1];
+	conf_args_t args = {
+		conf, txn, parser->file.name, incl_depth, ctx->key0, ctx->key1,
+		ctx->id, ctx->id_len, ctx->data, ctx->data_len
+	};
+
+	// Call semantic check if any.
+	if (sem_check != NULL && (ret = sem_check(&args)) != KNOT_EOK) {
+		return ret;
+	}
+
+	// Call callback function if any.
+	if (callback != NULL && (ret = callback(&args)) != KNOT_EOK) {
+		return ret;
+	}
+
+	return KNOT_EOK;
+}
+
+int conf_parse(
+	conf_t *conf,
+	namedb_txn_t *txn,
+	const char *input,
+	bool is_file,
+	size_t *incl_depth)
+{
+	if (conf == NULL || txn == NULL || input == NULL ||
+	    incl_depth == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	// Check for include loop.
+	if ((*incl_depth)++ > MAX_INCLUDE_DEPTH) {
+		return KNOT_EPARSEFAIL;
+	}
+
+	yp_parser_t *parser = malloc(sizeof(yp_parser_t));
+	if (parser == NULL) {
+		return KNOT_ENOMEM;
+	}
+	yp_init(parser);
+
+	int ret;
+	if (is_file) {
+		ret = yp_set_input_file(parser, input);
+	} else {
+		ret = yp_set_input_string(parser, input, strlen(input));
+	}
+	if (ret != KNOT_EOK) {
+		goto init_error;
+	}
+
+	yp_check_ctx_t *ctx = yp_scheme_check_init(conf->scheme);
+	if (ctx == NULL) {
+		ret = KNOT_ENOMEM;
+		goto init_error;
+	}
+
+	while ((ret = yp_parse(parser)) == KNOT_EOK) {
+		ret = parser_process(conf, txn, parser, ctx, incl_depth);
+		if (ret != KNOT_EOK) {
+			break;
+		}
+	}
+
+	yp_scheme_check_deinit(ctx);
+
+	if (ret != KNOT_EOF) {
+		log_error("invalid configuration%s%s%s, line %zu (%s)",
+		          (is_file ? " file '" : ""),
+		          (is_file ? input : ""),
+		          (is_file ? "'" : ""),
+		          parser->line_count, knot_strerror(ret));
+		goto init_error;
+	}
+
+	(*incl_depth)--;
+
+	ret = KNOT_EOK;
+init_error:
+	yp_deinit(parser);
+	free(parser);
+
+	return ret;
+}
+
+int conf_import(
+	conf_t *conf,
+	const char *input,
+	bool is_file)
+{
+	if (conf == NULL || input == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	namedb_txn_t txn;
+	int ret = conf->api->txn_begin(conf->db, &txn, 0);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	// Drop the current DB content.
+	ret = conf->api->clear(&txn);
+	if (ret != KNOT_EOK) {
+		conf->api->txn_abort(&txn);
+		return ret;
+	}
+
+	// Initialize new DB.
+	ret = conf_db_init(conf, &txn);
+	if (ret != KNOT_EOK) {
+		conf->api->txn_abort(&txn);
+		return ret;
+	}
+
+	size_t depth = 0;
+
+	// Parse and import given file.
+	ret = conf_parse(conf, &txn, input, is_file, &depth);
+	if (ret != KNOT_EOK) {
+		conf->api->txn_abort(&txn);
+		return ret;
+	}
+
+	// Commit new configuration.
+	ret = conf->api->txn_commit(&txn);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	// Update read-only transaction.
+	conf->api->txn_abort(&conf->read_txn);
+	ret = conf->api->txn_begin(conf->db, &conf->read_txn, NAMEDB_RDONLY);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	return KNOT_EOK;
+}
+
+static int export_group(
+	conf_t *conf,
+	FILE *fp,
+	yp_item_t *group,
+	uint8_t *id,
+	size_t id_len,
+	char *out,
+	size_t out_len,
+	yp_style_t style)
+{
+	yp_item_t *item;
+	for (item = group->sub_items; item->name != NULL; item++) {
+		conf_val_t bin;
+		bin.code = conf_db_get(conf, &conf->read_txn, group->name,
+		                       item->name, id, id_len, &bin);
+		if (bin.code == KNOT_ENOENT) {
+			continue;
+		} else if (bin.code != KNOT_EOK) {
+			return bin.code;
+		}
+
+		// Format single/multiple-valued item.
+		size_t values = conf_val_count(&bin);
+		for (size_t i = 1; i <= values; i++) {
+			conf_db_val(&bin);
+			int ret = yp_format_key1(item, bin.data, bin.len, out,
+			                         out_len, style, i == 1,
+			                         i == values);
+			if (ret != KNOT_EOK) {
+				return ret;
+			}
+			fprintf(fp, "%s", out);
+
+			if (values > 1) {
+				conf_val_next(&bin);
+			}
+		}
+	}
+
+	fprintf(fp, "\n");
+
+	return KNOT_EOK;
+}
+
+int conf_export(
+	conf_t *conf,
+	const char *file_name,
+	yp_style_t style)
+{
+	if (conf == NULL || file_name == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	// Prepare common buffer;
+	const size_t buff_len = 2 * CONF_MAX_DATA_LEN; // Rough limit.
+	char *buff = malloc(buff_len);
+	if (buff == NULL) {
+		return KNOT_ENOMEM;
+	}
+
+	FILE *fp = fopen(file_name, "w");
+	if (fp == NULL) {
+		free(buff);
+		return KNOT_EFILE;
+	}
+
+	int ret;
+
+	// Iterate over the current scheme.
+	yp_item_t *item;
+	for (item = conf->scheme; item->name != NULL; item++) {
+		// Skip non-group items (include).
+		if (item->type != YP_TGRP) {
+			continue;
+		}
+
+		// Check if the item is ever stored in DB.
+		uint8_t item_code;
+		ret = conf_db_code(conf, &conf->read_txn, CONF_CODE_KEY0_ROOT,
+		                   item->name, true, &item_code);
+		if (ret == KNOT_ENOENT) {
+			continue;
+		} else if (ret != KNOT_EOK) {
+			goto export_error;
+		}
+
+		// Export group name.
+		ret = yp_format_key0(item, NULL, 0, buff, buff_len, style,
+		                     true, true);
+		if (ret != KNOT_EOK) {
+			goto export_error;
+		}
+		fprintf(fp, "%s", buff);
+
+		// Export simple group without identifiers.
+		if (item->var.g.id == NULL) {
+			ret = export_group(conf, fp, item, NULL, 0, buff,
+			                   buff_len, style);
+			if (ret != KNOT_EOK) {
+				goto export_error;
+			}
+
+			continue;
+		}
+
+		// Iterate over all identifiers.
+		conf_iter_t iter;
+		ret = conf_db_iter_begin(conf, &conf->read_txn, item->name,
+		                         &iter);
+		if (ret != KNOT_EOK) {
+			goto export_error;
+		}
+
+		while (ret == KNOT_EOK) {
+			uint8_t *id;
+			size_t id_len;
+			ret = conf_db_iter_id(conf, &iter, &id, &id_len);
+			if (ret != KNOT_EOK) {
+				conf_db_iter_finish(conf, &iter);
+				goto export_error;
+			}
+
+			// Export identifier.
+			ret = yp_format_id(item->var.g.id, id, id_len, buff,
+			                   buff_len, style);
+			if (ret != KNOT_EOK) {
+				conf_db_iter_finish(conf, &iter);
+				goto export_error;
+			}
+			fprintf(fp, "%s", buff);
+
+			// Export other items.
+			ret = export_group(conf, fp, item, id, id_len, buff,
+			                   buff_len, style);
+			if (ret != KNOT_EOK) {
+				conf_db_iter_finish(conf, &iter);
+				goto export_error;
+			}
+
+			ret = conf_db_iter_next(conf, &iter);
+		}
+
+		conf_db_iter_finish(conf, &iter);
+	}
+
+	ret = KNOT_EOK;
+export_error:
+	fclose(fp);
+	free(buff);
+
+	return ret;
+}
+
+static conf_val_t raw_id_get(
+	conf_t *conf,
+	const yp_name_t *key0_name,
+	const yp_name_t *key1_name,
+	const uint8_t *id,
+	size_t id_len)
+{
+	conf_val_t val = { NULL };
+
+	val.code = conf_db_get(conf, &conf->read_txn, key0_name, key1_name,
+	                       id, id_len, &val);
+	switch (val.code) {
+	default:
+		log_error("failed to read configuration '%s/%s' (%s)",
+		          key0_name + 1, key1_name + 1, knot_strerror(val.code));
+	case KNOT_EOK:
+	case KNOT_ENOENT:
+		return val;
+	}
+}
+
+conf_val_t conf_get(
+	conf_t *conf,
+	const yp_name_t *key0_name,
+	const yp_name_t *key1_name)
+{
+	// Check for empty key1.
+	if (key1_name == NULL) {
+		conf_val_t val = { NULL };
+		val.code = KNOT_EINVAL;
+		return val;
+	}
+
+	return raw_id_get(conf, key0_name, key1_name, NULL, 0);
+}
+
+conf_val_t conf_id_get(
+	conf_t *conf,
+	const yp_name_t *key0_name,
+	const yp_name_t *key1_name,
+	conf_val_t *id)
+{
+	// Check for invalid id.
+	if (id != NULL) {
+		if (id->code != KNOT_EOK) {
+			conf_val_t val = { NULL };
+			val.code = id->code;
+			return val;
+		}
+		conf_db_val(id);
+	} else {
+		conf_val_t val = { NULL };
+		val.code = KNOT_EINVAL;
+		return val;
+	}
+
+	return raw_id_get(conf, key0_name, key1_name,
+	                  (id == NULL) ? NULL : id->data,
+	                  (id == NULL) ? 0 : id->len);
+}
+
+conf_val_t conf_zone_get(
+	conf_t *conf,
+	const yp_name_t *key1_name,
+	const knot_dname_t *dname)
+{
+	conf_val_t val = { NULL };
+
+	if (dname == NULL) {
+		val.code = KNOT_EINVAL;
+		return val;
+	}
+
+	int dname_size = knot_dname_size(dname);
+
+	// Try to get explicit value.
+	val.code = conf_db_get(conf, &conf->read_txn, C_ZONE, key1_name,
+	                       dname, dname_size, &val);
+	switch (val.code) {
+	case KNOT_EOK:
+		return val;
+	default:
+		log_zone_error(dname, "failed to read configuration '%s/%s' (%s)",
+		               C_ZONE + 1, key1_name + 1, knot_strerror(val.code));
+	case KNOT_ENOENT:
+		break;
+	}
+
+	// Check if a template is available.
+	val.code = conf_db_get(conf, &conf->read_txn, C_ZONE, C_TPL, dname,
+	                       dname_size, &val);
+	switch (val.code) {
+	case KNOT_EOK:
+		// Use the specified template.
+		conf_db_val(&val);
+		val.code = conf_db_get(conf, &conf->read_txn, C_TPL, key1_name,
+		                       val.data, val.len, &val);
+		break;
+	default:
+		log_zone_error(dname, "failed to read configuration '%s/%s' (%s)",
+		               C_ZONE + 1, C_TPL + 1, knot_strerror(val.code));
+	case KNOT_ENOENT:
+		// Use the default template.
+		val.code = conf_db_get(conf, &conf->read_txn, C_TPL, key1_name,
+		                       CONF_DEFAULT_ID + 1, CONF_DEFAULT_ID[0],
+		                       &val);
+	}
+
+	switch (val.code) {
+	default:
+		log_zone_error(dname, "failed to read configuration '%s/%s' (%s)",
+		               C_TPL + 1, key1_name + 1, knot_strerror(val.code));
+	case KNOT_EOK:
+	case KNOT_ENOENT:
+		break;
+	}
+
+	return val;
+}
+
+conf_val_t conf_default_get(
+	conf_t *conf,
+	const yp_name_t *key1_name)
+{
+	conf_val_t val = { NULL };
+
+	val.code = conf_db_get(conf, &conf->read_txn, C_TPL, key1_name,
+	                       CONF_DEFAULT_ID + 1, CONF_DEFAULT_ID[0], &val);
+	switch (val.code) {
+	default:
+		log_error("failed to read configuration '%s/%s' (%s)",
+		          C_TPL + 1, key1_name + 1, knot_strerror(val.code));
+	case KNOT_EOK:
+	case KNOT_ENOENT:
+		break;
+	}
+
+	return val;
+}
+
+size_t conf_id_count(
+	conf_t *conf,
+	const yp_name_t *key0_name)
+{
+	size_t count = 0;
+	conf_iter_t iter = { NULL };
+
+	int ret = conf_db_iter_begin(conf, &conf->read_txn, key0_name, &iter);
+	switch (ret) {
+	case KNOT_EOK:
+		break;
+	default:
+		log_error("failed to iterate through configuration '%s' (%s)",
+		          key0_name + 1, knot_strerror(ret));
+	case KNOT_ENOENT:
+		return count;
+	}
+
+	while (ret == KNOT_EOK) {
+		count++;
+		ret = conf_db_iter_next(conf, &iter);
+	}
+	conf_db_iter_finish(conf, &iter);
+
+	return count;
+}
+
+conf_iter_t conf_iter(
+	conf_t *conf,
+	const yp_name_t *key0_name)
+{
+	conf_iter_t iter = { NULL };
+
+	iter.code = conf_db_iter_begin(conf, &conf->read_txn, key0_name, &iter);
+	switch (iter.code) {
+	default:
+		log_error("failed to iterate thgrough configuration '%s' (%s)",
+		          key0_name + 1, knot_strerror(iter.code));
+	case KNOT_EOK:
+	case KNOT_ENOENT:
+		return iter;
+	}
+}
+
+void conf_iter_next(
+	conf_t *conf,
+	conf_iter_t *iter)
+{
+	iter->code = conf_db_iter_next(conf, iter);
+	switch (iter->code) {
+	default:
+		log_error("failed to read next configuration item (%s)",
+		          knot_strerror(iter->code));
+	case KNOT_EOK:
+	case KNOT_EOF:
+		return;
+	}
+}
+
+conf_val_t conf_iter_id(
+	conf_t *conf,
+	conf_iter_t *iter)
+{
+	conf_val_t val = { NULL };
+
+	val.code = conf_db_iter_id(conf, iter, (uint8_t **)&val.blob,
+	                           &val.blob_len);
+	switch (val.code) {
+	default:
+		log_error("failed to read configuration identifier (%s)",
+		          knot_strerror(val.code));
+	case KNOT_EOK:
+		val.item = iter->item;
+		return val;
+	}
+}
+
+void conf_iter_finish(
+	conf_t *conf,
+	conf_iter_t *iter)
+{
+	conf_db_iter_finish(conf, iter);
+}
+
+size_t conf_val_count(
+	conf_val_t *val)
+{
+	if (val == NULL || val->code != KNOT_EOK) {
+		return 0;
+	}
+
+	if (!(val->item->flags & YP_FMULTI)) {
+		return 1;
+	}
+
+	size_t count = 0;
+	conf_db_val(val);
+	while (val->code == KNOT_EOK) {
+		count++;
+		conf_db_val_next(val);
+	}
+	if (val->code != KNOT_EOF) {
+		return 0;
+	}
+
+	// Reset to the initial state.
+	conf_db_val(val);
+
+	return count;
+}
+
+void conf_val_next(
+	conf_val_t *val)
+{
+	conf_db_val_next(val);
+}
+
+int64_t conf_int(
+	conf_val_t *val)
+{
+	assert(val != NULL);
+	assert(val->item->type == YP_TINT ||
+	       (val->item->type == YP_TREF &&
+	        val->item->var.r.ref->var.g.id->type == YP_TINT));
+
+	if (val->code == KNOT_EOK) {
+		conf_db_val(val);
+		return yp_int(val->data, val->len);
+	} else {
+		return val->item->var.i.dflt;
+	}
+}
+
+bool conf_bool(
+	conf_val_t *val)
+{
+	assert(val != NULL);
+	assert(val->item->type == YP_TBOOL ||
+	       (val->item->type == YP_TREF &&
+	        val->item->var.r.ref->var.g.id->type == YP_TBOOL));
+
+	if (val->code == KNOT_EOK) {
+		conf_db_val(val);
+		return yp_bool(val->len);
+	} else {
+		return val->item->var.b.dflt;
+	}
+}
+
+unsigned conf_opt(
+	conf_val_t *val)
+{
+	assert(val != NULL);
+	assert(val->item->type == YP_TOPT ||
+	       (val->item->type == YP_TREF &&
+	        val->item->var.r.ref->var.g.id->type == YP_TOPT));
+
+	if (val->code == KNOT_EOK) {
+		conf_db_val(val);
+		return yp_opt(val->data);
+	} else {
+		return val->item->var.o.dflt;
+	}
+}
+
+const char* conf_str(
+	conf_val_t *val)
+{
+	assert(val != NULL);
+	assert(val->item->type == YP_TSTR ||
+	       (val->item->type == YP_TREF &&
+	        val->item->var.r.ref->var.g.id->type == YP_TSTR));
+
+	if (val->code == KNOT_EOK) {
+		conf_db_val(val);
+		return yp_str(val->data);
+	} else {
+		return val->item->var.s.dflt;
+	}
+}
+
+char* conf_abs_path(
+	conf_val_t *val,
+	const char *base_dir)
+{
+	assert(val != NULL);
+
+	const char *path = conf_str(val);
+
+	if (path[0] == '/') {
+		return strdup(path);
+	} else {
+		char *abs_path;
+		if (base_dir == NULL) {
+			char *cwd = realpath("./", NULL);
+			abs_path = sprintf_alloc("%s/%s", cwd, path);
+			free(cwd);
+		} else {
+			abs_path = sprintf_alloc("%s/%s", base_dir, path);
+		}
+		return abs_path;
+	}
+}
+
+const knot_dname_t* conf_dname(
+	conf_val_t *val)
+{
+	assert(val != NULL);
+	assert(val->item->type == YP_TDNAME ||
+	       (val->item->type == YP_TREF &&
+	        val->item->var.r.ref->var.g.id->type == YP_TDNAME));
+
+	if (val->code == KNOT_EOK) {
+		conf_db_val(val);
+		return yp_dname(val->data);
+	} else {
+		return (const knot_dname_t *)val->item->var.d.dflt;
+	}
+}
+
+struct sockaddr_storage conf_addr(
+	conf_val_t *val,
+	const char *sock_base_dir)
+{
+	assert(val != NULL);
+	assert(val->item->type == YP_TADDR ||
+	       (val->item->type == YP_TREF &&
+	        val->item->var.r.ref->var.g.id->type == YP_TADDR));
+
+	struct sockaddr_storage out = { AF_UNSPEC };
+
+	if (val->code == KNOT_EOK) {
+		int port;
+		conf_db_val(val);
+		out = yp_addr(val->data, val->len, &port);
+
+		// val->data[0] is socket type identifier.
+		if (out.ss_family == AF_UNIX && val->data[1] != '/' &&
+		    sock_base_dir != NULL) {
+			char *tmp = sprintf_alloc("%s/%.*s", sock_base_dir,
+			                          (int)val->len - 1,
+			                          val->data + 1);
+			val->code = sockaddr_set(&out, AF_UNIX, tmp, 0);
+			free(tmp);
+		} else if (port != -1) {
+			sockaddr_port_set(&out, port);
+		} else {
+			sockaddr_port_set(&out, val->item->var.a.dflt_port);
+		}
+	} else {
+		const char *dflt_socket = val->item->var.a.dflt_socket;
+		if (dflt_socket != NULL) {
+			if (dflt_socket[0] != '/' && sock_base_dir != NULL) {
+				char *tmp = sprintf_alloc("%s/%s", sock_base_dir,
+				                          dflt_socket);
+				val->code = sockaddr_set(&out, AF_UNIX, tmp, 0);
+				free(tmp);
+			} else {
+				val->code = sockaddr_set(&out, AF_UNIX,
+				                         dflt_socket, 0);
+			}
+		}
+	}
+
+	return out;
+}
+
+struct sockaddr_storage conf_net(
+	conf_val_t *val,
+	unsigned *prefix_length)
+{
+	assert(val != NULL && prefix_length != NULL);
+	assert(val->item->type == YP_TNET ||
+	       (val->item->type == YP_TREF &&
+	        val->item->var.r.ref->var.g.id->type == YP_TNET));
+
+	struct sockaddr_storage out = { AF_UNSPEC };
+
+	if (val->code == KNOT_EOK) {
+		int prefix;
+		conf_db_val(val);
+		out = yp_addr(val->data, val->len, &prefix);
+		if (prefix < 0) {
+			if (out.ss_family == AF_INET) {
+				*prefix_length = IPV4_PREFIXLEN;
+			} else if (out.ss_family == AF_INET6) {
+				*prefix_length = IPV6_PREFIXLEN;
+			}
+		} else {
+			*prefix_length = prefix;
+		}
+	} else {
+		*prefix_length = 0;
+	}
+
+	return out;
+}
+
+void conf_data(
+	conf_val_t *val)
+{
+	assert(val != NULL);
+	assert(val->item->type == YP_TB64 || val->item->type == YP_TDATA ||
+	       (val->item->type == YP_TREF &&
+	        (val->item->var.r.ref->var.g.id->type == YP_TB64 ||
+	         val->item->var.r.ref->var.g.id->type == YP_TDATA)));
+
+	if (val->code == KNOT_EOK) {
+		conf_db_val(val);
+	} else {
+		val->data = (const uint8_t *)val->item->var.d.dflt;
+		val->len = val->item->var.d.dflt_len;
+	}
+}
+
+static char* dname_to_filename(
+	const knot_dname_t *name,
+	const char *suffix)
+{
+	char *str = knot_dname_to_str_alloc(name);
+	if (str == NULL) {
+		return NULL;
+	}
+
+	// Replace possible slashes with underscores.
+	for (char *ch = str; *ch != '\0'; ch++) {
+		if (*ch == '/') {
+			*ch = '_';
+		}
+	}
+
+	char *out = sprintf_alloc("%s%s", str, suffix);
+	free(str);
+
+	return out;
+}
+
+char* conf_zonefile(
+	conf_t *conf,
+	const knot_dname_t *zone)
+{
+	assert(conf != NULL && zone != NULL);
+
+	// Item 'file' is not template item (cannot use conf_zone_get)! */
+	const char *file = NULL;
+	conf_val_t file_val = { NULL };
+	file_val.code = conf_db_get(conf, &conf->read_txn, C_ZONE, C_FILE,
+	                            zone, knot_dname_size(zone), &file_val);
+	if (file_val.code == KNOT_EOK) {
+		file = conf_str(&file_val);
+		if (file != NULL && file[0] == '/') {
+			return strdup(file);
+		}
+	}
+
+	char *abs_storage = NULL;
+	conf_val_t storage_val = conf_zone_get(conf, C_STORAGE, zone);
+	if (storage_val.code == KNOT_EOK) {
+		abs_storage = conf_abs_path(&storage_val, NULL);
+		if (abs_storage == NULL) {
+			return NULL;
+		}
+	}
+
+	char *out = NULL;
+
+	if (file == NULL) {
+		char *file = dname_to_filename(zone, "zone");
+		out = sprintf_alloc("%s/%s", abs_storage, file);
+		free(file);
+	} else {
+		out = sprintf_alloc("%s/%s", abs_storage, file);
+	}
+
+	free(abs_storage);
+
+	return out;
+}
+
+char* conf_journalfile(
+	conf_t *conf,
+	const knot_dname_t *zone)
+{
+	assert(conf != NULL && zone != NULL);
+
+	char *abs_storage = NULL;
+	conf_val_t storage_val = conf_zone_get(conf, C_STORAGE, zone);
+	if (storage_val.code == KNOT_EOK) {
+		abs_storage = conf_abs_path(&storage_val, NULL);
+		if (abs_storage == NULL) {
+			return NULL;
+		}
+	}
+
+	char *name = dname_to_filename(zone, "diff.db");
+	char *out = sprintf_alloc("%s/%s", abs_storage, name);
+	free(name);
+	free(abs_storage);
+
+	return out;
+}
+
+size_t conf_udp_threads(
+	conf_t *conf)
+{
+	conf_val_t val = conf_get(conf, C_SRV, C_WORKERS);
+	int64_t workers = conf_int(&val);
+	if (workers < 1) {
+		return dt_optimal_size();
+	}
+
+	return workers;
+}
+
+size_t conf_tcp_threads(
+	conf_t *conf)
+{
+	size_t thrcount = conf_udp_threads(conf);
+	return MAX(thrcount * 2, CONF_XFERS);
+}
+
+int conf_bg_threads(
+	conf_t *conf)
+{
+	conf_val_t val = conf_get(conf, C_SRV, C_BG_WORKERS);
+	int64_t bg_workers = conf_int(&val);
+	if (bg_workers < 1) {
+		return MIN(dt_optimal_size(), CONF_XFERS);
+	}
+
+	return bg_workers;
+}
+
+void conf_user(
+	conf_t *conf,
+	int *uid,
+	int *gid)
+{
+	assert(uid);
+	assert(gid);
+
+	int new_uid = getuid();
+	int new_gid = getgid();
+
+	conf_val_t val = conf_get(conf, C_SRV, C_USER);
+	if (val.code == KNOT_EOK) {
+		const char *user = conf_str(&val);
+
+		// Search for user:group separator.
+		char *sep_pos = strchr(user, ':');
+		if (sep_pos != NULL) {
+			// Process group name.
+			struct group *grp = getgrnam(sep_pos + 1);
+			if (grp != NULL) {
+				new_gid = grp->gr_gid;
+			} else {
+				log_error("invalid group name '%s'", sep_pos + 1);
+			}
+
+			// Cut off group part.
+			*sep_pos = '\0';
+		}
+
+		// Process user name.
+		struct passwd *pwd = getpwnam(user);
+		if (pwd != NULL) {
+			new_uid = pwd->pw_uid;
+		} else {
+			log_error("invalid user name '%s'", user);
+		}
+	}
+
+	*uid = new_uid;
+	*gid = new_gid;
+}
+
+conf_remote_t conf_remote(
+	conf_t *conf,
+	conf_val_t *id)
+{
+	conf_remote_t out = { { AF_UNSPEC } };
+
+	conf_val_t rundir_val = conf_get(conf, C_SRV, C_RUNDIR);
+	char *rundir = conf_abs_path(&rundir_val, NULL);
+
+	// Get remote address.
+	conf_val_t val = conf_id_get(conf, C_RMT, C_ADDR, id);
+	if (val.code != KNOT_EOK) {
+		log_error("invalid remote in configuration");
+		return out;
+	}
+	out.addr = conf_addr(&val, rundir);
+
+	// Get outgoing address (optional).
+	val = conf_id_get(conf, C_RMT, C_VIA, id);
+	out.via = conf_addr(&val, rundir);
+
+	// Get TSIG key (optional).
+	conf_val_t key_id = conf_id_get(conf, C_RMT, C_KEY, id);
+	if (key_id.code == KNOT_EOK) {
+		out.key.name = (knot_dname_t *)conf_dname(&key_id);
+
+		val = conf_id_get(conf, C_KEY, C_ALG, &key_id);
+		out.key.algorithm = conf_opt(&val);
+
+		val = conf_id_get(conf, C_KEY, C_SECRET, &key_id);
+		conf_data(&val);
+		out.key.secret.data = (uint8_t *)val.data;
+		out.key.secret.size = val.len;
+	}
+
+	free(rundir);
+
+	return out;
+}
diff --git a/src/knot/conf/conf.h b/src/knot/conf/conf.h
new file mode 100644
index 0000000000..4f5026cae1
--- /dev/null
+++ b/src/knot/conf/conf.h
@@ -0,0 +1,259 @@
+/*  Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+/*!
+ * \file
+ *
+ * Server configuration and API.
+ *
+ * \addtogroup config
+ *
+ * @{
+ */
+
+#pragma once
+
+#include <stdlib.h>
+#include <sys/socket.h>
+
+#include "knot/conf/scheme.h"
+#include "libknot/internal/lists.h"
+#include "libknot/internal/namedb/namedb.h"
+#include "libknot/rrtype/tsig.h"
+#include "libknot/yparser/ypscheme.h"
+
+#define CONF_XFERS		10
+#define CONF_DEFAULT_ID		((uint8_t *)"\x08""default\0")
+#define CONF_DEFAULT_FILE	(CONFIG_DIR "/knot.conf")
+//#define CONF_DEFAULT_DBDIR	(STORAGE_DIR "/confdb")
+
+typedef struct {
+	const struct namedb_api *api;
+	yp_item_t *scheme;
+	mm_ctx_t *mm;
+	namedb_t *db;
+	// Read-only transaction for config access.
+	namedb_txn_t read_txn;
+	// For automatic NSID or CH ident.
+	char *hostname;
+	// For reload if started with config file.
+	char *filename;
+	// Temporary database path.
+	char *tmp_dir;
+} conf_t;
+
+typedef struct {
+	struct sockaddr_storage addr;
+	struct sockaddr_storage via;
+	knot_tsig_key_t key;
+} conf_remote_t;
+
+typedef struct {
+	const yp_item_t *item;
+	const uint8_t *blob;
+	size_t blob_len;
+	// Public items.
+	const uint8_t *data;
+	size_t len;
+	int code; // Return code.
+} conf_val_t;
+
+typedef struct {
+	const yp_item_t *item;
+	namedb_iter_t *iter;
+	uint8_t key0_code;
+	// Public items.
+	int code; // Return code.
+} conf_iter_t;
+
+extern conf_t *s_conf;
+
+static inline conf_t* conf(void) {
+	return s_conf;
+}
+
+int conf_new(
+	conf_t **conf,
+	const yp_item_t *scheme,
+	const char *db_dir
+);
+
+int conf_clone(
+	conf_t **conf
+);
+
+int conf_post_open(
+	conf_t *conf
+);
+
+void conf_update(
+	conf_t *conf
+);
+
+void conf_free(
+	conf_t *conf,
+	bool is_clone
+);
+
+int conf_parse(
+	conf_t *conf,
+	namedb_txn_t *txn,
+	const char *input,
+	bool is_file,
+	size_t *incl_depth
+);
+
+int conf_import(
+	conf_t *conf,
+	const char *input,
+	bool is_file
+);
+
+int conf_export(
+	conf_t *conf,
+	const char *file_name,
+	yp_style_t style
+);
+
+/*****************/
+
+conf_val_t conf_get(
+	conf_t *conf,
+	const yp_name_t *key0_name,
+	const yp_name_t *key1_name
+);
+
+conf_val_t conf_id_get(
+	conf_t *conf,
+	const yp_name_t *key0_name,
+	const yp_name_t *key1_name,
+	conf_val_t *id
+);
+
+conf_val_t conf_zone_get(
+	conf_t *conf,
+	const yp_name_t *key1_name,
+	const knot_dname_t *dname
+);
+
+conf_val_t conf_default_get(
+	conf_t *conf,
+	const yp_name_t *key1_name
+);
+
+size_t conf_id_count(
+	conf_t *conf,
+	const yp_name_t *key0_name
+);
+
+conf_iter_t conf_iter(
+	conf_t *conf,
+	const yp_name_t *key0_name
+);
+
+void conf_iter_next(
+	conf_t *conf,
+	conf_iter_t *iter
+);
+
+conf_val_t conf_iter_id(
+	conf_t *conf,
+	conf_iter_t *iter
+);
+
+void conf_iter_finish(
+	conf_t *conf,
+	conf_iter_t *iter
+);
+
+size_t conf_val_count(
+	conf_val_t *val
+);
+
+void conf_val_next(
+	conf_val_t *val
+);
+
+int64_t conf_int(
+	conf_val_t *val
+);
+
+bool conf_bool(
+	conf_val_t *val
+);
+
+unsigned conf_opt(
+	conf_val_t *val
+);
+
+const char* conf_str(
+	conf_val_t *val
+);
+
+char* conf_abs_path(
+	conf_val_t *val,
+	const char *base_dir
+);
+
+const knot_dname_t* conf_dname(
+	conf_val_t *val
+);
+
+struct sockaddr_storage conf_addr(
+	conf_val_t *val,
+	const char *sock_base_dir
+);
+
+struct sockaddr_storage conf_net(
+	conf_val_t *val,
+	unsigned *prefix_length
+);
+
+void conf_data(
+	conf_val_t *val
+);
+
+char* conf_zonefile(
+	conf_t *conf,
+	const knot_dname_t *zone
+);
+
+char* conf_journalfile(
+	conf_t *conf,
+	const knot_dname_t *zone
+);
+
+size_t conf_udp_threads(
+	conf_t *conf
+);
+
+size_t conf_tcp_threads(
+	conf_t *conf
+);
+
+int conf_bg_threads(
+	conf_t *conf
+);
+
+void conf_user(
+	conf_t *conf,
+	int *uid,
+	int *gid
+);
+
+conf_remote_t conf_remote(
+	conf_t *conf,
+	conf_val_t *id
+);
diff --git a/src/knot/conf/confdb.c b/src/knot/conf/confdb.c
new file mode 100644
index 0000000000..543ea1c3de
--- /dev/null
+++ b/src/knot/conf/confdb.c
@@ -0,0 +1,641 @@
+/*  Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "knot/conf/confdb.h"
+#include "libknot/errcode.h"
+#include "libknot/yparser/yptrafo.h"
+
+#define KEY0_POS	0
+#define KEY1_POS	1
+#define NAME_POS	2
+
+static int conf_db_check(
+	conf_t *conf,
+	namedb_txn_t *txn)
+{
+	int ret = conf->api->count(txn);
+	if (ret == 0) { // Empty DB.
+		return KNOT_CONF_EMPTY;
+	} else if (ret > 0) { // Check existing DB.
+		uint8_t k[2] = { CONF_CODE_KEY0_ROOT, CONF_CODE_KEY1_VERSION };
+		namedb_val_t key = { k, sizeof(k) };
+		namedb_val_t data;
+
+		// Get conf-DB version.
+		ret = conf->api->find(txn, &key, &data, 0);
+		if (ret != KNOT_EOK) {
+			return ret;
+		}
+
+		// Check conf-DB version.
+		if (data.len != 1 ||
+		    ((uint8_t *)data.data)[0] != CONF_DB_VERSION) {
+			return KNOT_CONF_EVERSION;
+		}
+
+		return KNOT_EOK;
+	} else { // DB error.
+		return ret;
+	}
+}
+
+int conf_db_init(
+	conf_t *conf,
+	namedb_txn_t *txn)
+{
+	if (conf == NULL || txn == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	uint8_t k[2] = { CONF_CODE_KEY0_ROOT, CONF_CODE_KEY1_VERSION };
+	namedb_val_t key = { k, sizeof(k) };
+
+	int ret = conf->api->count(txn);
+	if (ret == 0) { // Initialize empty DB with DB version.
+		uint8_t d[1] = { CONF_DB_VERSION };
+		namedb_val_t data = { d, sizeof(d) };
+		return conf->api->insert(txn, &key, &data, 0);
+	} else if (ret > 0) { // Check existing DB.
+		return conf_db_check(conf, txn);
+	} else { // DB error.
+		return ret;
+	}
+}
+
+int conf_db_code(
+	conf_t *conf,
+	namedb_txn_t *txn,
+	uint8_t section_code,
+	const yp_name_t *name,
+	bool read_only,
+	uint8_t *db_code)
+{
+	if (conf == NULL || txn == NULL || name == NULL || db_code == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	namedb_val_t key;
+	uint8_t k[CONF_MIN_KEY_LEN + YP_MAX_ITEM_NAME_LEN];
+	k[KEY0_POS] = section_code;
+	k[KEY1_POS] = CONF_CODE_KEY1_ITEMS;
+	memcpy(k + NAME_POS, name + 1, name[0]);
+	key.data = k;
+	key.len = CONF_MIN_KEY_LEN + name[0];
+
+	// Check if the item is already registered.
+	namedb_val_t data;
+	int ret = conf->api->find(txn, &key, &data, 0);
+	if (ret == KNOT_EOK) {
+		*db_code = ((uint8_t *)data.data)[0];
+		return KNOT_EOK;
+	} else if (read_only) {
+		return KNOT_ENOENT;
+	}
+
+	uint8_t new_code = CONF_CODE_KEY1_FIRST;
+
+	// Reduce the key to common prefix only.
+	key.len = CONF_MIN_KEY_LEN;
+
+	// Find the smallest unused item code.
+	namedb_iter_t *it = conf->api->iter_begin(txn, NAMEDB_NOOP);
+	it = conf->api->iter_seek(it, &key, NAMEDB_GEQ);
+	while (it != NULL) {
+		namedb_val_t iter_key;
+		ret = conf->api->iter_key(it, &iter_key);
+		if (ret != KNOT_EOK) {
+			conf->api->iter_finish(it);
+			return ret;
+		}
+		uint8_t *key_data = (uint8_t *)iter_key.data;
+
+		// Check for database prefix end.
+		if (key_data[KEY0_POS] != k[KEY0_POS] ||
+		    key_data[KEY1_POS] != k[KEY1_POS]) {
+			break;
+		}
+
+		namedb_val_t iter_val;
+		ret = conf->api->iter_val(it, &iter_val);
+		if (ret != KNOT_EOK) {
+			conf->api->iter_finish(it);
+			return ret;
+		}
+		uint8_t code = ((uint8_t *)iter_val.data)[0];
+
+		// Check the current code if already used.
+		if (new_code <= code) {
+			if (code == CONF_CODE_KEY1_LAST) {
+				conf->api->iter_finish(it);
+				return KNOT_ERANGE;
+			}
+			new_code = code + 1;
+		}
+
+		it = conf->api->iter_next(it);
+	}
+	conf->api->iter_finish(it);
+
+	// Restore the full key.
+	key.len = CONF_MIN_KEY_LEN + name[0];
+
+	// Fill the data with a new code.
+	data.data = &new_code;
+	data.len = sizeof(new_code);
+
+	// Register new item code.
+	ret = conf->api->insert(txn, &key, &data, 0);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	*db_code = new_code;
+
+	return KNOT_EOK;
+}
+
+static bool new_data(
+	const namedb_val_t *new,
+	const namedb_val_t *current)
+{
+	uint8_t *d = current->data;
+	size_t len = 0;
+
+	// Loop over the data array. Each item has 2B prefix.
+	while (len < current->len) {
+		uint16_t prefix;
+		memcpy(&prefix, d + len, sizeof(prefix));
+		prefix = le16toh(prefix);
+		len += sizeof(prefix);
+
+		// Check for the same data.
+		if (prefix == new->len &&
+		    memcmp(d + len, new->data, new->len) == 0) {
+			return false;
+		}
+		len += prefix;
+	}
+
+	return true;
+}
+
+static int db_insert(
+	conf_t *conf,
+	namedb_txn_t *txn,
+	namedb_val_t *key,
+	namedb_val_t *data,
+	bool multi)
+{
+	if (multi) {
+		namedb_val_t d;
+
+		if (data->len > UINT16_MAX) {
+			return KNOT_ERANGE;
+		}
+
+		int ret = conf->api->find(txn, key, &d, 0);
+		if (ret == KNOT_ENOENT) {
+			d.len = 0;
+		} else if (ret == KNOT_EOK) {
+			// Check for duplicate data.
+			if (!new_data(data, &d)) {
+				return KNOT_EOK;
+			}
+		} else {
+			return ret;
+		}
+
+		// Prepare buffer for all data.
+		size_t total_len = d.len + sizeof(uint16_t) + data->len;
+		if (total_len > CONF_MAX_DATA_LEN) {
+			return KNOT_ESPACE;
+		}
+
+		uint8_t *new_data = malloc(total_len);
+		if (new_data == NULL) {
+			return KNOT_ENOMEM;
+		}
+
+		size_t new_len = 0;
+
+		// Copy current data array.
+		memcpy(new_data, d.data, d.len);
+		new_len += d.len;
+
+		// Copy length prefix for the new data item.
+		uint16_t prefix = htole16(data->len);
+		memcpy(new_data + new_len, &prefix, sizeof(prefix));
+		new_len += sizeof(prefix);
+
+		// Copy the new data item.
+		memcpy(new_data + new_len, data->data, data->len);
+		new_len += data->len;
+
+		d.data = new_data;
+		d.len = new_len;
+
+		// Insert new (or append) data.
+		ret = conf->api->insert(txn, key, &d, 0);
+
+		free(new_data);
+
+		return ret;
+	} else {
+		if (data->len > CONF_MAX_DATA_LEN) {
+			return KNOT_ERANGE;
+		}
+
+		// Insert new (overwrite old) data.
+		return conf->api->insert(txn, key, data, 0);
+	}
+}
+
+int conf_db_set(
+	conf_t *conf,
+	namedb_txn_t *txn,
+	yp_check_ctx_t *in)
+{
+	if (conf == NULL || txn == NULL || in == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	uint8_t k[CONF_MAX_KEY_LEN];
+	namedb_val_t key = { k, CONF_MIN_KEY_LEN };
+
+	// Ignore alone key0 insertion.
+	if (in->event == YP_EKEY0) {
+		return KNOT_EOK;
+	}
+
+	// Set key0 code.
+	int ret = conf_db_code(conf, txn, CONF_CODE_KEY0_ROOT, in->key0->name,
+	                       false, &k[KEY0_POS]);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	// Set id part.
+	if (in->id_len > 0) {
+		memcpy(k + CONF_MIN_KEY_LEN, in->id, in->id_len);
+		key.len += in->id_len;
+	}
+
+	// Insert id.
+	if (in->event == YP_EID) {
+		k[KEY1_POS] = CONF_CODE_KEY1_ID;
+		namedb_val_t val = { NULL };
+
+		// Check for already configured id.
+		ret = conf->api->find(txn, &key, &val, 0);
+		if (ret == KNOT_EOK) {
+			return KNOT_CONF_EREDEFINE;
+		}
+
+		return db_insert(conf, txn, &key, &val, false);
+	// Insert data.
+	} else {
+		// Set key1 code.
+		ret = conf_db_code(conf, txn, k[KEY0_POS], in->key1->name,
+		                   false, &k[KEY1_POS]);
+		if (ret != KNOT_EOK) {
+			return ret;
+		}
+
+		namedb_val_t val = { in->data, in->data_len };
+		return db_insert(conf, txn, &key, &val,
+		                 in->key1->flags & YP_FMULTI);
+	}
+}
+
+int conf_db_get(
+	conf_t *conf,
+	namedb_txn_t *txn,
+	const yp_name_t *key0,
+	const yp_name_t *key1,
+	const uint8_t *id,
+	size_t id_len,
+	conf_val_t *out)
+{
+	if (conf == NULL || txn == NULL || key0 == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	// Look-up item in the scheme.
+	if (out != NULL) {
+		out->item = yp_scheme_find(key1, key0, conf->scheme);
+		assert(out->item != NULL);
+	}
+
+	uint8_t k[CONF_MAX_KEY_LEN];
+	namedb_val_t key = { k, CONF_MIN_KEY_LEN };
+	namedb_val_t val;
+
+	// Set key0 code.
+	int ret = conf_db_code(conf, txn, CONF_CODE_KEY0_ROOT, key0, true,
+	                       &k[KEY0_POS]);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	// Set key1 code.
+	if (key1 != NULL) {
+		ret = conf_db_code(conf, txn, k[KEY0_POS], key1, true,
+		                   &k[KEY1_POS]);
+		if (ret != KNOT_EOK) {
+			return ret;
+		}
+	// Set key1 id code.
+	} else if (id != NULL && id_len > 0) {
+		k[KEY1_POS] = CONF_CODE_KEY1_ID;
+	// At least key1 or id must be non-zero.
+	} else {
+		return KNOT_EINVAL;
+	}
+
+	// Fill the item id.
+	if (id != NULL && id_len > 0) {
+		if (id_len > YP_MAX_ID_LEN) {
+			return KNOT_EINVAL;
+		}
+		memcpy(k + CONF_MIN_KEY_LEN, id, id_len);
+		key.len += id_len;
+	}
+
+	// Read the data.
+	ret = conf->api->find(txn, &key, &val, 0);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	// Set the output.
+	if (out != NULL) {
+		out->blob = val.data;
+		out->blob_len = val.len;
+		out->data = NULL;
+		out->len = 0;
+	}
+
+	return KNOT_EOK;
+}
+
+void conf_db_val(
+	conf_val_t *val)
+{
+	assert(val != NULL);
+	assert(val->code == KNOT_EOK || val->code == KNOT_EOF);
+
+	if (val->item->flags & YP_FMULTI) {
+		// Check if already called.
+		if (val->data != NULL) {
+			return;
+		}
+
+		assert(val->blob != NULL);
+		memcpy(&val->len, val->blob, sizeof(uint16_t));
+		val->len = le16toh(val->len);
+		val->data = val->blob + sizeof(uint16_t);
+		val->code = KNOT_EOK;
+	} else {
+		// Check for empty data.
+		if (val->blob_len == 0) {
+			val->data = NULL;
+			val->len = 0;
+			val->code = KNOT_EOK;
+			return;
+		} else {
+			assert(val->blob != NULL);
+			val->data = val->blob;
+			val->len = val->blob_len;
+			val->code = KNOT_EOK;
+		}
+	}
+}
+
+void conf_db_val_next(
+	conf_val_t *val)
+{
+	assert(val != NULL);
+	assert(val->code == KNOT_EOK);
+	assert(val->item->flags & YP_FMULTI);
+
+	// Check for the 'zero' call.
+	if (val->data == NULL) {
+		conf_db_val(val);
+		return;
+	}
+
+	if (val->data + val->len < val->blob + val->blob_len) {
+		val->data += val->len;
+		memcpy(&val->len, val->data, sizeof(uint16_t));
+		val->len = le16toh(val->len);
+		val->data += sizeof(uint16_t);
+		val->code = KNOT_EOK;
+	} else {
+		val->data = NULL;
+		val->len = 0;
+		val->code = KNOT_EOF;
+	}
+}
+
+int conf_db_iter_begin(
+	conf_t *conf,
+	namedb_txn_t *txn,
+	const yp_name_t *key0,
+	conf_iter_t *iter)
+{
+	if (conf == NULL || txn == NULL || key0 == NULL || iter == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	// Look-up group id item in the scheme.
+	const yp_item_t *grp = yp_scheme_find(key0, NULL, conf->scheme);
+	assert(grp != NULL);
+	assert(grp->type == YP_TGRP);
+	iter->item = grp->var.g.id;
+
+	// Get key0 code.
+	int ret = conf_db_code(conf, txn, CONF_CODE_KEY0_ROOT, key0, true,
+	                       &iter->key0_code);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	// Prepare key prefix.
+	uint8_t k[2] = { iter->key0_code, CONF_CODE_KEY1_ID };
+	namedb_val_t key = { k, sizeof(k) };
+
+	// Get the data.
+	iter->iter = conf->api->iter_begin(txn, NAMEDB_NOOP);
+	iter->iter = conf->api->iter_seek(iter->iter, &key, NAMEDB_GEQ);
+
+	return KNOT_EOK;
+}
+
+int conf_db_iter_next(
+	conf_t *conf,
+	conf_iter_t *iter)
+{
+	if (conf == NULL || iter == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	if (iter->iter == NULL) {
+		return KNOT_EOK;
+	}
+
+	// Move to the next key-value.
+	iter->iter = conf->api->iter_next(iter->iter);
+	if (iter->iter == NULL) {
+		return KNOT_EOF;
+	}
+
+	// Get new key.
+	namedb_val_t key;
+	int ret = conf->api->iter_key(iter->iter, &key);
+	if (ret != KNOT_EOK) {
+		conf->api->iter_finish(iter->iter);
+		return ret;
+	}
+	uint8_t *key_data = (uint8_t *)key.data;
+
+	// Check for key overflow.
+	if (key_data[KEY0_POS] != iter->key0_code ||
+	    key_data[KEY1_POS] != CONF_CODE_KEY1_ID) {
+		conf->api->iter_finish(iter->iter);
+		iter->iter = NULL;
+		return KNOT_EOF;
+	}
+
+	return KNOT_EOK;
+}
+
+void conf_db_iter_finish(
+	conf_t *conf,
+	conf_iter_t *iter)
+{
+	if (conf == NULL || iter == NULL) {
+		return;
+	}
+
+	conf->api->iter_finish(iter->iter);
+}
+
+int conf_db_iter_id(
+	conf_t *conf,
+	conf_iter_t *iter,
+	uint8_t **data,
+	size_t *data_len)
+{
+	if (conf == NULL || iter == NULL || data == NULL || data_len == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	namedb_val_t key;
+	int ret = conf->api->iter_key(iter->iter, &key);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	*data = key.data + CONF_MIN_KEY_LEN;
+	*data_len = key.len - CONF_MIN_KEY_LEN;
+
+	return KNOT_EOK;
+}
+
+int conf_db_raw_dump(
+	conf_t *conf,
+	const char *file_name)
+{
+	if (conf == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	FILE *fp = stdout;
+	if (file_name != NULL) {
+		fp = fopen(file_name, "w");
+		if (fp == NULL) {
+			return KNOT_ERROR;
+		}
+	}
+
+	int ret = KNOT_EOK;
+
+	namedb_txn_t txn = conf->read_txn;
+	namedb_iter_t *it = conf->api->iter_begin(&txn, NAMEDB_FIRST);
+	while (it != NULL) {
+		namedb_val_t key;
+		ret = conf->api->iter_key(it, &key);
+		if (ret != KNOT_EOK) {
+			break;
+		}
+
+		namedb_val_t data;
+		ret = conf->api->iter_val(it, &data);
+		if (ret != KNOT_EOK) {
+			break;
+		}
+
+		uint8_t *k = (uint8_t *)key.data;
+		uint8_t *d = (uint8_t *)data.data;
+		if (k[1] == CONF_CODE_KEY1_ITEMS) {
+			fprintf(fp, "[%i][%i]%.*s", k[0], k[1],
+			        (int)key.len - 2, k + 2);
+			fprintf(fp, ": %u\n", d[0]);
+		} else if (k[1] == CONF_CODE_KEY1_ID) {
+			fprintf(fp, "[%i][%i](%zu){", k[0], k[1], key.len - 2);
+			for (size_t i = 2; i < key.len; i++) {
+				fprintf(fp, "%02x", (uint8_t)k[i]);
+			}
+			fprintf(fp, "}\n");
+		} else {
+			fprintf(fp, "[%i][%i]", k[0], k[1]);
+			if (key.len > 2) {
+				fprintf(fp, "(%zu){", key.len - 2);
+				for (size_t i = 2; i < key.len; i++) {
+					fprintf(fp, "%02x", (uint8_t)k[i]);
+				}
+				fprintf(fp, "}");
+			}
+			fprintf(fp, ": (%zu)<", data.len);
+			for (size_t i = 0; i < data.len; i++) {
+				fprintf(fp, "%02x", (uint8_t)d[i]);
+			}
+			fprintf(fp, ">\n");
+		}
+
+		it = conf->api->iter_next(it);
+	}
+	conf->api->iter_finish(it);
+
+	if (file_name != NULL) {
+		fclose(fp);
+	} else {
+		fflush(fp);
+	}
+
+	return ret;
+}
diff --git a/src/knot/conf/confdb.h b/src/knot/conf/confdb.h
new file mode 100644
index 0000000000..5c695c3787
--- /dev/null
+++ b/src/knot/conf/confdb.h
@@ -0,0 +1,118 @@
+/*  Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+/*!
+ * \file
+ *
+ * Configuration database interface.
+ *
+ * \addtogroup config
+ *
+ * @{
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "knot/conf/conf.h"
+#include "libknot/internal/namedb/namedb.h"
+#include "libknot/yparser/ypscheme.h"
+
+/*! Current version of the configuration database structure. */
+#define CONF_DB_VERSION		1
+/*! Minimum length of a database key ([category_id, item_id]. */
+#define CONF_MIN_KEY_LEN	(2 * sizeof(uint8_t))
+/*! Maximum length of a database key ([category_id, item_id, identifier]. */
+#define CONF_MAX_KEY_LEN	(CONF_MIN_KEY_LEN + YP_MAX_ID_LEN)
+/*! Maximum size of database data. */
+#define CONF_MAX_DATA_LEN	65536
+
+enum knot_conf_code {
+	CONF_CODE_KEY0_ROOT    =   0,
+	CONF_CODE_KEY1_ITEMS   =   0,
+	CONF_CODE_KEY1_ID      =   1,
+	CONF_CODE_KEY1_FIRST   =   2,
+	CONF_CODE_KEY1_LAST    = 200,
+	CONF_CODE_KEY1_VERSION = 255
+};
+
+int conf_db_init(
+	conf_t *conf,
+	namedb_txn_t *txn
+);
+
+int conf_db_set(
+	conf_t *conf,
+	namedb_txn_t *txn,
+	yp_check_ctx_t *in
+);
+
+int conf_db_get(
+	conf_t *conf,
+	namedb_txn_t *txn,
+	const yp_name_t *key0,
+	const yp_name_t *key1,
+	const uint8_t *id,
+	size_t id_len,
+	conf_val_t *out
+);
+
+void conf_db_val(
+	conf_val_t *val
+);
+
+void conf_db_val_next(
+	conf_val_t *val
+);
+
+int conf_db_iter_begin(
+	conf_t *conf,
+	namedb_txn_t *txn,
+	const yp_name_t *key0,
+	conf_iter_t *iter
+);
+
+int conf_db_iter_next(
+	conf_t *conf,
+	conf_iter_t *iter
+);
+
+int conf_db_iter_id(
+	conf_t *conf,
+	conf_iter_t *iter,
+	uint8_t **data,
+	size_t *data_len
+);
+
+void conf_db_iter_finish(
+	conf_t *conf,
+	conf_iter_t *iter
+);
+
+int conf_db_code(
+	conf_t *conf,
+	namedb_txn_t *txn,
+	uint8_t section_code,
+	const yp_name_t *name,
+	bool read_only,
+	uint8_t *db_code
+);
+
+int conf_db_raw_dump(
+	conf_t *conf,
+	const char *file_name
+);
diff --git a/src/knot/conf/scheme.c b/src/knot/conf/scheme.c
new file mode 100644
index 0000000000..eb8df40f2d
--- /dev/null
+++ b/src/knot/conf/scheme.c
@@ -0,0 +1,178 @@
+/*  Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "knot/conf/scheme.h"
+#include "knot/conf/tools.h"
+#include "knot/common/log.h"
+#include "knot/ctl/remote.h"
+#include "knot/server/rrl.h"
+#include "knot/updates/acl.h"
+#include "libknot/rrtype/opt.h"
+#include "dnssec/lib/dnssec/tsig.h"
+
+static const lookup_table_t key_algs[] = {
+	{ DNSSEC_TSIG_HMAC_MD5,    "hmac-md5" },
+	{ DNSSEC_TSIG_HMAC_SHA1,   "hmac-sha1" },
+	{ DNSSEC_TSIG_HMAC_SHA224, "hmac-sha224" },
+	{ DNSSEC_TSIG_HMAC_SHA256, "hmac-sha256" },
+	{ DNSSEC_TSIG_HMAC_SHA384, "hmac-sha384" },
+	{ DNSSEC_TSIG_HMAC_SHA512, "hmac-sha512" },
+	{ 0, NULL }
+};
+
+static const lookup_table_t acl_actions[] = {
+	{ ACL_ACTION_DENY, "deny" },
+	{ ACL_ACTION_XFER, "xfer" },
+	{ ACL_ACTION_NOTF, "notify" },
+	{ ACL_ACTION_DDNS, "update" },
+	{ ACL_ACTION_CNTL, "control" },
+	{ 0, NULL }
+};
+
+static const lookup_table_t serial_policies[] = {
+	{ SERIAL_POLICY_INCREMENT, "increment" },
+	{ SERIAL_POLICY_UNIXTIME,  "unixtime" },
+	{ 0, NULL }
+};
+
+static const lookup_table_t log_severities[] = {
+	{ LOG_UPTO(LOG_CRIT),    "critical" },
+	{ LOG_UPTO(LOG_ERR),     "error" },
+	{ LOG_UPTO(LOG_WARNING), "warning" },
+	{ LOG_UPTO(LOG_NOTICE),  "notice" },
+	{ LOG_UPTO(LOG_INFO),    "info" },
+	{ LOG_UPTO(LOG_DEBUG),   "debug" },
+	{ 0, NULL }
+};
+
+static const yp_item_t desc_server[] = {
+	{ C_IDENT,              YP_TSTR,  YP_VNONE },
+	{ C_VERSION,            YP_TSTR,  YP_VNONE },
+	{ C_NSID,               YP_TDATA, YP_VDATA = { 0, NULL, hex_text_to_bin,
+	                                               hex_text_to_txt } },
+	{ C_RUNDIR,             YP_TSTR,  YP_VSTR = { RUN_DIR } },
+	{ C_USER,               YP_TSTR,  YP_VNONE },
+	{ C_PIDFILE,            YP_TSTR,  YP_VSTR = { "knot.pid" } },
+	{ C_WORKERS,            YP_TINT,  YP_VINT = { 1, 255, YP_NIL } },
+	{ C_BG_WORKERS,         YP_TINT,  YP_VINT = { 1, 255, YP_NIL } },
+	{ C_ASYNC_START,        YP_TBOOL, YP_VNONE },
+	{ C_MAX_CONN_IDLE,      YP_TINT,  YP_VINT = { 0, INT32_MAX, 20, YP_STIME } },
+	{ C_MAX_CONN_HANDSHAKE, YP_TINT,  YP_VINT = { 0, INT32_MAX, 5, YP_STIME } },
+	{ C_MAX_CONN_REPLY,     YP_TINT,  YP_VINT = { 0, INT32_MAX, 10, YP_STIME } },
+	{ C_MAX_TCP_CLIENTS,    YP_TINT,  YP_VINT = { 0, INT32_MAX, 100 } },
+	{ C_MAX_UDP_PAYLOAD,    YP_TINT,  YP_VINT = { KNOT_EDNS_MIN_UDP_PAYLOAD,
+	                                              KNOT_EDNS_MAX_UDP_PAYLOAD,
+	                                              4096, YP_SSIZE } },
+	{ C_TRANSFERS,          YP_TINT,  YP_VINT = { 1, INT32_MAX, 10 } },
+	{ C_RATE_LIMIT,         YP_TINT,  YP_VINT = { 0, INT32_MAX, 0 } },
+	{ C_RATE_LIMIT_SLIP,    YP_TINT,  YP_VINT = { 1, RRL_SLIP_MAX, 1 } },
+	{ C_RATE_LIMIT_SIZE,    YP_TINT,  YP_VINT = { 1, INT32_MAX, 393241 } },
+	{ C_LISTEN,             YP_TADDR, YP_VADDR = { 53 }, YP_FMULTI },
+	{ C_COMMENT,            YP_TSTR,  YP_VNONE },
+	{ NULL }
+};
+
+static const yp_item_t desc_key[] = {
+	{ C_ID,      YP_TDNAME, YP_VNONE },
+	{ C_ALG,     YP_TOPT,   YP_VOPT = { key_algs, DNSSEC_TSIG_UNKNOWN } },
+	{ C_SECRET,  YP_TB64,   YP_VNONE },
+	{ C_COMMENT, YP_TSTR,   YP_VNONE },
+	{ NULL }
+};
+
+static const yp_item_t desc_acl[] = {
+	{ C_ID,      YP_TSTR, YP_VNONE },
+	{ C_ADDR,    YP_TNET, YP_VNONE },
+	{ C_KEY,     YP_TREF, YP_VREF = { C_KEY }, YP_FNONE, { check_ref } },
+	{ C_ACTION,  YP_TOPT, YP_VOPT = { acl_actions, ACL_ACTION_DENY }, YP_FMULTI },
+	{ C_COMMENT, YP_TSTR, YP_VNONE },
+	{ NULL }
+};
+
+static const yp_item_t desc_control[] = {
+	{ C_LISTEN,  YP_TADDR, YP_VADDR = { REMOTE_PORT, REMOTE_SOCKET } },
+	{ C_ACL,     YP_TREF,  YP_VREF = { C_ACL }, YP_FMULTI, { check_ref } },
+	{ C_COMMENT, YP_TSTR,  YP_VNONE },
+	{ NULL }
+};
+
+static const yp_item_t desc_remote[] = {
+	{ C_ID,      YP_TSTR,  YP_VNONE },
+	{ C_ADDR,    YP_TADDR, YP_VADDR = { 53 } },
+	{ C_VIA,     YP_TADDR, YP_VNONE },
+	{ C_KEY,     YP_TREF,  YP_VREF = { C_KEY }, YP_FNONE, { check_ref } },
+	{ C_COMMENT, YP_TSTR,  YP_VNONE },
+	{ NULL }
+};
+
+#define ZONE_ITEMS \
+	{ C_STORAGE,        YP_TSTR,  YP_VSTR = { STORAGE_DIR } }, \
+	{ C_MASTER,         YP_TREF,  YP_VREF = { C_RMT }, YP_FMULTI, { check_ref } }, \
+	{ C_NOTIFY,         YP_TREF,  YP_VREF = { C_RMT }, YP_FMULTI, { check_ref } }, \
+	{ C_ACL,            YP_TREF,  YP_VREF = { C_ACL }, YP_FMULTI, { check_ref } }, \
+	{ C_SEM_CHECKS,     YP_TBOOL, YP_VNONE }, \
+	{ C_DISABLE_ANY,    YP_TBOOL, YP_VNONE }, \
+	{ C_NOTIFY_TIMEOUT, YP_TINT,  YP_VINT = { 1, INT32_MAX, 60, YP_STIME } }, \
+	{ C_NOTIFY_RETRIES, YP_TINT,  YP_VINT = { 1, INT32_MAX, 5 } }, \
+	{ C_ZONEFILE_SYNC,  YP_TINT,  YP_VINT = { 0, INT32_MAX, 0, YP_STIME } }, \
+	{ C_IXFR_DIFF,      YP_TBOOL, YP_VNONE }, \
+	{ C_IXFR_FSLIMIT,   YP_TINT,  YP_VINT = { 0, INT64_MAX, INT64_MAX, YP_SSIZE } }, \
+	{ C_DNSSEC_ENABLE,  YP_TBOOL, YP_VNONE }, \
+	{ C_DNSSEC_KEYDIR,  YP_TSTR,  YP_VSTR = { "keys" } }, \
+	{ C_SIG_LIFETIME,   YP_TINT,  YP_VINT = { 3 * 3600, INT32_MAX, 30 * 24 * 3600, YP_STIME } }, \
+	{ C_SERIAL_POLICY,  YP_TOPT,  YP_VOPT = { serial_policies, SERIAL_POLICY_INCREMENT } }, \
+	{ C_COMMENT,        YP_TSTR,  YP_VNONE },
+
+static const yp_item_t desc_template[] = {
+	{ C_ID, YP_TSTR, YP_VNONE },
+	ZONE_ITEMS
+	{ NULL }
+};
+
+static const yp_item_t desc_zone[] = {
+	{ C_DOMAIN, YP_TDNAME, YP_VNONE },
+	{ C_FILE,   YP_TSTR,   YP_VNONE },
+	{ C_TPL,    YP_TREF,   YP_VREF = { C_TPL }, YP_FNONE, { check_ref } },
+	ZONE_ITEMS
+	{ NULL }
+};
+
+static const yp_item_t desc_log[] = {
+	{ C_TO,      YP_TSTR, YP_VNONE },
+	{ C_SERVER,  YP_TOPT, YP_VOPT = { log_severities, 0 } },
+	{ C_ZONE,    YP_TOPT, YP_VOPT = { log_severities, 0 } },
+	{ C_ANY,     YP_TOPT, YP_VOPT = { log_severities, 0 } },
+	{ C_COMMENT, YP_TSTR, YP_VNONE },
+	{ NULL }
+};
+
+const yp_item_t conf_scheme[] = {
+	{ C_SRV,  YP_TGRP, YP_VGRP = { desc_server } },
+	{ C_KEY,  YP_TGRP, YP_VGRP = { desc_key }, YP_FMULTI },
+	{ C_ACL,  YP_TGRP, YP_VGRP = { desc_acl }, YP_FMULTI },
+	{ C_CTL,  YP_TGRP, YP_VGRP = { desc_control } },
+	{ C_RMT,  YP_TGRP, YP_VGRP = { desc_remote }, YP_FMULTI },
+	{ C_TPL,  YP_TGRP, YP_VGRP = { desc_template }, YP_FMULTI },
+	{ C_ZONE, YP_TGRP, YP_VGRP = { desc_zone }, YP_FMULTI },
+	{ C_LOG,  YP_TGRP, YP_VGRP = { desc_log }, YP_FMULTI },
+	{ C_INCL, YP_TSTR, YP_VNONE, YP_FNONE, { NULL, include_file } },
+	{ NULL }
+};
diff --git a/src/knot/conf/scheme.h b/src/knot/conf/scheme.h
new file mode 100644
index 0000000000..34bc07f46a
--- /dev/null
+++ b/src/knot/conf/scheme.h
@@ -0,0 +1,91 @@
+/*  Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+/*!
+ * \file
+ *
+ * Server configuration scheme.
+ *
+ * \addtogroup config
+ *
+ * @{
+ */
+
+#pragma once
+
+#include "libknot/yparser/ypscheme.h"
+
+#define C_ACL			"\x03""acl"
+#define C_ACTION		"\x06""action"
+#define C_ADDR			"\x07""address"
+#define C_ALG			"\x09""algorithm"
+#define C_ANY			"\x03""any"
+#define C_ASYNC_START		"\x12""asynchronous-start"
+#define C_BG_WORKERS		"\x12""background-workers"
+#define C_COMMENT		"\x07""comment"
+#define C_CTL			"\x07""control"
+#define C_DISABLE_ANY		"\x0B""disable-any"
+#define C_DOMAIN		"\x06""domain"
+#define C_DNSSEC_ENABLE		"\x0D""dnssec-enable"
+#define C_DNSSEC_KEYDIR		"\x0D""dnssec-keydir"
+#define C_FILE			"\x04""file"
+#define C_IDENT			"\x08""identity"
+#define C_ID			"\x02""id"
+#define C_INCL			"\x07""include"
+#define C_IXFR_DIFF		"\x15""ixfr-from-differences"
+#define C_IXFR_FSLIMIT		"\x0C""ixfr-fslimit"
+#define C_KEY			"\x03""key"
+#define C_LOG			"\x03""log"
+#define C_LISTEN		"\x06""listen"
+#define C_MASTER		"\x06""master"
+#define C_MAX_CONN_HANDSHAKE	"\x12""max-conn-handshake"
+#define C_MAX_CONN_IDLE		"\x0D""max-conn-idle"
+#define C_MAX_CONN_REPLY	"\x0E""max-conn-reply"
+#define C_MAX_TCP_CLIENTS	"\x0F""max-tcp-clients"
+#define C_MAX_UDP_PAYLOAD	"\x0F""max-udp-payload"
+#define C_MODULE		"\x06""module"
+#define C_NOTIFY		"\x06""notify"
+#define C_NOTIFY_RETRIES	"\x0E""notify-retries"
+#define C_NOTIFY_TIMEOUT	"\x0E""notify-timeout"
+#define C_NSID			"\x04""nsid"
+#define C_PIDFILE		"\x07""pidfile"
+#define C_RATE_LIMIT		"\x0A""rate-limit"
+#define C_RATE_LIMIT_SIZE	"\x0F""rate-limit-size"
+#define C_RATE_LIMIT_SLIP	"\x0F""rate-limit-slip"
+#define C_RMT			"\x06""remote"
+#define C_RUNDIR		"\x06""rundir"
+#define C_SECRET		"\x06""secret"
+#define C_SEM_CHECKS		"\x0F""semantic-checks"
+#define C_SERIAL_POLICY		"\x0D""serial-policy"
+#define C_SERVER		"\x06""server"
+#define C_SIG_LIFETIME		"\x12""signature-lifetime"
+#define C_STORAGE		"\x07""storage"
+#define C_SRV			"\x06""server"
+#define C_TO			"\x02""to"
+#define C_TPL			"\x08""template"
+#define C_TRANSFERS		"\x09""transfers"
+#define C_USER			"\x04""user"
+#define C_VERSION		"\x07""version"
+#define C_VIA			"\x03""via"
+#define C_WORKERS		"\x07""workers"
+#define C_ZONE			"\x04""zone"
+#define C_ZONEFILE_SYNC		"\x0D""zonefile-sync"
+
+enum {
+	SERIAL_POLICY_INCREMENT = 1,
+	SERIAL_POLICY_UNIXTIME  = 2
+};
+
+extern const yp_item_t conf_scheme[];
diff --git a/src/knot/conf/tools.c b/src/knot/conf/tools.c
new file mode 100644
index 0000000000..c813a40dec
--- /dev/null
+++ b/src/knot/conf/tools.c
@@ -0,0 +1,241 @@
+/*  Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <ctype.h>
+#include <dirent.h>
+#include <inttypes.h>
+#include <libgen.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "knot/conf/tools.h"
+#include "knot/conf/conf.h"
+#include "knot/conf/confdb.h"
+#include "knot/conf/scheme.h"
+#include "libknot/errcode.h"
+#include "libknot/yparser/yptrafo.h"
+
+static int hex_to_num(char hex) {
+	if (hex >= '0' && hex <= '9') return hex - '0';
+	if (hex >= 'a' && hex <= 'f') return hex - 'a' + 10;
+	if (hex >= 'A' && hex <= 'F') return hex - 'A' + 10;
+	return -1;
+}
+
+int hex_text_to_bin(
+	char const *txt,
+	size_t txt_len,
+	uint8_t *bin,
+	size_t *bin_len)
+{
+	// Check for hex notation (leading "0x").
+	if (txt_len >= 2 && txt[0] == '0' && txt[1] == 'x') {
+		txt += 2;
+		txt_len -= 2;
+
+		if (txt_len % 2 != 0) {
+			return KNOT_EINVAL;
+		} else if (*bin_len <= txt_len / 2) {
+			return KNOT_ESPACE;
+		}
+
+		// Decode hex string.
+		for (size_t i = 0; i < txt_len; i++) {
+			if (isxdigit((int)txt[i]) == 0) {
+				return KNOT_EINVAL;
+			}
+
+			bin[i] = 16 * hex_to_num(txt[2 * i]) +
+			              hex_to_num(txt[2 * i + 1]);
+		}
+
+		*bin_len = txt_len / 2;
+	} else {
+		if (*bin_len <= txt_len) {
+			return KNOT_ESPACE;
+		}
+
+		memcpy(bin, txt, txt_len);
+		*bin_len = txt_len;
+	}
+
+	return KNOT_EOK;
+}
+
+int hex_text_to_txt(
+	uint8_t const *bin,
+	size_t bin_len,
+	char *txt,
+	size_t *txt_len)
+{
+	bool printable = true;
+
+	// Check for printable string.
+	for (size_t i = 0; i < bin_len; i++) {
+		if (isprint(bin[i]) == 0) {
+			printable = false;
+			break;
+		}
+	}
+
+	if (printable) {
+		if (*txt_len <= bin_len) {
+			return KNOT_ESPACE;
+		}
+
+		memcpy(txt, bin, bin_len);
+		*txt_len = bin_len;
+		txt[*txt_len] = '\0';
+	} else {
+		static const char *hex = "0123456789ABCDEF";
+
+		if (*txt_len <= 2 + 2 * bin_len) {
+			return KNOT_ESPACE;
+		}
+
+		// Write hex prefix.
+		txt[0] = '0';
+		txt[1] = 'x';
+		txt += 2;
+
+		// Encode data to hex.
+		for (size_t i = 0; i < bin_len; i++) {
+			txt[2 * i]     = hex[bin[i] / 16];
+			txt[2 * i + 1] = hex[bin[i] % 16];
+		}
+
+		*txt_len = 2 + 2 * bin_len;
+		txt[*txt_len] = '\0';
+	}
+
+	return KNOT_EOK;
+}
+
+int check_ref(
+	conf_args_t *args)
+{
+	const yp_item_t *parent = args->key1->var.r.ref;
+
+	// Try to find the id in the referenced category.
+	return conf_db_get(args->conf, args->txn, parent->name, NULL,
+	                   args->data, args->data_len, NULL);
+}
+
+int include_file(
+	conf_args_t *args)
+{
+	size_t max_path = 4096;
+	char *path = malloc(max_path);
+	if (path == NULL) {
+		return KNOT_ENOMEM;
+	}
+
+	// Prepare absolute include path.
+	int ret;
+	if (args->data[0] == '/') {
+		ret = snprintf(path, max_path, "%.*s",
+		               (int)args->data_len, args->data);
+	} else {
+		char *full_current_name = realpath((args->file_name != NULL) ?
+		                                   args->file_name : "./", NULL);
+		if (full_current_name == NULL) {
+			return KNOT_ENOMEM;
+		}
+
+		ret = snprintf(path, max_path, "%s/%.*s",
+		               dirname(full_current_name),
+		               (int)args->data_len, args->data);
+		free(full_current_name);
+	}
+	if (ret <= 0 || ret >= max_path) {
+		free(path);
+		return ret;
+	}
+	size_t path_len = ret;
+
+	// Get file status.
+	struct stat file_stat;
+	if (stat(path, &file_stat) != 0) {
+		free(path);
+		return KNOT_EINVAL;
+	}
+
+	// Process regular file.
+	if (S_ISREG(file_stat.st_mode)) {
+		ret = conf_parse(args->conf, args->txn, path, true,
+		                 args->incl_depth);
+		free(path);
+		return ret;
+	} else if (!S_ISDIR(file_stat.st_mode)) {
+		free(path);
+		return KNOT_EINVAL;
+	}
+
+	// Process directory.
+	DIR *dir = opendir(path);
+	if (dir == NULL) {
+		free(path);
+		return KNOT_EINVAL;
+	}
+
+	// Prepare own dirent structure (see NOTES in man readdir_r).
+	size_t len = offsetof(struct dirent, d_name) +
+	             fpathconf(dirfd(dir), _PC_NAME_MAX) + 1;
+	struct dirent *entry = malloc(len);
+	if (entry == NULL) {
+		return KNOT_ENOMEM;
+	}
+	memset(entry, 0, len);
+
+	struct dirent *result = NULL;
+	while ((ret = readdir_r(dir, entry, &result)) == 0 && result != NULL) {
+		// Skip names with leading dot.
+		if (entry->d_name[0] == '.') {
+			continue;
+		}
+
+		// Prepare included file absolute path.
+		ret = snprintf(path + path_len, max_path - path_len, "/%s",
+		               entry->d_name);
+		if (ret <= 0 || ret >= max_path - path_len) {
+			free(entry);
+			free(path);
+			return KNOT_EINVAL;
+		}
+
+		// Ignore directories inside the current directory.
+		if (stat(path, &file_stat) == 0 && !S_ISREG(file_stat.st_mode)) {
+			continue;
+		}
+
+		ret = conf_parse(args->conf, args->txn, path, true,
+		                      args->incl_depth);
+		if (ret != KNOT_EOK) {
+			break;
+		}
+	}
+
+	free(entry);
+	closedir(dir);
+	free(path);
+
+	return ret;
+}
diff --git a/src/knot/conf/tools.h b/src/knot/conf/tools.h
new file mode 100644
index 0000000000..f106719c2d
--- /dev/null
+++ b/src/knot/conf/tools.h
@@ -0,0 +1,69 @@
+/*  Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+/*!
+ * \file
+ *
+ * Configuration scheme callbacks.
+ *
+ * \addtogroup config
+ *
+ * @{
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "knot/conf/conf.h"
+#include "libknot/yparser/ypscheme.h"
+
+typedef struct {
+	conf_t *conf;
+	namedb_txn_t *txn;
+	const char *file_name; // Current config file.
+	size_t *incl_depth; // Current include depth.
+	const yp_item_t *key0;
+	const yp_item_t *key1;
+	const uint8_t *id;
+	size_t id_len;
+	const uint8_t *data;
+	size_t data_len;
+} conf_args_t;
+
+typedef int conf_call_f(conf_args_t *);
+
+int hex_text_to_bin(
+	char const *txt,
+	size_t txt_len,
+	uint8_t *bin,
+	size_t *bin_len
+);
+
+int hex_text_to_txt(
+	uint8_t const *bin,
+	size_t bin_len,
+	char *txt,
+	size_t *txt_len
+);
+
+int check_ref(
+	conf_args_t *args
+);
+
+int include_file(
+	conf_args_t *args
+);
diff --git a/src/libknot/errcode.c b/src/libknot/errcode.c
index 3a58143816..df9f908f9f 100644
--- a/src/libknot/errcode.c
+++ b/src/libknot/errcode.c
@@ -130,6 +130,11 @@ static const struct error errors[] = {
 	{ KNOT_YP_ENODATA,      "missing value" },
 	{ KNOT_YP_ENOID,        "missing identifier" },
 
+	/* Configuration errors. */
+	{ KNOT_CONF_EMPTY,     "empty configuration database" },
+	{ KNOT_CONF_EVERSION,  "invalid configuration database version" },
+	{ KNOT_CONF_EREDEFINE, "identifier already specified" },
+
 	/* Processing errors. */
 	{ KNOT_LAYER_ERROR, "processing layer error" },
 
diff --git a/src/libknot/internal/errcode.h b/src/libknot/internal/errcode.h
index bdcafcc67b..90a411eb8a 100644
--- a/src/libknot/internal/errcode.h
+++ b/src/libknot/internal/errcode.h
@@ -131,6 +131,11 @@ enum knot_error {
 	KNOT_YP_ENODATA,
 	KNOT_YP_ENOID,
 
+	/* Configuration errors. */
+	KNOT_CONF_EMPTY,
+	KNOT_CONF_EVERSION,
+	KNOT_CONF_EREDEFINE,
+
 	/* Processing error. */
 	KNOT_LAYER_ERROR,
 
-- 
GitLab