summaryrefslogtreecommitdiffstats
path: root/src/knot/conf
diff options
context:
space:
mode:
Diffstat (limited to 'src/knot/conf')
-rw-r--r--src/knot/conf/base.c1056
-rw-r--r--src/knot/conf/base.h322
-rw-r--r--src/knot/conf/conf.c1469
-rw-r--r--src/knot/conf/conf.h939
-rw-r--r--src/knot/conf/confdb.c951
-rw-r--r--src/knot/conf/confdb.h230
-rw-r--r--src/knot/conf/confio.c1612
-rw-r--r--src/knot/conf/confio.h231
-rw-r--r--src/knot/conf/migration.c81
-rw-r--r--src/knot/conf/migration.h30
-rw-r--r--src/knot/conf/module.c509
-rw-r--r--src/knot/conf/module.h126
-rw-r--r--src/knot/conf/schema.c530
-rw-r--r--src/knot/conf/schema.h279
-rw-r--r--src/knot/conf/tools.c1069
-rw-r--r--src/knot/conf/tools.h147
16 files changed, 9581 insertions, 0 deletions
diff --git a/src/knot/conf/base.c b/src/knot/conf/base.c
new file mode 100644
index 0000000..1670929
--- /dev/null
+++ b/src/knot/conf/base.c
@@ -0,0 +1,1056 @@
+/* Copyright (C) 2022 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <urcu.h>
+
+#include "knot/conf/base.h"
+#include "knot/conf/confdb.h"
+#include "knot/conf/module.h"
+#include "knot/conf/tools.h"
+#include "knot/common/log.h"
+#include "knot/nameserver/query_module.h"
+#include "libknot/libknot.h"
+#include "libknot/yparser/ypformat.h"
+#include "libknot/yparser/yptrafo.h"
+#include "contrib/files.h"
+#include "contrib/sockaddr.h"
+#include "contrib/string.h"
+
+// The active configuration.
+conf_t *s_conf;
+
+conf_t* conf(void) {
+ return s_conf;
+}
+
+static int init_and_check(
+ conf_t *conf,
+ conf_flag_t flags)
+{
+ if (conf == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ knot_db_txn_t txn;
+ unsigned txn_flags = (flags & CONF_FREADONLY) ? KNOT_DB_RDONLY : 0;
+ int ret = conf->api->txn_begin(conf->db, &txn, txn_flags);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ // Initialize the database.
+ if (!(flags & CONF_FREADONLY)) {
+ ret = conf_db_init(conf, &txn, false);
+ if (ret != KNOT_EOK) {
+ conf->api->txn_abort(&txn);
+ return ret;
+ }
+ }
+
+ // Check the database.
+ if (!(flags & CONF_FNOCHECK)) {
+ ret = conf_db_check(conf, &txn);
+ if (ret < KNOT_EOK) {
+ conf->api->txn_abort(&txn);
+ return ret;
+ }
+ }
+
+ if (flags & CONF_FREADONLY) {
+ conf->api->txn_abort(&txn);
+ return KNOT_EOK;
+ } else {
+ return conf->api->txn_commit(&txn);
+ }
+}
+
+int conf_refresh_txn(
+ conf_t *conf)
+{
+ if (conf == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ // Close previously opened transaction.
+ conf->api->txn_abort(&conf->read_txn);
+
+ return conf->api->txn_begin(conf->db, &conf->read_txn, KNOT_DB_RDONLY);
+}
+
+static void refresh_hostname(
+ conf_t *conf)
+{
+ if (conf == NULL) {
+ return;
+ }
+
+ free(conf->hostname);
+ conf->hostname = sockaddr_hostname();
+ if (conf->hostname == NULL) {
+ // Empty hostname fallback, NULL cannot be passed to strlen!
+ conf->hostname = strdup("");
+ }
+}
+
+static int infinite_adjust(
+ int timeout)
+{
+ return (timeout > 0) ? timeout : -1;
+}
+
+static void init_cache(
+ conf_t *conf,
+ bool reinit_cache)
+{
+ /*
+ * For UDP, TCP, XDP, and background workers, cache the number of running
+ * workers. Cache the setting of TCP reuseport too. These values
+ * can't change in runtime, while config data can.
+ */
+
+ static bool first_init = true;
+ static bool running_tcp_reuseport;
+ static bool running_socket_affinity;
+ static bool running_xdp_udp;
+ static bool running_xdp_tcp;
+ static uint16_t running_xdp_quic;
+ static bool running_route_check;
+ static size_t running_udp_threads;
+ static size_t running_tcp_threads;
+ static size_t running_xdp_threads;
+ static size_t running_bg_threads;
+ static size_t running_quic_clients;
+ static size_t running_quic_outbufs;
+ static size_t running_quic_idle;
+
+ if (first_init || reinit_cache) {
+ running_tcp_reuseport = conf_get_bool(conf, C_SRV, C_TCP_REUSEPORT);
+ running_socket_affinity = conf_get_bool(conf, C_SRV, C_SOCKET_AFFINITY);
+ running_xdp_udp = conf_get_bool(conf, C_XDP, C_UDP);
+ running_xdp_tcp = conf_get_bool(conf, C_XDP, C_TCP);
+ running_xdp_quic = 0;
+ if (conf_get_bool(conf, C_XDP, C_QUIC)) {
+ running_xdp_quic = conf_get_int(conf, C_XDP, C_QUIC_PORT);
+ }
+ running_route_check = conf_get_bool(conf, C_XDP, C_ROUTE_CHECK);
+ running_udp_threads = conf_udp_threads(conf);
+ running_tcp_threads = conf_tcp_threads(conf);
+ running_xdp_threads = conf_xdp_threads(conf);
+ running_bg_threads = conf_bg_threads(conf);
+ running_quic_clients = conf_get_int(conf, C_SRV, C_QUIC_MAX_CLIENTS);
+ running_quic_outbufs = conf_get_int(conf, C_SRV, C_QUIC_OUTBUF_MAX_SIZE);
+ running_quic_idle = conf_get_int(conf, C_SRV, C_QUIC_IDLE_CLOSE);
+
+ first_init = false;
+ }
+
+ conf_val_t val = conf_get(conf, C_SRV, C_UDP_MAX_PAYLOAD_IPV4);
+ if (val.code != KNOT_EOK) {
+ val = conf_get(conf, C_SRV, C_UDP_MAX_PAYLOAD);
+ }
+ conf->cache.srv_udp_max_payload_ipv4 = conf_int(&val);
+
+ val = conf_get(conf, C_SRV, C_UDP_MAX_PAYLOAD_IPV6);
+ if (val.code != KNOT_EOK) {
+ val = conf_get(conf, C_SRV, C_UDP_MAX_PAYLOAD);
+ }
+ conf->cache.srv_udp_max_payload_ipv6 = conf_int(&val);
+
+ val = conf_get(conf, C_SRV, C_TCP_IDLE_TIMEOUT);
+ conf->cache.srv_tcp_idle_timeout = conf_int(&val);
+
+ val = conf_get(conf, C_SRV, C_TCP_IO_TIMEOUT);
+ conf->cache.srv_tcp_io_timeout = infinite_adjust(conf_int(&val));
+
+ val = conf_get(conf, C_SRV, C_TCP_RMT_IO_TIMEOUT);
+ conf->cache.srv_tcp_remote_io_timeout = infinite_adjust(conf_int(&val));
+
+ val = conf_get(conf, C_SRV, C_TCP_FASTOPEN);
+ conf->cache.srv_tcp_fastopen = conf_bool(&val);
+
+ conf->cache.srv_quic_max_clients = running_quic_clients;
+
+ conf->cache.srv_quic_idle_close = running_quic_idle;
+
+ conf->cache.srv_quic_obuf_max_size = running_quic_outbufs;
+
+ conf->cache.srv_tcp_reuseport = running_tcp_reuseport;
+
+ conf->cache.srv_socket_affinity = running_socket_affinity;
+
+ val = conf_get(conf, C_SRV, C_DBUS_EVENT);
+ while (val.code == KNOT_EOK) {
+ conf->cache.srv_dbus_event |= conf_opt(&val);
+ conf_val_next(&val);
+ }
+
+ conf->cache.srv_udp_threads = running_udp_threads;
+
+ conf->cache.srv_tcp_threads = running_tcp_threads;
+
+ conf->cache.srv_xdp_threads = running_xdp_threads;
+
+ conf->cache.srv_bg_threads = running_bg_threads;
+
+ conf->cache.srv_tcp_max_clients = conf_tcp_max_clients(conf);
+
+ val = conf_get(conf, C_XDP, C_TCP_MAX_CLIENTS);
+ conf->cache.xdp_tcp_max_clients = conf_int(&val);
+
+ val = conf_get(conf, C_XDP, C_TCP_INBUF_MAX_SIZE);
+ conf->cache.xdp_tcp_inbuf_max_size = conf_int(&val);
+
+ val = conf_get(conf, C_XDP, C_TCP_OUTBUF_MAX_SIZE);
+ conf->cache.xdp_tcp_outbuf_max_size = conf_int(&val);
+
+ val = conf_get(conf, C_XDP, C_TCP_IDLE_CLOSE);
+ conf->cache.xdp_tcp_idle_close = conf_int(&val);
+
+ val = conf_get(conf, C_XDP, C_TCP_IDLE_RESET);
+ conf->cache.xdp_tcp_idle_reset = conf_int(&val);
+
+ val = conf_get(conf, C_XDP, C_TCP_RESEND);
+ conf->cache.xdp_tcp_idle_resend = conf_int(&val);
+
+ conf->cache.xdp_udp = running_xdp_udp;
+
+ conf->cache.xdp_tcp = running_xdp_tcp;
+
+ conf->cache.xdp_quic = running_xdp_quic;
+
+ conf->cache.xdp_route_check = running_route_check;
+
+ val = conf_get(conf, C_CTL, C_TIMEOUT);
+ conf->cache.ctl_timeout = conf_int(&val) * 1000;
+ /* infinite_adjust() call isn't needed, 0 is adjusted later anyway. */
+
+ val = conf_get(conf, C_SRV, C_NSID);
+ if (val.code != KNOT_EOK) {
+ if (conf->hostname == NULL) {
+ conf->cache.srv_nsid_data = (const uint8_t *)"";
+ conf->cache.srv_nsid_len = 0;
+ } else {
+ conf->cache.srv_nsid_data = (const uint8_t *)conf->hostname;
+ conf->cache.srv_nsid_len = strlen(conf->hostname);
+ }
+ } else {
+ conf->cache.srv_nsid_data = conf_bin(&val, &conf->cache.srv_nsid_len);
+ }
+
+ val = conf_get(conf, C_SRV, C_ECS);
+ conf->cache.srv_ecs = conf_bool(&val);
+
+ val = conf_get(conf, C_SRV, C_ANS_ROTATION);
+ conf->cache.srv_ans_rotate = conf_bool(&val);
+
+ val = conf_get(conf, C_SRV, C_AUTO_ACL);
+ conf->cache.srv_auto_acl = conf_bool(&val);
+
+ val = conf_get(conf, C_SRV, C_PROXY_ALLOWLIST);
+ conf->cache.srv_proxy_enabled = (conf_val_count(&val) > 0);
+}
+
+int conf_new(
+ conf_t **conf,
+ const yp_item_t *schema,
+ const char *db_dir,
+ size_t max_conf_size,
+ conf_flag_t flags)
+{
+ 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 schema.
+ int ret = yp_schema_copy(&out->schema, schema);
+ if (ret != KNOT_EOK) {
+ goto new_error;
+ }
+
+ // Initialize query modules list.
+ out->query_modules = malloc(sizeof(list_t));
+ if (out->query_modules == NULL) {
+ ret = KNOT_ENOMEM;
+ goto new_error;
+ }
+ init_list(out->query_modules);
+
+ // Set the DB api.
+ out->mapsize = max_conf_size;
+ out->api = knot_db_lmdb_api();
+ struct knot_db_lmdb_opts lmdb_opts = KNOT_DB_LMDB_OPTS_INITIALIZER;
+ lmdb_opts.mapsize = out->mapsize;
+ lmdb_opts.maxreaders = CONF_MAX_DB_READERS;
+ lmdb_opts.flags.env = KNOT_DB_LMDB_NOTLS;
+
+ // Open the database.
+ if (db_dir == NULL) {
+ // Prepare a temporary database.
+ char tpl[] = "/tmp/knot-confdb.XXXXXX";
+ lmdb_opts.path = mkdtemp(tpl);
+ if (lmdb_opts.path == NULL) {
+ CONF_LOG(LOG_ERR, "failed to create temporary directory (%s)",
+ knot_strerror(knot_map_errno()));
+ ret = KNOT_ENOMEM;
+ goto new_error;
+ }
+
+ ret = out->api->init(&out->db, NULL, &lmdb_opts);
+
+ // Remove the database to ensure it is temporary.
+ if (!remove_path(lmdb_opts.path)) {
+ CONF_LOG(LOG_WARNING, "failed to purge temporary directory '%s'",
+ lmdb_opts.path);
+ }
+ } else {
+ // Set the specified database.
+ lmdb_opts.path = db_dir;
+
+ // Set the read-only mode.
+ if (flags & CONF_FREADONLY) {
+ lmdb_opts.flags.env |= KNOT_DB_LMDB_RDONLY;
+ }
+
+ ret = out->api->init(&out->db, NULL, &lmdb_opts);
+ }
+ if (ret != KNOT_EOK) {
+ goto new_error;
+ }
+
+ // Initialize and check the database.
+ ret = init_and_check(out, flags);
+ if (ret != KNOT_EOK) {
+ goto new_error;
+ }
+
+ // Open common read-only transaction.
+ ret = conf_refresh_txn(out);
+ if (ret != KNOT_EOK) {
+ goto new_error;
+ }
+
+ // Cache the current hostname.
+ if (!(flags & CONF_FNOHOSTNAME)) {
+ refresh_hostname(out);
+ }
+
+ // Initialize cached values.
+ init_cache(out, false);
+
+ // Load module schemas.
+ if (flags & (CONF_FREQMODULES | CONF_FOPTMODULES)) {
+ ret = conf_mod_load_common(out);
+ if (ret != KNOT_EOK && (flags & CONF_FREQMODULES)) {
+ goto new_error;
+ }
+
+ for (conf_iter_t iter = conf_iter(out, C_MODULE);
+ iter.code == KNOT_EOK; conf_iter_next(out, &iter)) {
+ conf_val_t id = conf_iter_id(out, &iter);
+ conf_val_t file = conf_id_get(out, C_MODULE, C_FILE, &id);
+ ret = conf_mod_load_extra(out, conf_str(&id), conf_str(&file),
+ MOD_EXPLICIT);
+ if (ret != KNOT_EOK && (flags & CONF_FREQMODULES)) {
+ conf_iter_finish(out, &iter);
+ goto new_error;
+ }
+ }
+
+ conf_mod_load_purge(out, false);
+ }
+
+ *conf = out;
+
+ return KNOT_EOK;
+new_error:
+ conf_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 schema.
+ int ret = yp_schema_copy(&out->schema, s_conf->schema);
+ if (ret != KNOT_EOK) {
+ free(out);
+ return ret;
+ }
+
+ // Set shared items.
+ out->api = s_conf->api;
+ out->db = s_conf->db;
+
+ // Initialize query modules list.
+ out->query_modules = malloc(sizeof(list_t));
+ if (out->query_modules == NULL) {
+ yp_schema_free(out->schema);
+ free(out);
+ return KNOT_ENOMEM;
+ }
+ init_list(out->query_modules);
+
+ // Open common read-only transaction.
+ ret = conf_refresh_txn(out);
+ if (ret != KNOT_EOK) {
+ free(out->query_modules);
+ yp_schema_free(out->schema);
+ free(out);
+ return ret;
+ }
+
+ // Copy the filename.
+ if (s_conf->filename != NULL) {
+ out->filename = strdup(s_conf->filename);
+ }
+
+ // Copy the hostname.
+ if (s_conf->hostname != NULL) {
+ out->hostname = strdup(s_conf->hostname);
+ }
+
+ out->catalog = s_conf->catalog;
+
+ // Initialize cached values.
+ init_cache(out, false);
+
+ out->is_clone = true;
+
+ *conf = out;
+
+ return KNOT_EOK;
+}
+
+conf_t *conf_update(
+ conf_t *conf,
+ conf_update_flag_t flags)
+{
+ // Remove the clone flag for new master configuration.
+ if (conf != NULL) {
+ conf->is_clone = false;
+
+ if ((flags & CONF_UPD_FCONFIO) && s_conf != NULL) {
+ conf->io.flags = s_conf->io.flags;
+ conf->io.zones = s_conf->io.zones;
+ }
+ if ((flags & CONF_UPD_FMODULES) && s_conf != NULL) {
+ free(conf->query_modules);
+ conf->query_modules = s_conf->query_modules;
+ conf->query_plan = s_conf->query_plan;
+ }
+ }
+
+ conf_t **current_conf = &s_conf;
+ conf_t *old_conf = rcu_xchg_pointer(current_conf, conf);
+
+ synchronize_rcu();
+
+ if (old_conf != NULL) {
+ // Remove the clone flag if a single configuration.
+ old_conf->is_clone = (conf != NULL) ? true : false;
+
+ if (flags & CONF_UPD_FCONFIO) {
+ old_conf->io.zones = NULL;
+ }
+ if (flags & CONF_UPD_FMODULES) {
+ old_conf->query_modules = NULL;
+ old_conf->query_plan = NULL;
+ }
+ if (!(flags & CONF_UPD_FNOFREE)) {
+ conf_free(old_conf);
+ old_conf = NULL;
+ }
+ }
+
+ return old_conf;
+}
+
+void conf_free(
+ conf_t *conf)
+{
+ if (conf == NULL) {
+ return;
+ }
+
+ yp_schema_free(conf->schema);
+ free(conf->filename);
+ free(conf->hostname);
+ if (conf->api != NULL) {
+ conf->api->txn_abort(&conf->read_txn);
+ }
+
+ if (conf->io.txn != NULL && conf->api != NULL) {
+ conf->api->txn_abort(conf->io.txn_stack);
+ }
+ if (conf->io.zones != NULL) {
+ trie_free(conf->io.zones);
+ }
+
+ conf_mod_load_purge(conf, false);
+ conf_deactivate_modules(conf->query_modules, &conf->query_plan);
+ free(conf->query_modules);
+ conf_mod_unload_shared(conf);
+
+ if (!conf->is_clone) {
+ if (conf->api != NULL) {
+ conf->api->deinit(conf->db);
+ }
+ }
+
+ free(conf);
+}
+
+#define CONF_LOG_LINE(file, line, msg, ...) do { \
+ CONF_LOG(LOG_ERR, "%s%s%sline %zu" msg, \
+ (file != NULL ? "file '" : ""), (file != NULL ? file : ""), \
+ (file != NULL ? "', " : ""), line, ##__VA_ARGS__); \
+ } while (0)
+
+static void log_parser_err(
+ yp_parser_t *parser,
+ int ret)
+{
+ if (parser->event == YP_ENULL) {
+ CONF_LOG_LINE(parser->file.name, parser->line_count,
+ " (%s)", knot_strerror(ret));
+ } else {
+ CONF_LOG_LINE(parser->file.name, parser->line_count,
+ ", item '%s'%s%.*s%s (%s)", parser->key,
+ (parser->data_len > 0) ? ", value '" : "",
+ (int)parser->data_len,
+ (parser->data_len > 0) ? parser->data : "",
+ (parser->data_len > 0) ? "'" : "",
+ knot_strerror(ret));
+ }
+}
+
+static void log_parser_schema_err(
+ yp_parser_t *parser,
+ int ret)
+{
+ // Emit better message for 'unknown module' error.
+ if (ret == KNOT_YP_EINVAL_ITEM && parser->event == YP_EKEY0 &&
+ strncmp(parser->key, KNOTD_MOD_NAME_PREFIX, strlen(KNOTD_MOD_NAME_PREFIX)) == 0) {
+ CONF_LOG_LINE(parser->file.name, parser->line_count,
+ ", unknown module '%s'", parser->key);
+ } else {
+ log_parser_err(parser, ret);
+ }
+}
+
+static void log_call_err(
+ yp_parser_t *parser,
+ knotd_conf_check_args_t *args,
+ int ret)
+{
+ CONF_LOG_LINE(args->extra->file_name, args->extra->line,
+ ", item '%s'%s%s%s (%s)", args->item->name + 1,
+ (parser->data_len > 0) ? ", value '" : "",
+ (parser->data_len > 0) ? parser->data : "",
+ (parser->data_len > 0) ? "'" : "",
+ (args->err_str != NULL) ? args->err_str : knot_strerror(ret));
+}
+
+static void log_prev_err(
+ knotd_conf_check_args_t *args,
+ int ret)
+{
+ char buff[512] = { 0 };
+ size_t len = sizeof(buff);
+
+ // Get the previous textual identifier.
+ if ((args->item->flags & YP_FMULTI) != 0) {
+ if (yp_item_to_txt(args->item->var.g.id, args->id, args->id_len,
+ buff, &len, YP_SNOQUOTE) != KNOT_EOK) {
+ buff[0] = '\0';
+ }
+ }
+
+ CONF_LOG_LINE(args->extra->file_name, args->extra->line - 1,
+ ", section '%s%s%s%s' (%s)", args->item->name + 1,
+ (buff[0] != '\0') ? "[" : "",
+ buff,
+ (buff[0] != '\0') ? "]" : "",
+ args->err_str != NULL ? args->err_str : knot_strerror(ret));
+}
+
+static int finalize_previous_section(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ yp_parser_t *parser,
+ yp_check_ctx_t *ctx)
+{
+ yp_node_t *node = &ctx->nodes[0];
+
+ // Return if no previous section or include or empty multi-section.
+ if (node->item == NULL || node->item->type != YP_TGRP ||
+ (node->id_len == 0 && (node->item->flags & YP_FMULTI) != 0)) {
+ return KNOT_EOK;
+ }
+
+ knotd_conf_check_extra_t extra = {
+ .conf = conf,
+ .txn = txn,
+ .file_name = parser->file.name,
+ .line = parser->line_count
+ };
+ knotd_conf_check_args_t args = {
+ .item = node->item,
+ .id = node->id,
+ .id_len = node->id_len,
+ .data = node->data,
+ .data_len = node->data_len,
+ .extra = &extra
+ };
+
+ int ret = conf_exec_callbacks(&args);
+ if (ret != KNOT_EOK) {
+ log_prev_err(&args, ret);
+ }
+
+ return ret;
+}
+
+static int finalize_item(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ yp_parser_t *parser,
+ yp_check_ctx_t *ctx)
+{
+ yp_node_t *node = &ctx->nodes[ctx->current];
+
+ // Section callbacks are executed before another section.
+ if (node->item->type == YP_TGRP && node->id_len == 0) {
+ return KNOT_EOK;
+ }
+
+ knotd_conf_check_extra_t extra = {
+ .conf = conf,
+ .txn = txn,
+ .file_name = parser->file.name,
+ .line = parser->line_count
+ };
+ knotd_conf_check_args_t args = {
+ .item = (parser->event == YP_EID) ? node->item->var.g.id : node->item,
+ .id = node->id,
+ .id_len = node->id_len,
+ .data = node->data,
+ .data_len = node->data_len,
+ .extra = &extra
+ };
+
+ int ret = conf_exec_callbacks(&args);
+ if (ret != KNOT_EOK) {
+ log_call_err(parser, &args, ret);
+ }
+
+ return ret;
+}
+
+int conf_parse(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const char *input,
+ bool is_file)
+{
+ if (conf == NULL || txn == NULL || input == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ yp_parser_t *parser = malloc(sizeof(yp_parser_t));
+ if (parser == NULL) {
+ return KNOT_ENOMEM;
+ }
+ yp_init(parser);
+
+ int ret;
+
+ // Set parser source.
+ if (is_file) {
+ ret = yp_set_input_file(parser, input);
+ } else {
+ ret = yp_set_input_string(parser, input, strlen(input));
+ }
+ if (ret != KNOT_EOK) {
+ CONF_LOG(LOG_ERR, "failed to load file '%s' (%s)",
+ input, knot_strerror(ret));
+ goto parse_error;
+ }
+
+ // Initialize parser check context.
+ yp_check_ctx_t *ctx = yp_schema_check_init(&conf->schema);
+ if (ctx == NULL) {
+ ret = KNOT_ENOMEM;
+ goto parse_error;
+ }
+
+ int check_ret = KNOT_EOK;
+
+ // Parse the configuration.
+ while ((ret = yp_parse(parser)) == KNOT_EOK) {
+ if (parser->event == YP_EKEY0 || parser->event == YP_EID) {
+ check_ret = finalize_previous_section(conf, txn, parser, ctx);
+ if (check_ret != KNOT_EOK) {
+ break;
+ }
+ }
+
+ check_ret = yp_schema_check_parser(ctx, parser);
+ if (check_ret != KNOT_EOK) {
+ log_parser_schema_err(parser, check_ret);
+ break;
+ }
+
+ yp_node_t *node = &ctx->nodes[ctx->current];
+ yp_node_t *parent = node->parent;
+
+ if (parent == NULL) {
+ check_ret = conf_db_set(conf, txn, node->item->name,
+ NULL, node->id, node->id_len,
+ node->data, node->data_len);
+ } else {
+ check_ret = conf_db_set(conf, txn, parent->item->name,
+ node->item->name, parent->id,
+ parent->id_len, node->data,
+ node->data_len);
+ }
+ if (check_ret != KNOT_EOK) {
+ log_parser_err(parser, check_ret);
+ break;
+ }
+
+ check_ret = finalize_item(conf, txn, parser, ctx);
+ if (check_ret != KNOT_EOK) {
+ break;
+ }
+ }
+
+ if (ret == KNOT_EOF) {
+ ret = finalize_previous_section(conf, txn, parser, ctx);
+ } else if (ret != KNOT_EOK) {
+ log_parser_err(parser, ret);
+ } else {
+ ret = check_ret;
+ }
+
+ yp_schema_check_deinit(ctx);
+parse_error:
+ yp_deinit(parser);
+ free(parser);
+
+ return ret;
+}
+
+int conf_import(
+ conf_t *conf,
+ const char *input,
+ bool is_file,
+ bool reinit_cache)
+{
+ if (conf == NULL || input == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ int ret;
+
+ knot_db_txn_t txn;
+ ret = conf->api->txn_begin(conf->db, &txn, 0);
+ if (ret != KNOT_EOK) {
+ goto import_error;
+ }
+
+ // Initialize the DB.
+ ret = conf_db_init(conf, &txn, true);
+ if (ret != KNOT_EOK) {
+ conf->api->txn_abort(&txn);
+ goto import_error;
+ }
+
+ // Parse and import given file.
+ ret = conf_parse(conf, &txn, input, is_file);
+ if (ret != KNOT_EOK) {
+ conf->api->txn_abort(&txn);
+ goto import_error;
+ }
+ // Load purge must be here as conf_parse may be called recursively!
+ conf_mod_load_purge(conf, false);
+
+ // Commit new configuration.
+ ret = conf->api->txn_commit(&txn);
+ if (ret != KNOT_EOK) {
+ goto import_error;
+ }
+
+ // Update read-only transaction.
+ ret = conf_refresh_txn(conf);
+ if (ret != KNOT_EOK) {
+ goto import_error;
+ }
+
+ // Update cached values.
+ init_cache(conf, reinit_cache);
+
+ // Reset the filename.
+ free(conf->filename);
+ conf->filename = NULL;
+ if (is_file) {
+ conf->filename = strdup(input);
+ }
+
+ ret = KNOT_EOK;
+import_error:
+
+ return ret;
+}
+
+static int export_group_name(
+ FILE *fp,
+ const yp_item_t *group,
+ char *out,
+ size_t out_len,
+ yp_style_t style)
+{
+ int ret = yp_format_key0(group, NULL, 0, out, out_len, style, true, true);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ fprintf(fp, "%s", out);
+
+ return KNOT_EOK;
+}
+
+static int export_group(
+ conf_t *conf,
+ FILE *fp,
+ const yp_item_t *group,
+ const uint8_t *id,
+ size_t id_len,
+ char *out,
+ size_t out_len,
+ yp_style_t style,
+ bool *exported)
+{
+ // Export the multi-group name.
+ if ((group->flags & YP_FMULTI) != 0 && !(*exported)) {
+ int ret = export_group_name(fp, group, out, out_len, style);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ *exported = true;
+ }
+
+ // Iterate through all possible group items.
+ for (yp_item_t *item = group->sub_items; item->name != NULL; item++) {
+ // Export the identifier.
+ if (group->var.g.id == item && (group->flags & YP_FMULTI) != 0) {
+ int ret = yp_format_id(group->var.g.id, id, id_len, out,
+ out_len, style);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ fprintf(fp, "%s", out);
+ continue;
+ }
+
+ conf_val_t bin;
+ 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;
+ }
+
+ // Export the single-group name if an item is set.
+ if ((group->flags & YP_FMULTI) == 0 && !(*exported)) {
+ int ret = export_group_name(fp, group, out, out_len, style);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ *exported = true;
+ }
+
+ // Format single/multiple-valued item.
+ size_t values = conf_val_count(&bin);
+ for (size_t i = 1; i <= values; i++) {
+ conf_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);
+ }
+ }
+ }
+
+ if (*exported) {
+ fprintf(fp, "\n");
+ }
+
+ return KNOT_EOK;
+}
+
+static int export_item(
+ conf_t *conf,
+ FILE *fp,
+ const yp_item_t *item,
+ char *buff,
+ size_t buff_len,
+ yp_style_t style)
+{
+ bool exported = false;
+
+ // Skip non-group items (include).
+ if (item->type != YP_TGRP) {
+ return KNOT_EOK;
+ }
+
+ // Export simple group without identifiers.
+ if (!(item->flags & YP_FMULTI)) {
+ return export_group(conf, fp, item, NULL, 0, buff, buff_len,
+ style, &exported);
+ }
+
+ // Iterate over all identifiers.
+ conf_iter_t iter;
+ int ret = conf_db_iter_begin(conf, &conf->read_txn, item->name, &iter);
+ switch (ret) {
+ case KNOT_EOK:
+ break;
+ case KNOT_ENOENT:
+ return KNOT_EOK;
+ default:
+ return ret;
+ }
+
+ while (ret == KNOT_EOK) {
+ const 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);
+ return ret;
+ }
+
+ // Export group with identifiers.
+ ret = export_group(conf, fp, item, id, id_len, buff, buff_len,
+ style, &exported);
+ if (ret != KNOT_EOK) {
+ conf_db_iter_finish(conf, &iter);
+ return ret;
+ }
+
+ ret = conf_db_iter_next(conf, &iter);
+ }
+ if (ret != KNOT_EOF) {
+ return ret;
+ }
+
+ return KNOT_EOK;
+}
+
+int conf_export(
+ conf_t *conf,
+ const char *file_name,
+ yp_style_t style)
+{
+ if (conf == 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 = (file_name != NULL) ? fopen(file_name, "w") : stdout;
+ if (fp == NULL) {
+ free(buff);
+ return knot_map_errno();
+ }
+
+ fprintf(fp, "# Configuration export (Knot DNS %s)\n\n", PACKAGE_VERSION);
+
+ const char *mod_prefix = KNOTD_MOD_NAME_PREFIX;
+ const size_t mod_prefix_len = strlen(mod_prefix);
+
+ int ret;
+
+ // Iterate over the schema.
+ for (yp_item_t *item = conf->schema; item->name != NULL; item++) {
+ // Don't export module sections again.
+ if (strncmp(item->name + 1, mod_prefix, mod_prefix_len) == 0) {
+ break;
+ }
+
+ // Export module sections before the template section.
+ if (strcmp(&item->name[1], &C_TPL[1]) == 0) {
+ for (yp_item_t *mod = item + 1; mod->name != NULL; mod++) {
+ // Skip non-module sections.
+ if (strncmp(mod->name + 1, mod_prefix, mod_prefix_len) != 0) {
+ continue;
+ }
+
+ // Export module section.
+ ret = export_item(conf, fp, mod, buff, buff_len, style);
+ if (ret != KNOT_EOK) {
+ goto export_error;
+ }
+ }
+ }
+
+ // Export non-module section.
+ ret = export_item(conf, fp, item, buff, buff_len, style);
+ if (ret != KNOT_EOK) {
+ goto export_error;
+ }
+ }
+
+ ret = KNOT_EOK;
+export_error:
+ if (file_name != NULL) {
+ fclose(fp);
+ }
+ free(buff);
+
+ return ret;
+}
diff --git a/src/knot/conf/base.h b/src/knot/conf/base.h
new file mode 100644
index 0000000..693ffd6
--- /dev/null
+++ b/src/knot/conf/base.h
@@ -0,0 +1,322 @@
+/* Copyright (C) 2022 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 <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "libknot/libknot.h"
+#include "libknot/yparser/ypschema.h"
+#include "contrib/qp-trie/trie.h"
+#include "contrib/ucw/lists.h"
+#include "libknot/dynarray.h"
+#include "knot/include/module.h"
+
+/*! Default template identifier. */
+#define CONF_DEFAULT_ID ((uint8_t *)"\x08""default\0")
+/*! Default configuration file. */
+#define CONF_DEFAULT_FILE (CONFIG_DIR "/knot.conf")
+/*! Default configuration database. */
+#define CONF_DEFAULT_DBDIR (STORAGE_DIR "/confdb")
+/*! Maximum depth of nested transactions. */
+#define CONF_MAX_TXN_DEPTH 5
+
+/*! Maximum number of UDP workers. */
+#define CONF_MAX_UDP_WORKERS 256
+/*! Maximum number of TCP workers. */
+#define CONF_MAX_TCP_WORKERS 256
+/*! Maximum number of background workers. */
+#define CONF_MAX_BG_WORKERS 512
+/*! Maximum number of concurrent DB readers. */
+#define CONF_MAX_DB_READERS (CONF_MAX_UDP_WORKERS + CONF_MAX_TCP_WORKERS + \
+ CONF_MAX_BG_WORKERS + 10 + 128 /* Utils, XDP workers */)
+
+/*! Configuration specific logging. */
+#define CONF_LOG(severity, msg, ...) do { \
+ log_fmt(severity, LOG_SOURCE_SERVER, "config, " msg, ##__VA_ARGS__); \
+ } while (0)
+
+#define CONF_LOG_ZONE(severity, zone, msg, ...) do { \
+ log_fmt_zone(severity, LOG_SOURCE_ZONE, zone, NULL, "config, " msg, ##__VA_ARGS__); \
+ } while (0)
+
+/*! Configuration getter output. */
+typedef struct {
+ /*! Item description. */
+ const yp_item_t *item;
+ /*! Whole data (can be array). */
+ const uint8_t *blob;
+ /*! Whole data length. */
+ size_t blob_len;
+ // Public items.
+ /*! Current single data. */
+ const uint8_t *data;
+ /*! Current single data length. */
+ size_t len;
+ /*! Value getter return code. */
+ int code;
+} conf_val_t;
+
+/*! Shared module types. */
+typedef enum {
+ /*! Static module. */
+ MOD_STATIC = 0,
+ /*! Implicit shared module which is always loaded. */
+ MOD_IMPLICIT,
+ /*! Explicit shared module which is currently loaded. */
+ MOD_EXPLICIT,
+ /*! Explicit shared temporary module which is loaded during config check. */
+ MOD_TEMPORARY
+} module_type_t;
+
+/*! Query module context. */
+typedef struct {
+ /*! Module interface. */
+ const knotd_mod_api_t *api;
+ /*! Shared library dlopen handler. */
+ void *lib_handle;
+ /*! Module type. */
+ module_type_t type;
+} module_t;
+
+knot_dynarray_declare(mod, module_t *, DYNARRAY_VISIBILITY_NORMAL, 16)
+knot_dynarray_declare(old_schema, yp_item_t *, DYNARRAY_VISIBILITY_NORMAL, 16)
+
+struct knot_catalog;
+
+/*! Configuration context. */
+typedef struct {
+ /*! Cloned configuration indicator. */
+ bool is_clone;
+ /*! Currently used namedb api. */
+ const struct knot_db_api *api;
+ /*! Configuration schema. */
+ yp_item_t *schema;
+ /*! Configuration database. */
+ knot_db_t *db;
+ /*! LMDB mapsize. */
+ size_t mapsize;
+
+ /*! Read-only transaction for config access. */
+ knot_db_txn_t read_txn;
+
+ struct {
+ /*! The current writing transaction. */
+ knot_db_txn_t *txn;
+ /*! Stack of nested writing transactions. */
+ knot_db_txn_t txn_stack[CONF_MAX_TXN_DEPTH];
+ /*! Master transaction flags. */
+ yp_flag_t flags;
+ /*! Changed zones. */
+ trie_t *zones;
+ } io;
+
+ /*! Current config file (for reload if started with config file). */
+ char *filename;
+
+ /*! Prearranged hostname string (for automatic NSID or CH ident value). */
+ char *hostname;
+
+ /*! Cached critical confdb items. */
+ struct {
+ uint16_t srv_udp_max_payload_ipv4;
+ uint16_t srv_udp_max_payload_ipv6;
+ int srv_tcp_idle_timeout;
+ int srv_tcp_io_timeout;
+ int srv_tcp_remote_io_timeout;
+ bool srv_tcp_reuseport;
+ bool srv_tcp_fastopen;
+ bool srv_socket_affinity;
+ unsigned srv_dbus_event;
+ size_t srv_udp_threads;
+ size_t srv_tcp_threads;
+ size_t srv_xdp_threads;
+ size_t srv_bg_threads;
+ size_t srv_tcp_max_clients;
+ size_t xdp_tcp_max_clients;
+ size_t xdp_tcp_inbuf_max_size;
+ size_t xdp_tcp_outbuf_max_size;
+ uint32_t xdp_tcp_idle_close;
+ uint32_t xdp_tcp_idle_reset;
+ uint32_t xdp_tcp_idle_resend;
+ size_t srv_quic_max_clients;
+ size_t srv_quic_obuf_max_size;
+ uint32_t srv_quic_idle_close;
+ bool xdp_udp;
+ bool xdp_tcp;
+ uint16_t xdp_quic;
+ bool xdp_route_check;
+ int ctl_timeout;
+ const uint8_t *srv_nsid_data;
+ size_t srv_nsid_len;
+ bool srv_ecs;
+ bool srv_ans_rotate;
+ bool srv_auto_acl;
+ bool srv_proxy_enabled;
+ } cache;
+
+ /*! List of dynamically loaded modules. */
+ mod_dynarray_t modules;
+ /*! List of old schemas (lazy freed). */
+ old_schema_dynarray_t old_schemas;
+ /*! List of active query modules. */
+ list_t *query_modules;
+ /*! Default query modules plan. */
+ struct query_plan *query_plan;
+ /*! Zone catalog database. */
+ struct catalog *catalog;
+} conf_t;
+
+/*!
+ * Configuration access flags.
+ */
+typedef enum {
+ CONF_FNONE = 0, /*!< Empty flag. */
+ CONF_FREADONLY = 1 << 0, /*!< Read only access. */
+ CONF_FNOCHECK = 1 << 1, /*!< Disabled confdb check. */
+ CONF_FNOHOSTNAME = 1 << 2, /*!< Don't set the hostname. */
+ CONF_FREQMODULES = 1 << 3, /*!< Load module schemas (must succeed). */
+ CONF_FOPTMODULES = 1 << 4, /*!< Load module schemas (may fail). */
+} conf_flag_t;
+
+/*!
+ * Configuration update flags.
+ */
+typedef enum {
+ CONF_UPD_FNONE = 0, /*!< Empty flag. */
+ CONF_UPD_FNOFREE = 1 << 0, /*!< Disable auto-free of previous config. */
+ CONF_UPD_FMODULES = 1 << 1, /*!< Reuse previous global modules. */
+ CONF_UPD_FCONFIO = 1 << 2, /*!< Reuse previous confio reload context. */
+} conf_update_flag_t;
+
+/*!
+ * Returns the active configuration.
+ */
+conf_t* conf(void);
+
+/*!
+ * Refreshes common read-only transaction.
+ *
+ * \param[in] conf Configuration.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_refresh_txn(
+ conf_t *conf
+);
+
+/*!
+ * Creates new or opens old configuration database.
+ *
+ * \param[out] conf Configuration.
+ * \param[in] schema Configuration schema.
+ * \param[in] db_dir Database path or NULL.
+ * \param[in] max_conf_size Maximum configuration DB size in bytes (LMDB mapsize).
+ * \param[in] flags Access flags.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_new(
+ conf_t **conf,
+ const yp_item_t *schema,
+ const char *db_dir,
+ size_t max_conf_size,
+ conf_flag_t flags
+);
+
+/*!
+ * Creates a partial copy of the active configuration.
+ *
+ * Shared objects: api, mm, db, filename.
+ *
+ * \param[out] conf Configuration.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_clone(
+ conf_t **conf
+);
+
+/*!
+ * Replaces the active configuration with the specified one.
+ *
+ * \param[in] conf New configuration.
+ * \param[in] flags Update flags.
+ *
+ * \return Previous config if CONF_UPD_FNOFREE, else NULL.
+ */
+conf_t *conf_update(
+ conf_t *conf,
+ conf_update_flag_t flags
+);
+
+/*!
+ * Removes the specified configuration.
+ *
+ * \param[in] conf Configuration.
+ */
+void conf_free(
+ conf_t *conf
+);
+
+/*!
+ * Parses textual configuration from the string or from the file.
+ *
+ * This function is not for direct using, just for includes processing!
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Transaction.
+ * \param[in] input Configuration string or filename.
+ * \param[in] is_file Specifies if the input is string or input filename.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_parse(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const char *input,
+ bool is_file
+);
+
+/*!
+ * Imports textual configuration.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] input Configuration string or input filename.
+ * \param[in] is_file Specifies if the input is string or filename.
+ * \param[in] reinit_cache Indication if cache reinitialization needed.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_import(
+ conf_t *conf,
+ const char *input,
+ bool is_file,
+ bool reinit_cache
+);
+
+/*!
+ * Exports configuration to textual file.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] file_name Output filename (stdout is used if NULL).
+ * \param[in] style Formatting style.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_export(
+ conf_t *conf,
+ const char *file_name,
+ yp_style_t style
+);
diff --git a/src/knot/conf/conf.c b/src/knot/conf/conf.c
new file mode 100644
index 0000000..016f01e
--- /dev/null
+++ b/src/knot/conf/conf.c
@@ -0,0 +1,1469 @@
+/* Copyright (C) 2022 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+
+#include "knot/conf/base.h"
+#include "knot/conf/confdb.h"
+#include "knot/catalog/catalog_db.h"
+#include "knot/common/log.h"
+#include "knot/server/dthreads.h"
+#include "libknot/libknot.h"
+#include "libknot/yparser/yptrafo.h"
+#include "libknot/xdp.h"
+#include "contrib/files.h"
+#include "contrib/macros.h"
+#include "contrib/sockaddr.h"
+#include "contrib/strtonum.h"
+#include "contrib/string.h"
+#include "contrib/wire_ctx.h"
+#include "contrib/openbsd/strlcat.h"
+#include "contrib/openbsd/strlcpy.h"
+
+#define DBG_LOG(err) CONF_LOG(LOG_DEBUG, "%s (%s)", __func__, knot_strerror((err)));
+
+#define DFLT_MIN_TCP_WORKERS 10
+#define DFLT_MAX_BG_WORKERS 10
+#define FALLBACK_MAX_TCP_CLIENTS 100
+
+bool conf_db_exists(
+ const char *db_dir)
+{
+ if (db_dir == NULL) {
+ return false;
+ }
+
+ struct stat st;
+ char data_mdb[strlen(db_dir) + 10];
+ (void)snprintf(data_mdb, sizeof(data_mdb), "%s/data.mdb", db_dir);
+ return (stat(data_mdb, &st) == 0 && st.st_size > 0);
+}
+
+conf_val_t conf_get_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key0_name,
+ const yp_name_t *key1_name)
+{
+ conf_val_t val = { NULL };
+
+ if (key0_name == NULL || key1_name == NULL) {
+ val.code = KNOT_EINVAL;
+ DBG_LOG(val.code);
+ return val;
+ }
+
+ conf_db_get(conf, txn, key0_name, key1_name, NULL, 0, &val);
+ switch (val.code) {
+ default:
+ CONF_LOG(LOG_ERR, "failed to read '%s/%s' (%s)",
+ key0_name + 1, key1_name + 1, knot_strerror(val.code));
+ // FALLTHROUGH
+ case KNOT_EOK:
+ case KNOT_ENOENT:
+ return val;
+ }
+}
+
+conf_val_t conf_rawid_get_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ 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 };
+
+ if (key0_name == NULL || key1_name == NULL || id == NULL) {
+ val.code = KNOT_EINVAL;
+ DBG_LOG(val.code);
+ return val;
+ }
+
+ conf_db_get(conf, txn, key0_name, key1_name, id, id_len, &val);
+ switch (val.code) {
+ default:
+ CONF_LOG(LOG_ERR, "failed to read '%s/%s' with identifier (%s)",
+ key0_name + 1, key1_name + 1, knot_strerror(val.code));
+ // FALLTHROUGH
+ case KNOT_EOK:
+ case KNOT_ENOENT:
+ case KNOT_YP_EINVAL_ID:
+ return val;
+ }
+}
+
+conf_val_t conf_id_get_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key0_name,
+ const yp_name_t *key1_name,
+ conf_val_t *id)
+{
+ conf_val_t val = { NULL };
+
+ if (key0_name == NULL || key1_name == NULL || id == NULL ||
+ id->code != KNOT_EOK) {
+ val.code = KNOT_EINVAL;
+ DBG_LOG(val.code);
+ return val;
+ }
+
+ conf_val(id);
+
+ conf_db_get(conf, txn, key0_name, key1_name, id->data, id->len, &val);
+ switch (val.code) {
+ default:
+ CONF_LOG(LOG_ERR, "failed to read '%s/%s' with identifier (%s)",
+ key0_name + 1, key1_name + 1, knot_strerror(val.code));
+ // FALLTHROUGH
+ case KNOT_EOK:
+ case KNOT_ENOENT:
+ case KNOT_YP_EINVAL_ID:
+ return val;
+ }
+}
+
+conf_val_t conf_mod_get_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key1_name,
+ const conf_mod_id_t *mod_id)
+{
+ conf_val_t val = { NULL };
+
+ if (key1_name == NULL || mod_id == NULL) {
+ val.code = KNOT_EINVAL;
+ DBG_LOG(val.code);
+ return val;
+ }
+
+ conf_db_get(conf, txn, mod_id->name, key1_name, mod_id->data, mod_id->len,
+ &val);
+ switch (val.code) {
+ default:
+ CONF_LOG(LOG_ERR, "failed to read '%s/%s' (%s)",
+ mod_id->name + 1, key1_name + 1, knot_strerror(val.code));
+ // FALLTHROUGH
+ case KNOT_EOK:
+ case KNOT_ENOENT:
+ case KNOT_YP_EINVAL_ID:
+ return val;
+ }
+}
+
+conf_val_t conf_zone_get_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key1_name,
+ const knot_dname_t *dname)
+{
+ conf_val_t val = { NULL };
+
+ if (key1_name == NULL || dname == NULL) {
+ val.code = KNOT_EINVAL;
+ DBG_LOG(val.code);
+ return val;
+ }
+
+ size_t dname_size = knot_dname_size(dname);
+
+ // Try to get explicit value.
+ conf_db_get(conf, txn, C_ZONE, key1_name, dname, dname_size, &val);
+ switch (val.code) {
+ case KNOT_EOK:
+ return val;
+ default:
+ CONF_LOG_ZONE(LOG_ERR, dname, "failed to read '%s/%s' (%s)",
+ &C_ZONE[1], &key1_name[1], knot_strerror(val.code));
+ // FALLTHROUGH
+ case KNOT_YP_EINVAL_ID:
+ case KNOT_ENOENT:
+ break;
+ }
+
+ // Check if a template is available.
+ conf_db_get(conf, txn, C_ZONE, C_TPL, dname, dname_size, &val);
+ switch (val.code) {
+ case KNOT_EOK:
+ // Use the specified template.
+ conf_val(&val);
+ conf_db_get(conf, txn, C_TPL, key1_name, val.data, val.len, &val);
+ goto got_template;
+ default:
+ CONF_LOG_ZONE(LOG_ERR, dname, "failed to read '%s/%s' (%s)",
+ &C_ZONE[1], &C_TPL[1], knot_strerror(val.code));
+ // FALLTHROUGH
+ case KNOT_ENOENT:
+ case KNOT_YP_EINVAL_ID:
+ break;
+ }
+
+ // Check if this is a catalog member zone.
+ if (conf->catalog != NULL) {
+ void *tofree = NULL;
+ const knot_dname_t *catalog;
+ const char *group;
+ int ret = catalog_get_catz(conf->catalog, dname, &catalog, &group, &tofree);
+ if (ret == KNOT_EOK) {
+ val = conf_zone_get_txn(conf, txn, C_CATALOG_TPL, catalog);
+ if (val.code == KNOT_EOK) {
+ conf_val(&val);
+ while (val.code == KNOT_EOK) {
+ if (strmemcmp(group, val.data, val.len) == 0) {
+ break;
+ }
+ conf_val_next(&val);
+ }
+ conf_val(&val); // Use first value if no match.
+ free(tofree);
+
+ conf_db_get(conf, txn, C_TPL, key1_name, val.data,
+ val.len, &val);
+ goto got_template;
+ } else {
+ CONF_LOG_ZONE(LOG_ERR, catalog,
+ "orphaned catalog database record (%s)",
+ knot_strerror(val.code));
+ free(tofree);
+ }
+ }
+ }
+
+ // Use the default template.
+ conf_db_get(conf, txn, C_TPL, key1_name, CONF_DEFAULT_ID + 1,
+ CONF_DEFAULT_ID[0], &val);
+
+got_template:
+ switch (val.code) {
+ default:
+ CONF_LOG_ZONE(LOG_ERR, dname, "failed to read '%s/%s' (%s)",
+ &C_TPL[1], &key1_name[1], knot_strerror(val.code));
+ // FALLTHROUGH
+ case KNOT_EOK:
+ case KNOT_ENOENT:
+ case KNOT_YP_EINVAL_ID:
+ break;
+ }
+
+ return val;
+}
+
+conf_val_t conf_default_get_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key1_name)
+{
+ conf_val_t val = { NULL };
+
+ if (key1_name == NULL) {
+ val.code = KNOT_EINVAL;
+ DBG_LOG(val.code);
+ return val;
+ }
+
+ conf_db_get(conf, txn, C_TPL, key1_name, CONF_DEFAULT_ID + 1,
+ CONF_DEFAULT_ID[0], &val);
+ switch (val.code) {
+ default:
+ CONF_LOG(LOG_ERR, "failed to read default '%s/%s' (%s)",
+ &C_TPL[1], &key1_name[1], knot_strerror(val.code));
+ // FALLTHROUGH
+ case KNOT_EOK:
+ case KNOT_ENOENT:
+ case KNOT_YP_EINVAL_ID:
+ break;
+ }
+
+ return val;
+}
+
+bool conf_rawid_exists_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key0_name,
+ const uint8_t *id,
+ size_t id_len)
+{
+ if (key0_name == NULL || id == NULL) {
+ DBG_LOG(KNOT_EINVAL);
+ return false;
+ }
+
+ int ret = conf_db_get(conf, txn, key0_name, NULL, id, id_len, NULL);
+ switch (ret) {
+ case KNOT_EOK:
+ return true;
+ default:
+ CONF_LOG(LOG_ERR, "failed to check '%s' for identifier (%s)",
+ key0_name + 1, knot_strerror(ret));
+ // FALLTHROUGH
+ case KNOT_ENOENT:
+ case KNOT_YP_EINVAL_ID:
+ return false;
+ }
+}
+
+bool conf_id_exists_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key0_name,
+ conf_val_t *id)
+{
+ if (key0_name == NULL || id == NULL || id->code != KNOT_EOK) {
+ DBG_LOG(KNOT_EINVAL);
+ return false;
+ }
+
+ conf_val(id);
+
+ int ret = conf_db_get(conf, txn, key0_name, NULL, id->data, id->len, NULL);
+ switch (ret) {
+ case KNOT_EOK:
+ return true;
+ default:
+ CONF_LOG(LOG_ERR, "failed to check '%s' for identifier (%s)",
+ key0_name + 1, knot_strerror(ret));
+ // FALLTHROUGH
+ case KNOT_ENOENT:
+ case KNOT_YP_EINVAL_ID:
+ return false;
+ }
+}
+
+size_t conf_id_count_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key0_name)
+{
+ size_t count = 0;
+
+ for (conf_iter_t iter = conf_iter_txn(conf, txn, key0_name);
+ iter.code == KNOT_EOK; conf_iter_next(conf, &iter)) {
+ count++;
+ }
+
+ return count;
+}
+
+conf_iter_t conf_iter_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key0_name)
+{
+ conf_iter_t iter = { NULL };
+
+ (void)conf_db_iter_begin(conf, txn, key0_name, &iter);
+ switch (iter.code) {
+ default:
+ CONF_LOG(LOG_ERR, "failed to iterate through '%s' (%s)",
+ key0_name + 1, knot_strerror(iter.code));
+ // FALLTHROUGH
+ case KNOT_EOK:
+ case KNOT_ENOENT:
+ return iter;
+ }
+}
+
+void conf_iter_next(
+ conf_t *conf,
+ conf_iter_t *iter)
+{
+ (void)conf_db_iter_next(conf, iter);
+ switch (iter->code) {
+ default:
+ CONF_LOG(LOG_ERR, "failed to read next item (%s)",
+ knot_strerror(iter->code));
+ // FALLTHROUGH
+ 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, &val.blob, &val.blob_len);
+ switch (val.code) {
+ default:
+ CONF_LOG(LOG_ERR, "failed to read identifier (%s)",
+ knot_strerror(val.code));
+ // FALLTHROUGH
+ 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_val(val);
+ while (val->code == KNOT_EOK) {
+ count++;
+ conf_val_next(val);
+ }
+ if (val->code != KNOT_EOF) {
+ return 0;
+ }
+
+ // Reset to the initial state.
+ conf_val(val);
+
+ return count;
+}
+
+void conf_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 and not at the end.
+ if (val->data != NULL && val->code != KNOT_EOF) {
+ return;
+ }
+ // Otherwise set to the first value.
+ conf_val_reset(val);
+ } 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_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_val(val);
+ return;
+ }
+
+ if (val->data + val->len < val->blob + val->blob_len) {
+ wire_ctx_t ctx = wire_ctx_init_const(val->blob, val->blob_len);
+ size_t offset = val->data + val->len - val->blob;
+ wire_ctx_skip(&ctx, offset);
+ uint16_t len = wire_ctx_read_u16(&ctx);
+ assert(ctx.error == KNOT_EOK);
+
+ val->data = ctx.position;
+ val->len = len;
+ val->code = KNOT_EOK;
+ } else {
+ val->data = NULL;
+ val->len = 0;
+ val->code = KNOT_EOF;
+ }
+}
+
+void conf_val_reset(conf_val_t *val)
+{
+ assert(val != NULL);
+ assert(val->code == KNOT_EOK || val->code == KNOT_EOF);
+ assert(val->item->flags & YP_FMULTI);
+
+ assert(val->blob != NULL);
+ wire_ctx_t ctx = wire_ctx_init_const(val->blob, val->blob_len);
+ uint16_t len = wire_ctx_read_u16(&ctx);
+ assert(ctx.error == KNOT_EOK);
+
+ val->data = ctx.position;
+ val->len = len;
+ val->code = KNOT_EOK;
+}
+
+bool conf_val_equal(
+ conf_val_t *val1,
+ conf_val_t *val2)
+{
+ if (val1->blob_len == val2->blob_len &&
+ memcmp(val1->blob, val2->blob, val1->blob_len) == 0) {
+ return true;
+ }
+
+ return false;
+}
+
+void conf_mix_iter_init(
+ conf_t *conf,
+ conf_val_t *mix_id,
+ conf_mix_iter_t *iter)
+{
+ assert(mix_id != NULL && mix_id->item != NULL);
+ assert(mix_id->item->type == YP_TREF &&
+ mix_id->item->var.r.ref != NULL &&
+ mix_id->item->var.r.grp_ref != NULL &&
+ mix_id->item->var.r.ref->var.g.id->type == YP_TSTR &&
+ mix_id->item->var.r.grp_ref->var.g.id->type == YP_TSTR);
+
+ iter->conf = conf;
+ iter->mix_id = mix_id;
+ iter->id = mix_id;
+ iter->nested = false;
+
+ if (mix_id->code != KNOT_EOK) {
+ return;
+ }
+
+ iter->sub_id = conf_id_get_txn(conf, &conf->read_txn,
+ mix_id->item->var.r.grp_ref_name,
+ mix_id->item->var.r.ref_name,
+ mix_id);
+ if (iter->sub_id.code == KNOT_EOK) {
+ conf_val(&iter->sub_id);
+ iter->id = &iter->sub_id;
+ iter->nested = true;
+ }
+}
+
+void conf_mix_iter_next(
+ conf_mix_iter_t *iter)
+{
+ conf_val_next(iter->id);
+ if (iter->nested) {
+ if (iter->id->code == KNOT_EOK) {
+ return;
+ }
+ assert(iter->id->code == KNOT_EOF);
+ conf_val_next(iter->mix_id);
+ if (iter->mix_id->code != KNOT_EOK) {
+ return;
+ }
+ } else if (iter->id->code != KNOT_EOK){
+ return;
+ }
+
+ iter->sub_id = conf_id_get_txn(iter->conf, &iter->conf->read_txn,
+ iter->mix_id->item->var.r.grp_ref_name,
+ iter->mix_id->item->var.r.ref_name,
+ iter->mix_id);
+ if (iter->sub_id.code == KNOT_EOK) {
+ conf_val(&iter->sub_id);
+ iter->id = &iter->sub_id;
+ iter->nested = true;
+ } else {
+ iter->id = iter->mix_id;
+ iter->nested = false;
+ }
+}
+
+int64_t conf_int(
+ conf_val_t *val)
+{
+ assert(val != NULL && val->item != 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_val(val);
+ return yp_int(val->data);
+ } else {
+ return val->item->var.i.dflt;
+ }
+}
+
+bool conf_bool(
+ conf_val_t *val)
+{
+ assert(val != NULL && val->item != 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_val(val);
+ return yp_bool(val->data);
+ } else {
+ return val->item->var.b.dflt;
+ }
+}
+
+unsigned conf_opt(
+ conf_val_t *val)
+{
+ assert(val != NULL && val->item != 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_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 && val->item != 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_val(val);
+ return yp_str(val->data);
+ } else {
+ return val->item->var.s.dflt;
+ }
+}
+
+const knot_dname_t* conf_dname(
+ conf_val_t *val)
+{
+ assert(val != NULL && val->item != 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_val(val);
+ return yp_dname(val->data);
+ } else {
+ return (const knot_dname_t *)val->item->var.d.dflt;
+ }
+}
+
+const uint8_t* conf_bin(
+ conf_val_t *val,
+ size_t *len)
+{
+ assert(val != NULL && val->item != NULL && len != NULL);
+ assert(val->item->type == YP_THEX || val->item->type == YP_TB64 ||
+ (val->item->type == YP_TREF &&
+ (val->item->var.r.ref->var.g.id->type == YP_THEX ||
+ val->item->var.r.ref->var.g.id->type == YP_TB64)));
+
+ if (val->code == KNOT_EOK) {
+ conf_val(val);
+ *len = yp_bin_len(val->data);
+ return yp_bin(val->data);
+ } else {
+ *len = val->item->var.d.dflt_len;
+ return val->item->var.d.dflt;
+ }
+}
+
+const uint8_t* conf_data(
+ conf_val_t *val,
+ size_t *len)
+{
+ assert(val != NULL && val->item != NULL);
+ assert(val->item->type == YP_TDATA ||
+ (val->item->type == YP_TREF &&
+ val->item->var.r.ref->var.g.id->type == YP_TDATA));
+
+ if (val->code == KNOT_EOK) {
+ conf_val(val);
+ *len = val->len;
+ return val->data;
+ } else {
+ *len = val->item->var.d.dflt_len;
+ return val->item->var.d.dflt;
+ }
+}
+
+struct sockaddr_storage conf_addr(
+ conf_val_t *val,
+ const char *sock_base_dir)
+{
+ assert(val != NULL && val->item != 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) {
+ bool no_port;
+ conf_val(val);
+ assert(val->data);
+ out = yp_addr(val->data, &no_port);
+
+ if (out.ss_family == AF_UNIX) {
+ // val->data[0] is socket type identifier!
+ if (val->data[1] != '/' && sock_base_dir != NULL) {
+ char *tmp = sprintf_alloc("%s/%s", sock_base_dir,
+ val->data + 1);
+ val->code = sockaddr_set(&out, AF_UNIX, tmp, 0);
+ free(tmp);
+ }
+ } else if (no_port) {
+ 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) {
+ val->code = sockaddr_set(&out, AF_UNIX,
+ dflt_socket, 0);
+ } else {
+ char *tmp = sprintf_alloc("%s/%s", sock_base_dir,
+ dflt_socket);
+ val->code = sockaddr_set(&out, AF_UNIX, tmp, 0);
+ free(tmp);
+ }
+ }
+ }
+
+ return out;
+}
+
+bool conf_addr_match(
+ conf_val_t *match,
+ const struct sockaddr_storage *addr)
+{
+ if (match == NULL || addr == NULL) {
+ return false;
+ }
+
+ while (match->code == KNOT_EOK) {
+ struct sockaddr_storage maddr = conf_addr(match, NULL);
+ if (sockaddr_cmp(&maddr, addr, true) == 0) {
+ return true;
+ }
+
+ conf_val_next(match);
+ }
+
+ return false;
+}
+
+struct sockaddr_storage conf_addr_range(
+ conf_val_t *val,
+ struct sockaddr_storage *max_ss,
+ int *prefix_len)
+{
+ assert(val != NULL && val->item != NULL && max_ss != NULL &&
+ prefix_len != 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) {
+ conf_val(val);
+ assert(val->data);
+ out = yp_addr_noport(val->data);
+ // addr_type, addr, format, formatted_data (port| addr| empty).
+ const uint8_t *format = val->data + sizeof(uint8_t) +
+ ((out.ss_family == AF_INET) ?
+ IPV4_PREFIXLEN / 8 : IPV6_PREFIXLEN / 8);
+ // See addr_range_to_bin.
+ switch (*format) {
+ case 1:
+ max_ss->ss_family = AF_UNSPEC;
+ *prefix_len = yp_int(format + sizeof(uint8_t));
+ break;
+ case 2:
+ *max_ss = yp_addr_noport(format + sizeof(uint8_t));
+ *prefix_len = -1;
+ break;
+ default:
+ max_ss->ss_family = AF_UNSPEC;
+ *prefix_len = -1;
+ break;
+ }
+ } else {
+ max_ss->ss_family = AF_UNSPEC;
+ *prefix_len = -1;
+ }
+
+ return out;
+}
+
+bool conf_addr_range_match(
+ conf_val_t *range,
+ const struct sockaddr_storage *addr)
+{
+ if (range == NULL || addr == NULL) {
+ return false;
+ }
+
+ while (range->code == KNOT_EOK) {
+ int mask;
+ struct sockaddr_storage min, max;
+
+ min = conf_addr_range(range, &max, &mask);
+ if (max.ss_family == AF_UNSPEC) {
+ if (sockaddr_net_match(addr, &min, mask)) {
+ return true;
+ }
+ } else {
+ if (sockaddr_range_match(addr, &min, &max)) {
+ return true;
+ }
+ }
+
+ conf_val_next(range);
+ }
+
+ return false;
+}
+
+char* conf_abs_path(
+ conf_val_t *val,
+ const char *base_dir)
+{
+ const char *path = conf_str(val);
+ return abs_path(path, base_dir);
+}
+
+conf_mod_id_t* conf_mod_id(
+ conf_val_t *val)
+{
+ assert(val != NULL && val->item != NULL);
+ assert(val->item->type == YP_TDATA ||
+ (val->item->type == YP_TREF &&
+ val->item->var.r.ref->var.g.id->type == YP_TDATA));
+
+ conf_mod_id_t *mod_id = NULL;
+
+ if (val->code == KNOT_EOK) {
+ conf_val(val);
+ assert(val->data);
+
+ mod_id = malloc(sizeof(conf_mod_id_t));
+ if (mod_id == NULL) {
+ return NULL;
+ }
+
+ // Set module name in yp_name_t format + add zero termination.
+ size_t name_len = 1 + val->data[0];
+ mod_id->name = malloc(name_len + 1);
+ if (mod_id->name == NULL) {
+ free(mod_id);
+ return NULL;
+ }
+ memcpy(mod_id->name, val->data, name_len);
+ mod_id->name[name_len] = '\0';
+
+ // Set module identifier.
+ mod_id->len = val->len - name_len;
+ mod_id->data = malloc(mod_id->len);
+ if (mod_id->data == NULL) {
+ free(mod_id->name);
+ free(mod_id);
+ return NULL;
+ }
+ memcpy(mod_id->data, val->data + name_len, mod_id->len);
+ }
+
+ return mod_id;
+}
+
+void conf_free_mod_id(
+ conf_mod_id_t *mod_id)
+{
+ if (mod_id == NULL) {
+ return;
+ }
+
+ free(mod_id->name);
+ free(mod_id->data);
+ free(mod_id);
+}
+
+static int get_index(
+ const char **start,
+ const char *end,
+ unsigned *index1,
+ unsigned *index2)
+{
+ char c, *p;
+ if (sscanf(*start, "[%u%c", index1, &c) != 2) {
+ return KNOT_EINVAL;
+ }
+ switch (c) {
+ case '-':
+ p = strchr(*start, '-') + 1;
+ if (end - p < 2 || index2 == NULL ||
+ sscanf(p, "%u%c", index2, &c) != 2 || c != ']') {
+ return KNOT_EINVAL;
+ }
+ break;
+ case ']':
+ if (index2 != NULL) {
+ *index2 = *index1;
+ }
+ break;
+ default:
+ return KNOT_EINVAL;
+ }
+
+ *start = strchr(*start, ']') + 1;
+ return ((*index1 < 256 && (index2 == NULL || *index2 < 256)
+ && end - *start >= 0 && (index2 == NULL || *index2 >= *index1))
+ ? KNOT_EOK : KNOT_EINVAL);
+}
+
+static void replace_slashes(
+ char *name,
+ bool remove_dot)
+{
+ // Replace possible slashes with underscores.
+ char *ch;
+ for (ch = name; *ch != '\0'; ch++) {
+ if (*ch == '/') {
+ *ch = '_';
+ }
+ }
+
+ // Remove trailing dot.
+ if (remove_dot && ch > name) {
+ assert(*(ch - 1) == '.');
+ *(ch - 1) = '\0';
+ }
+}
+
+static int str_char(
+ const knot_dname_t *zone,
+ char *buff,
+ size_t buff_len,
+ unsigned index1,
+ unsigned index2)
+{
+ assert(buff);
+
+ if (knot_dname_to_str(buff, zone, buff_len) == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ size_t zone_len = strlen(buff);
+ assert(zone_len > 0);
+
+ // Get the block length.
+ size_t len = index2 - index1 + 1;
+
+ // Check for out of scope block.
+ if (index1 >= zone_len) {
+ buff[0] = '\0';
+ return KNOT_EOK;
+ }
+ // Check for partial block.
+ if (index2 >= zone_len) {
+ len = zone_len - index1;
+ }
+
+ // Copy the block.
+ memmove(buff, buff + index1, len);
+ buff[len] = '\0';
+
+ // Replace possible slashes with underscores.
+ replace_slashes(buff, false);
+
+ return KNOT_EOK;
+}
+
+static int str_zone(
+ const knot_dname_t *zone,
+ char *buff,
+ size_t buff_len)
+{
+ assert(buff);
+
+ if (knot_dname_to_str(buff, zone, buff_len) == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ // Replace possible slashes with underscores.
+ replace_slashes(buff, true);
+
+ return KNOT_EOK;
+}
+
+static int str_label(
+ const knot_dname_t *zone,
+ char *buff,
+ size_t buff_len,
+ size_t right_index)
+{
+ size_t labels = knot_dname_labels(zone, NULL);
+
+ // Check for root label of the root zone.
+ if (labels == 0 && right_index == 0) {
+ return str_zone(zone, buff, buff_len);
+ // Check for labels error or for an exceeded index.
+ } else if (labels < 1 || labels <= right_index) {
+ buff[0] = '\0';
+ return KNOT_EOK;
+ }
+
+ // ~ Label length + label + root label.
+ knot_dname_t label[1 + KNOT_DNAME_MAXLABELLEN + 1];
+
+ // Compute the index from the left.
+ assert(labels > right_index);
+ size_t index = labels - right_index - 1;
+
+ // Create a dname from the single label.
+ size_t prefix_len = knot_dname_prefixlen(zone, index, NULL);
+ size_t label_len = *(zone + prefix_len);
+ memcpy(label, zone + prefix_len, 1 + label_len);
+ label[1 + label_len] = '\0';
+
+ return str_zone(label, buff, buff_len);
+}
+
+static char* get_filename(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const knot_dname_t *zone,
+ const char *name)
+{
+ assert(name);
+
+ const char *end = name + strlen(name);
+ char out[1024] = "";
+
+ do {
+ // Search for a formatter.
+ const char *pos = strchr(name, '%');
+
+ // If no formatter, copy the rest of the name.
+ if (pos == NULL) {
+ if (strlcat(out, name, sizeof(out)) >= sizeof(out)) {
+ CONF_LOG_ZONE(LOG_WARNING, zone, "too long zonefile name");
+ return NULL;
+ }
+ break;
+ }
+
+ // Copy constant block.
+ char *block = strndup(name, pos - name);
+ if (block == NULL ||
+ strlcat(out, block, sizeof(out)) >= sizeof(out)) {
+ CONF_LOG_ZONE(LOG_WARNING, zone, "too long zonefile name");
+ free(block);
+ return NULL;
+ }
+ free(block);
+
+ // Move name pointer behind the formatter.
+ name = pos + 2;
+
+ char buff[512] = "";
+ unsigned idx1, idx2;
+ bool failed = false;
+
+ const char type = *(pos + 1);
+ switch (type) {
+ case '%':
+ strlcat(buff, "%", sizeof(buff));
+ break;
+ case 'c':
+ if (get_index(&name, end, &idx1, &idx2) != KNOT_EOK ||
+ str_char(zone, buff, sizeof(buff), idx1, idx2) != KNOT_EOK) {
+ failed = true;
+ }
+ break;
+ case 'l':
+ if (get_index(&name, end, &idx1, NULL) != KNOT_EOK ||
+ str_label(zone, buff, sizeof(buff), idx1) != KNOT_EOK) {
+ failed = true;
+ }
+ break;
+ case 's':
+ if (str_zone(zone, buff, sizeof(buff)) != KNOT_EOK) {
+ failed = true;
+ }
+ break;
+ case '\0':
+ CONF_LOG_ZONE(LOG_WARNING, zone, "ignoring missing "
+ "trailing zonefile formatter");
+ continue;
+ default:
+ CONF_LOG_ZONE(LOG_WARNING, zone, "ignoring zonefile "
+ "formatter '%%%c'", type);
+ continue;
+ }
+
+ if (failed) {
+ CONF_LOG_ZONE(LOG_WARNING, zone, "failed to process "
+ "zonefile formatter '%%%c'", type);
+ return NULL;
+ }
+
+ if (strlcat(out, buff, sizeof(out)) >= sizeof(out)) {
+ CONF_LOG_ZONE(LOG_WARNING, zone, "too long zonefile name");
+ return NULL;
+ }
+ } while (name < end);
+
+ // Use storage prefix if not absolute path.
+ if (out[0] == '/') {
+ return strdup(out);
+ } else {
+ conf_val_t val = conf_zone_get_txn(conf, txn, C_STORAGE, zone);
+ char *storage = conf_abs_path(&val, NULL);
+ if (storage == NULL) {
+ return NULL;
+ }
+ char *abs = sprintf_alloc("%s/%s", storage, out);
+ free(storage);
+ return abs;
+ }
+}
+
+char* conf_zonefile_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const knot_dname_t *zone)
+{
+ if (zone == NULL) {
+ return NULL;
+ }
+
+ conf_val_t val = conf_zone_get_txn(conf, txn, C_FILE, zone);
+ const char *file = conf_str(&val);
+
+ // Use default zonefile name pattern if not specified.
+ if (file == NULL) {
+ file = "%s.zone";
+ }
+
+ return get_filename(conf, txn, zone, file);
+}
+
+char* conf_db_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *db_type)
+{
+ conf_val_t storage_val = conf_get_txn(conf, txn, C_DB, C_STORAGE);
+ char *storage = conf_abs_path(&storage_val, NULL);
+
+ if (db_type == NULL) {
+ return storage;
+ }
+
+ conf_val_t db_val = conf_get_txn(conf, txn, C_DB, db_type);
+ char *dbdir = conf_abs_path(&db_val, storage);
+ free(storage);
+
+ return dbdir;
+}
+
+char *conf_tls_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *tls_item)
+{
+ conf_val_t tls_val = conf_get_txn(conf, txn, C_SRV, tls_item);
+ if (conf_str(&tls_val) == NULL) {
+ return NULL;
+ }
+
+ return conf_abs_path(&tls_val, CONFIG_DIR);
+}
+
+size_t conf_udp_threads_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn)
+{
+ conf_val_t val = conf_get_txn(conf, txn, C_SRV, C_UDP_WORKERS);
+ int64_t workers = conf_int(&val);
+ assert(workers <= CONF_MAX_UDP_WORKERS);
+ if (workers == YP_NIL) {
+ return MIN(dt_optimal_size(), CONF_MAX_UDP_WORKERS);
+ }
+
+ return workers;
+}
+
+size_t conf_tcp_threads_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn)
+{
+ conf_val_t val = conf_get_txn(conf, txn, C_SRV, C_TCP_WORKERS);
+ int64_t workers = conf_int(&val);
+ assert(workers <= CONF_MAX_TCP_WORKERS);
+ if (workers == YP_NIL) {
+ size_t optimal = MAX(dt_optimal_size(), DFLT_MIN_TCP_WORKERS);
+ return MIN(optimal, CONF_MAX_TCP_WORKERS);
+ }
+
+ return workers;
+}
+
+size_t conf_xdp_threads_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn)
+{
+ size_t workers = 0;
+
+ conf_val_t val = conf_get_txn(conf, txn, C_XDP, C_LISTEN);
+ while (val.code == KNOT_EOK) {
+ struct sockaddr_storage addr = conf_addr(&val, NULL);
+ conf_xdp_iface_t iface;
+ int ret = conf_xdp_iface(&addr, &iface);
+ if (ret == KNOT_EOK) {
+ workers += iface.queues;
+ }
+ conf_val_next(&val);
+ }
+
+ return workers;
+}
+
+size_t conf_bg_threads_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn)
+{
+ conf_val_t val = conf_get_txn(conf, txn, C_SRV, C_BG_WORKERS);
+ int64_t workers = conf_int(&val);
+ assert(workers <= CONF_MAX_BG_WORKERS);
+ if (workers == YP_NIL) {
+ assert(DFLT_MAX_BG_WORKERS <= CONF_MAX_BG_WORKERS);
+ return MIN(dt_optimal_size(), DFLT_MAX_BG_WORKERS);
+ }
+
+ return workers;
+}
+
+size_t conf_tcp_max_clients_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn)
+{
+ conf_val_t val = conf_get_txn(conf, txn, C_SRV, C_TCP_MAX_CLIENTS);
+ int64_t clients = conf_int(&val);
+ if (clients == YP_NIL) {
+ static size_t permval = 0;
+ if (permval == 0) {
+ struct rlimit numfiles;
+ if (getrlimit(RLIMIT_NOFILE, &numfiles) == 0) {
+ permval = (size_t)numfiles.rlim_cur / 2;
+ } else {
+ permval = FALLBACK_MAX_TCP_CLIENTS;
+ }
+ }
+ return permval;
+ }
+
+ return clients;
+}
+
+int conf_user_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ int *uid,
+ int *gid)
+{
+ if (uid == NULL || gid == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ conf_val_t val = conf_get_txn(conf, txn, C_SRV, C_USER);
+ if (val.code == KNOT_EOK) {
+ char *user = strdup(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) {
+ *gid = grp->gr_gid;
+ } else {
+ CONF_LOG(LOG_ERR, "invalid group name '%s'",
+ sep_pos + 1);
+ free(user);
+ return KNOT_EINVAL;
+ }
+
+ // Cut off group part.
+ *sep_pos = '\0';
+ } else {
+ *gid = getgid();
+ }
+
+ // Process user name.
+ struct passwd *pwd = getpwnam(user);
+ if (pwd != NULL) {
+ *uid = pwd->pw_uid;
+ } else {
+ CONF_LOG(LOG_ERR, "invalid user name '%s'", user);
+ free(user);
+ return KNOT_EINVAL;
+ }
+
+ free(user);
+ return KNOT_EOK;
+ } else if (val.code == KNOT_ENOENT) {
+ *uid = getuid();
+ *gid = getgid();
+ return KNOT_EOK;
+ } else {
+ return val.code;
+ }
+}
+
+conf_remote_t conf_remote_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ conf_val_t *id,
+ size_t index)
+{
+ assert(id != NULL && id->item != NULL);
+ assert(id->item->type == YP_TSTR ||
+ (id->item->type == YP_TREF &&
+ id->item->var.r.ref->var.g.id->type == YP_TSTR));
+
+ conf_remote_t out = { { AF_UNSPEC } };
+
+ conf_val_t rundir_val = conf_get_txn(conf, txn, C_SRV, C_RUNDIR);
+ char *rundir = conf_abs_path(&rundir_val, NULL);
+
+ // Get indexed remote address.
+ conf_val_t val = conf_id_get_txn(conf, txn, C_RMT, C_ADDR, id);
+ for (size_t i = 0; val.code == KNOT_EOK && i < index; i++) {
+ if (i == 0) {
+ conf_val(&val);
+ }
+ conf_val_next(&val);
+ }
+ // Index overflow causes empty socket.
+ out.addr = conf_addr(&val, rundir);
+
+ // Get outgoing address if family matches (optional).
+ val = conf_id_get_txn(conf, txn, C_RMT, C_VIA, id);
+ while (val.code == KNOT_EOK) {
+ struct sockaddr_storage via = conf_addr(&val, rundir);
+ if (via.ss_family == out.addr.ss_family) {
+ out.via = conf_addr(&val, rundir);
+ break;
+ }
+ conf_val_next(&val);
+ }
+
+ // Get TSIG key (optional).
+ conf_val_t key_id = conf_id_get_txn(conf, txn, 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_txn(conf, txn, C_KEY, C_ALG, &key_id);
+ out.key.algorithm = conf_opt(&val);
+
+ val = conf_id_get_txn(conf, txn, C_KEY, C_SECRET, &key_id);
+ out.key.secret.data = (uint8_t *)conf_bin(&val, &out.key.secret.size);
+ }
+
+ free(rundir);
+
+ val = conf_id_get_txn(conf, txn, C_RMT, C_BLOCK_NOTIFY_XFR, id);
+ out.block_notify_after_xfr = conf_bool(&val);
+
+ val = conf_id_get_txn(conf, txn, C_RMT, C_NO_EDNS, id);
+ out.no_edns = conf_bool(&val);
+
+ return out;
+}
+
+int conf_xdp_iface(
+ struct sockaddr_storage *addr,
+ conf_xdp_iface_t *iface)
+{
+#ifndef ENABLE_XDP
+ return KNOT_ENOTSUP;
+#else
+ if (addr == NULL || iface == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ if (addr->ss_family == AF_UNIX) {
+ const char *addr_str = ((struct sockaddr_un *)addr)->sun_path;
+ strlcpy(iface->name, addr_str, sizeof(iface->name));
+
+ const char *port = strchr(addr_str, '@');
+ if (port != NULL) {
+ iface->name[port - addr_str] = '\0';
+ int ret = str_to_u16(port + 1, &iface->port);
+ if (ret != KNOT_EOK) {
+ return ret;
+ } else if (iface->port == 0) {
+ return KNOT_EINVAL;
+ }
+ } else {
+ iface->port = 53;
+ }
+ } else {
+ int ret = knot_eth_name_from_addr(addr, iface->name, sizeof(iface->name));
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ ret = sockaddr_port(addr);
+ if (ret < 0) { // Cannot check for 0 as don't know if port specified.
+ return KNOT_EINVAL;
+ }
+ iface->port = ret;
+ }
+
+ int queues = knot_eth_queues(iface->name);
+ if (queues <= 0) {
+ assert(queues != 0);
+ return queues;
+ }
+ iface->queues = queues;
+
+ return KNOT_EOK;
+#endif
+}
diff --git a/src/knot/conf/conf.h b/src/knot/conf/conf.h
new file mode 100644
index 0000000..83dfd1d
--- /dev/null
+++ b/src/knot/conf/conf.h
@@ -0,0 +1,939 @@
+/* Copyright (C) 2022 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 <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <sys/socket.h>
+
+#include "knot/conf/base.h"
+#include "knot/conf/schema.h"
+
+/*! Configuration remote getter output. */
+typedef struct {
+ /*! Target socket address. */
+ struct sockaddr_storage addr;
+ /*! Local outgoing socket address. */
+ struct sockaddr_storage via;
+ /*! TSIG key. */
+ knot_tsig_key_t key;
+ /*! Suppress sending NOTIFY after zone transfer from this master. */
+ bool block_notify_after_xfr;
+ /*! Disable EDNS on XFR queries. */
+ bool no_edns;
+} conf_remote_t;
+
+/*! Configuration section iterator. */
+typedef struct {
+ /*! Item description. */
+ const yp_item_t *item;
+ /*! Namedb iterator. */
+ knot_db_iter_t *iter;
+ /*! Key0 database code. */
+ uint8_t key0_code;
+ // Public items.
+ /*! Iterator return code. */
+ int code;
+} conf_iter_t;
+
+/*! Configuration iterator over mixed references (e.g. remote and remotes). */
+typedef struct {
+ /*! Configuration context. */
+ conf_t *conf;
+ /*! Mixed references. */
+ conf_val_t *mix_id;
+ /*! Temporary nested references. */
+ conf_val_t sub_id;
+ /*! Current (possibly expanded) reference to use. */
+ conf_val_t *id;
+ /*! Nested references in use indication. */
+ bool nested;
+} conf_mix_iter_t;
+
+/*! Configuration module getter output. */
+typedef struct {
+ /*! Module name. */
+ yp_name_t *name;
+ /*! Module id data. */
+ uint8_t *data;
+ /*! Module id data length. */
+ size_t len;
+} conf_mod_id_t;
+
+/*!
+ * Check if the configuration database exists on the filesystem.
+ *
+ * \param[in] db_dir Database path.
+ *
+ * \return True if it already exists.
+ */
+
+bool conf_db_exists(
+ const char *db_dir
+);
+
+/*!
+ * Gets the configuration item value of the section without identifiers.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ * \param[in] key0_name Section name.
+ * \param[in] key1_name Item name.
+ *
+ * \return Item value.
+ */
+conf_val_t conf_get_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key0_name,
+ const yp_name_t *key1_name
+);
+static inline conf_val_t conf_get(
+ conf_t *conf,
+ const yp_name_t *key0_name,
+ const yp_name_t *key1_name)
+{
+ return conf_get_txn(conf, &conf->read_txn, key0_name, key1_name);
+}
+
+/*!
+ * Gets the configuration item value of the section with identifiers (raw version).
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ * \param[in] key0_name Section name.
+ * \param[in] key1_name Item name.
+ * \param[in] id Section identifier (raw value).
+ * \param[in] id_len Length of the section identifier.
+ *
+ * \return Item value.
+ */
+conf_val_t conf_rawid_get_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key0_name,
+ const yp_name_t *key1_name,
+ const uint8_t *id,
+ size_t id_len
+);
+static inline conf_val_t conf_rawid_get(
+ conf_t *conf,
+ const yp_name_t *key0_name,
+ const yp_name_t *key1_name,
+ const uint8_t *id,
+ size_t id_len)
+{
+ return conf_rawid_get_txn(conf, &conf->read_txn, key0_name, key1_name,
+ id, id_len);
+}
+
+/*!
+ * Gets the configuration item value of the section with identifiers.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ * \param[in] key0_name Section name.
+ * \param[in] key1_name Item name.
+ * \param[in] id Section identifier (output of a config getter).
+ *
+ * \return Item value.
+ */
+conf_val_t conf_id_get_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key0_name,
+ const yp_name_t *key1_name,
+ conf_val_t *id
+);
+static inline 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)
+{
+ return conf_id_get_txn(conf, &conf->read_txn, key0_name, key1_name, id);
+}
+
+/*!
+ * Gets the configuration item value of the module section.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ * \param[in] key1_name Item name.
+ * \param[in] mod_id Module identifier.
+ *
+ * \return Item value.
+ */
+conf_val_t conf_mod_get_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key1_name,
+ const conf_mod_id_t *mod_id
+);
+static inline conf_val_t conf_mod_get(
+ conf_t *conf,
+ const yp_name_t *key1_name,
+ const conf_mod_id_t *mod_id)
+{
+ return conf_mod_get_txn(conf, &conf->read_txn, key1_name, mod_id);
+}
+
+/*!
+ * Gets the configuration item value of the zone section.
+ *
+ * \note A possibly associated template is taken into account.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ * \param[in] key1_name Item name.
+ * \param[in] dname Zone name.
+ *
+ * \return Item value.
+ */
+conf_val_t conf_zone_get_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key1_name,
+ const knot_dname_t *dname
+);
+static inline conf_val_t conf_zone_get(
+ conf_t *conf,
+ const yp_name_t *key1_name,
+ const knot_dname_t *dname)
+{
+ return conf_zone_get_txn(conf, &conf->read_txn, key1_name, dname);
+}
+
+/*!
+ * Gets the configuration item value of the default template.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ * \param[in] key1_name Item name.
+ *
+ * \return Item value.
+ */
+conf_val_t conf_default_get_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key1_name
+);
+static inline conf_val_t conf_default_get(
+ conf_t *conf,
+ const yp_name_t *key1_name)
+{
+ return conf_default_get_txn(conf, &conf->read_txn, key1_name);
+}
+
+/*!
+ * Checks the configuration section for the identifier (raw version).
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ * \param[in] key0_name Section name.
+ * \param[in] id Section identifier (raw value).
+ * \param[in] id_len Length of the section identifier.
+ *
+ * \return True if exists.
+ */
+bool conf_rawid_exists_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key0_name,
+ const uint8_t *id,
+ size_t id_len
+);
+static inline bool conf_rawid_exists(
+ conf_t *conf,
+ const yp_name_t *key0_name,
+ const uint8_t *id,
+ size_t id_len)
+{
+ return conf_rawid_exists_txn(conf, &conf->read_txn, key0_name, id, id_len);
+}
+
+/*!
+ * Checks the configuration section for the identifier.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ * \param[in] key0_name Section name.
+ * \param[in] id Section identifier (output of a config getter).
+ *
+ * \return True if exists.
+ */
+bool conf_id_exists_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key0_name,
+ conf_val_t *id
+);
+static inline bool conf_id_exists(
+ conf_t *conf,
+ const yp_name_t *key0_name,
+ conf_val_t *id)
+{
+ return conf_id_exists_txn(conf, &conf->read_txn, key0_name, id);
+}
+
+/*!
+ * Gets the number of section identifiers.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ * \param[in] key0_name Section name.
+ *
+ * \return Number of identifiers.
+ */
+size_t conf_id_count_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key0_name
+);
+static inline size_t conf_id_count(
+ conf_t *conf,
+ const yp_name_t *key0_name)
+{
+ return conf_id_count_txn(conf, &conf->read_txn, key0_name);
+}
+
+/*!
+ * Gets a configuration section iterator.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ * \param[in] key0_name Section name.
+ *
+ * \return Section iterator.
+ */
+conf_iter_t conf_iter_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key0_name
+);
+static inline conf_iter_t conf_iter(
+ conf_t *conf,
+ const yp_name_t *key0_name)
+{
+ return conf_iter_txn(conf, &conf->read_txn, key0_name);
+}
+
+/*!
+ * Moves the configuration section iterator to the next identifier.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] iter Configuration iterator.
+ */
+void conf_iter_next(
+ conf_t *conf,
+ conf_iter_t *iter
+);
+
+/*!
+ * Gets the current iterator value (identifier).
+ *
+ * \param[in] conf Configuration.
+ * \param[in] iter Configuration iterator.
+ *
+ * \return Section identifier.
+ */
+conf_val_t conf_iter_id(
+ conf_t *conf,
+ conf_iter_t *iter
+);
+
+/*!
+ * Deletes the section iterator.
+ *
+ * This function should be called when the iterating is early interrupted,
+ * otherwise this is done automatically at KNOT_EOF.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] iter Configuration iterator.
+ */
+void conf_iter_finish(
+ conf_t *conf,
+ conf_iter_t *iter
+);
+
+/*!
+ * Prepares the value for the direct access.
+ *
+ * The following access is through val->len and val->data.
+ *
+ * \param[in] val Item value.
+ */
+void conf_val(
+ conf_val_t *val
+);
+
+/*!
+ * Moves to the next value of a multi-valued item.
+ *
+ * \param[in] val Item value.
+ */
+void conf_val_next(
+ conf_val_t *val
+);
+
+/*!
+ * Resets to the first value of a multi-valued item.
+ *
+ * \param[in] val Item value.
+ */
+void conf_val_reset(
+ conf_val_t *val
+);
+
+/*!
+ * Gets the number of values if multivalued item.
+ *
+ * \param[in] val Item value.
+ *
+ * \return Number of values.
+ */
+size_t conf_val_count(
+ conf_val_t *val
+);
+
+/*!
+ * Checks if two item values are equal.
+ *
+ * \param[in] val1 First item value.
+ * \param[in] val2 Second item value.
+ *
+ * \return true if equal, false if not.
+ */
+bool conf_val_equal(
+ conf_val_t *val1,
+ conf_val_t *val2
+);
+
+/*!
+ * Initializes a mixed reference iterator.
+ *
+ * The following access is through iter->id.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] mix_id First mixed reference.
+ * \param[out] iter Iterator to be initialized.
+ */
+void conf_mix_iter_init(
+ conf_t *conf,
+ conf_val_t *mix_id,
+ conf_mix_iter_t *iter
+);
+
+/*!
+ * Increments the mixed iterator.
+ *
+ * \param[in] iter Mixed reference iterator.
+ */
+void conf_mix_iter_next(
+ conf_mix_iter_t *iter
+);
+
+/*!
+ * Gets the numeric value of the item.
+ *
+ * \param[in] val Item value.
+ *
+ * \return Integer.
+ */
+int64_t conf_int(
+ conf_val_t *val
+);
+
+/*!
+ * Gets the boolean value of the item.
+ *
+ * \param[in] val Item value.
+ *
+ * \return Boolean.
+ */
+bool conf_bool(
+ conf_val_t *val
+);
+
+/*!
+ * Gets the option value of the item.
+ *
+ * \param[in] val Item value.
+ *
+ * \return Option id.
+ */
+unsigned conf_opt(
+ conf_val_t *val
+);
+
+/*!
+ * Gets the string value of the item.
+ *
+ * \param[in] val Item value.
+ *
+ * \return String pointer.
+ */
+const char* conf_str(
+ conf_val_t *val
+);
+
+/*!
+ * Gets the dname value of the item.
+ *
+ * \param[in] val Item value.
+ *
+ * \return Dname pointer.
+ */
+const knot_dname_t* conf_dname(
+ conf_val_t *val
+);
+
+/*!
+ * Gets the length-prefixed data value of the item.
+ *
+ * \param[in] val Item value.
+ * \param[out] len Output length.
+ *
+ * \return Data pointer.
+ */
+const uint8_t* conf_bin(
+ conf_val_t *val,
+ size_t *len
+);
+
+/*!
+ * Gets the generic data value of the item.
+ *
+ * \param[in] val Item value.
+ * \param[out] len Output length.
+ *
+ * \return Data pointer.
+ */
+const uint8_t* conf_data(
+ conf_val_t *val,
+ size_t *len
+);
+
+/*!
+ * Gets the socket address value of the item.
+ *
+ * \param[in] val Item value.
+ * \param[in] sock_base_dir Path prefix for a relative UNIX socket location.
+ *
+ * \return Socket address.
+ */
+struct sockaddr_storage conf_addr(
+ conf_val_t *val,
+ const char *sock_base_dir
+);
+
+/*!
+ * Checks the configured address if equal to given one (except port).
+ *
+ * \param[in] match Configured address.
+ * \param[in] addr Address to check.
+ *
+ * \return True if matches.
+ */
+bool conf_addr_match(
+ conf_val_t *match,
+ const struct sockaddr_storage *addr
+);
+
+/*!
+ * Gets the socket address range value of the item.
+ *
+ * \param[in] val Item value.
+ * \param[out] max_ss Upper address bound or AF_UNSPEC family if not specified.
+ * \param[out] prefix_len Network subnet prefix length or -1 if not specified.
+ *
+ * \return Socket address.
+ */
+struct sockaddr_storage conf_addr_range(
+ conf_val_t *val,
+ struct sockaddr_storage *max_ss,
+ int *prefix_len
+);
+
+/*!
+ * Checks the address if matches given address range/network block.
+ *
+ * \param[in] range Address range/network block.
+ * \param[in] addr Address to check.
+ *
+ * \return True if matches.
+ */
+bool conf_addr_range_match(
+ conf_val_t *range,
+ const struct sockaddr_storage *addr
+);
+
+/*!
+ * Gets the absolute string value of the item.
+ *
+ * \note The result must be explicitly deallocated.
+ *
+ * \param[in] val Item value.
+ * \param[in] base_dir Path prefix for a relative string.
+ *
+ * \return Absolute path string pointer.
+ */
+char* conf_abs_path(
+ conf_val_t *val,
+ const char *base_dir
+);
+
+/*!
+ * Ensures empty 'default' identifier value.
+ *
+ * \param[in] val Item value.
+ *
+ * \return Empty item value.
+ */
+static inline void conf_id_fix_default(conf_val_t *val)
+{
+ if (val->code != KNOT_EOK) {
+ conf_val_t empty = {
+ .item = val->item,
+ .code = KNOT_EOK
+ };
+
+ *val = empty;
+ }
+}
+
+/*!
+ * Gets the module identifier value of the item.
+ *
+ * \param[in] val Item value.
+ *
+ * \return Module identifier.
+ */
+conf_mod_id_t* conf_mod_id(
+ conf_val_t *val
+);
+
+/*!
+ * Destroys the module identifier.
+ *
+ * \param[in] mod_id Module identifier.
+ */
+void conf_free_mod_id(
+ conf_mod_id_t *mod_id
+);
+
+/*!
+ * Gets the absolute zone file path.
+ *
+ * \note The result must be explicitly deallocated.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ * \param[in] zone Zone name.
+ *
+ * \return Absolute zone file path string pointer.
+ */
+char* conf_zonefile_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const knot_dname_t *zone
+);
+static inline char* conf_zonefile(
+ conf_t *conf,
+ const knot_dname_t *zone)
+{
+ return conf_zonefile_txn(conf, &conf->read_txn, zone);
+}
+
+/*!
+ * Gets the absolute directory path for a database.
+ *
+ * e.g. Journal, KASP db, Timers
+ *
+ * \note The result must be explicitly deallocated.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ * \param[in] db_type Database name.
+ *
+ * \return Absolute database path string pointer.
+ */
+char* conf_db_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *db_type
+);
+static inline char* conf_db(
+ conf_t *conf,
+ const yp_name_t *db_type)
+{
+ return conf_db_txn(conf, &conf->read_txn, db_type);
+}
+
+/*!
+ * Gets the absolute directory path for a TLS key/cert file.
+ *
+ * \note The result must be explicitly deallocated.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ * \param[in] db_type TLS configuration option.
+ *
+ * \return Absolute path string pointer.
+ */
+char *conf_tls_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *tls_item);
+static inline char* conf_tls(
+ conf_t *conf,
+ const yp_name_t *tls_item)
+{
+ return conf_tls_txn(conf, &conf->read_txn, tls_item);
+}
+
+/*!
+ * Gets database-specific parameter.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] param Parameter name.
+ *
+ * \return Item value.
+ */
+static inline conf_val_t conf_db_param(
+ conf_t *conf,
+ const yp_name_t *param)
+{
+ return conf_get_txn(conf, &conf->read_txn, C_DB, param);
+}
+
+/*!
+ * Gets the configured setting of the bool option in the specified section.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] section Section name.
+ * \param[in] param Parameter name.
+ *
+ * \return True if enabled, false otherwise.
+ */
+static inline bool conf_get_bool(
+ conf_t *conf,
+ const yp_name_t *section,
+ const yp_name_t *param)
+{
+ conf_val_t val = conf_get_txn(conf, &conf->read_txn, section, param);
+ return conf_bool(&val);
+}
+
+/*!
+ * Gets the configured setting of the int option in the specified section.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] section Section name.
+ * \param[in] param Parameter name.
+ *
+ * \return Configured integer value.
+ */
+static inline int64_t conf_get_int(
+ conf_t *conf,
+ const yp_name_t *section,
+ const yp_name_t *param)
+{
+ conf_val_t val = conf_get_txn(conf, &conf->read_txn, section, param);
+ return conf_int(&val);
+}
+
+/*!
+ * Gets the configured number of UDP threads.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ *
+ * \return Number of threads.
+ */
+size_t conf_udp_threads_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn
+);
+static inline size_t conf_udp_threads(
+ conf_t *conf)
+{
+ return conf_udp_threads_txn(conf, &conf->read_txn);
+}
+
+/*!
+ * Gets the configured number of TCP threads.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ *
+ * \return Number of threads.
+ */
+size_t conf_tcp_threads_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn
+);
+static inline size_t conf_tcp_threads(
+ conf_t *conf)
+{
+ return conf_tcp_threads_txn(conf, &conf->read_txn);
+}
+
+/*!
+ * Gets the number of used XDP threads.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ *
+ * \return Number of threads.
+ */
+size_t conf_xdp_threads_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn
+);
+static inline size_t conf_xdp_threads(
+ conf_t *conf)
+{
+ return conf_xdp_threads_txn(conf, &conf->read_txn);
+}
+
+/*!
+ * Gets the configured number of worker threads.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ *
+ * \return Number of threads.
+ */
+size_t conf_bg_threads_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn
+);
+static inline size_t conf_bg_threads(
+ conf_t *conf)
+{
+ return conf_bg_threads_txn(conf, &conf->read_txn);
+}
+
+/*!
+ * Gets the required LMDB readers limit based on the current configuration.
+ *
+ * \note The resulting value is a common limit to journal, kasp, timers,
+ * and catalog databases. So it's over-estimated for simplicity reasons.
+ *
+ * \note This function cannot be used for the configuration database setting :-/
+ *
+ * \param[in] conf Configuration.
+ *
+ * \return Number of readers.
+ */
+static inline size_t conf_lmdb_readers(
+ conf_t *conf)
+{
+ if (conf == NULL) { // Return default in tests.
+ return 126;
+ }
+ return conf_udp_threads(conf) + conf_tcp_threads(conf) +
+ conf_bg_threads(conf) + conf_xdp_threads(conf) + 2; // Main thread, utils.
+}
+
+/*!
+ * Gets the configured maximum number of TCP clients.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ *
+ * \return Maximum number of TCP clients.
+ */
+size_t conf_tcp_max_clients_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn
+);
+static inline size_t conf_tcp_max_clients(
+ conf_t *conf)
+{
+ return conf_tcp_max_clients_txn(conf, &conf->read_txn);
+}
+
+/*!
+ * Gets the configured user and group identifiers.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ * \param[out] uid User identifier.
+ * \param[out] gid Group identifier.
+ *
+ * \return Knot error code.
+ */
+int conf_user_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ int *uid,
+ int *gid
+);
+static inline int conf_user(
+ conf_t *conf,
+ int *uid,
+ int *gid)
+{
+ return conf_user_txn(conf, &conf->read_txn, uid, gid);
+}
+
+/*!
+ * Gets the remote parameters for the given identifier.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ * \param[in] id Remote identifier.
+ * \param[in] index Remote index (counted from 0).
+ *
+ * \return Remote parameters.
+ */
+conf_remote_t conf_remote_txn(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ conf_val_t *id,
+ size_t index
+);
+static inline conf_remote_t conf_remote(
+ conf_t *conf,
+ conf_val_t *id,
+ size_t index)
+{
+ return conf_remote_txn(conf, &conf->read_txn, id, index);
+}
+
+/*! XDP interface parameters. */
+typedef struct {
+ /*! Interface name. */
+ char name[32];
+ /*! UDP port to listen on. */
+ uint16_t port;
+ /*! Number of active IO queues. */
+ uint16_t queues;
+} conf_xdp_iface_t;
+
+/*!
+ * Gets the XDP interface parameters for a given configuration value.
+ *
+ * \param[in] addr XDP interface name stored in the configuration.
+ * \param[out] iface Interface parameters.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_xdp_iface(
+ struct sockaddr_storage *addr,
+ conf_xdp_iface_t *iface
+);
diff --git a/src/knot/conf/confdb.c b/src/knot/conf/confdb.c
new file mode 100644
index 0000000..e1262c2
--- /dev/null
+++ b/src/knot/conf/confdb.c
@@ -0,0 +1,951 @@
+/* Copyright (C) 2019 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 <https://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"
+#include "contrib/openbsd/strlcpy.h"
+#include "contrib/wire_ctx.h"
+
+/*
+ * A simple configuration:
+ *
+ * server.identity: "knot"
+ * server.version: "version"
+ * template[tpl1].storage: "directory1"
+ * template[tpl2].storage: "directory2"
+ * template[tpl2].master: [ "master1", "master2" ]
+ *
+ * And the corresponding configuration DB content:
+ *
+ * # DB structure version.
+ * [00][FF]: [02]
+ * # Sections codes.
+ * [00][00]server: [02]
+ * [00][00]template: [03]
+ * # Server section items codes.
+ * [02][00]identity: [02]
+ * [02][00]version: [03]
+ * # Server items values.
+ * [02][02]: knot\0
+ * [02][03]: version\0
+ * # Template section items codes.
+ * [03][00]master: [03]
+ * [03][00]storage: [02]
+ * # Template identifiers.
+ * [03][01]tpl1\0
+ * [03][01]tpl2\0
+ * # Template items values.
+ * [03][02]tpl1\0: directory1\0
+ * [03][02]tpl2\0: directory2\0
+ * [03][03]tpl2\0: [00][08]master1\0 [00][08]master2\0
+ */
+
+typedef enum {
+ KEY0_ROOT = 0,
+ KEY1_ITEMS = 0,
+ KEY1_ID = 1,
+ KEY1_FIRST = 2,
+ KEY1_LAST = 200,
+ KEY1_VERSION = 255
+} db_code_t;
+
+typedef enum {
+ KEY0_POS = 0,
+ KEY1_POS = 1,
+ NAME_POS = 2
+} db_code_pos_t;
+
+typedef enum {
+ DB_GET,
+ DB_SET,
+ DB_DEL
+} db_action_t;
+
+static int db_check_version(
+ conf_t *conf,
+ knot_db_txn_t *txn)
+{
+ uint8_t k[2] = { KEY0_ROOT, KEY1_VERSION };
+ knot_db_val_t key = { k, sizeof(k) };
+ knot_db_val_t data;
+
+ // Get conf-DB version.
+ int 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;
+}
+
+int conf_db_init(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ bool purge)
+{
+ if (conf == NULL || txn == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ uint8_t k[2] = { KEY0_ROOT, KEY1_VERSION };
+ knot_db_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 };
+ knot_db_val_t data = { d, sizeof(d) };
+ return conf->api->insert(txn, &key, &data, 0);
+ } else if (ret > 0) { // Non-empty DB.
+ if (purge) {
+ // Purge the DB.
+ ret = conf->api->clear(txn);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ return conf_db_init(conf, txn, false);
+ }
+ return KNOT_EOK;
+ } else { // DB error.
+ return ret;
+ }
+}
+
+int conf_db_check(
+ conf_t *conf,
+ knot_db_txn_t *txn)
+{
+ int ret = conf->api->count(txn);
+ if (ret == 0) { // Not initialized DB.
+ return KNOT_CONF_ENOTINIT;
+ } else if (ret > 0) { // Check the DB.
+ int count = ret;
+
+ ret = db_check_version(conf, txn);
+ if (ret != KNOT_EOK) {
+ return ret;
+ } else if (count == 1) {
+ return KNOT_EOK; // Empty but initialized DB.
+ } else {
+ return count - 1; // Non-empty DB.
+ }
+ } else { // DB error.
+ return ret;
+ }
+}
+
+static int db_code(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ uint8_t section_code,
+ const yp_name_t *name,
+ db_action_t action,
+ uint8_t *code)
+{
+ if (name == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ knot_db_val_t key;
+ uint8_t k[CONF_MIN_KEY_LEN + YP_MAX_ITEM_NAME_LEN];
+ k[KEY0_POS] = section_code;
+ k[KEY1_POS] = 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.
+ knot_db_val_t data;
+ int ret = conf->api->find(txn, &key, &data, 0);
+ switch (ret) {
+ case KNOT_EOK:
+ if (action == DB_DEL) {
+ return conf->api->del(txn, &key);
+ }
+ if (code != NULL) {
+ *code = ((uint8_t *)data.data)[0];
+ }
+ return KNOT_EOK;
+ case KNOT_ENOENT:
+ if (action != DB_SET) {
+ return KNOT_ENOENT;
+ }
+ break;
+ default:
+ return ret;
+ }
+
+ // Reduce the key to common prefix only.
+ key.len = CONF_MIN_KEY_LEN;
+
+ bool codes[KEY1_LAST + 1] = { false };
+
+ // Find all used item codes.
+ knot_db_iter_t *it = conf->api->iter_begin(txn, KNOT_DB_NOOP);
+ it = conf->api->iter_seek(it, &key, KNOT_DB_GEQ);
+ while (it != NULL) {
+ knot_db_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;
+ }
+
+ knot_db_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 used_code = ((uint8_t *)iter_val.data)[0];
+ codes[used_code] = true;
+
+ it = conf->api->iter_next(it);
+ }
+ conf->api->iter_finish(it);
+
+ // Find the smallest unused item code.
+ uint8_t new_code = KEY1_FIRST;
+ while (codes[new_code]) {
+ new_code++;
+ if (new_code > KEY1_LAST) {
+ return KNOT_ESPACE;
+ }
+ }
+
+ // 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;
+ }
+
+ if (code != NULL) {
+ *code = new_code;
+ }
+
+ return KNOT_EOK;
+}
+
+static uint8_t *find_data(
+ const knot_db_val_t *value,
+ const knot_db_val_t *current)
+{
+ wire_ctx_t ctx = wire_ctx_init_const(current->data, current->len);
+
+ // Loop over the data array. Each item has 2B length prefix.
+ while (wire_ctx_available(&ctx) > 0) {
+ uint16_t len = wire_ctx_read_u16(&ctx);
+ assert(ctx.error == KNOT_EOK);
+
+ // Check for the same data.
+ if (len == value->len &&
+ (len == 0 || memcmp(ctx.position, value->data, value->len) == 0)) {
+ wire_ctx_skip(&ctx, -sizeof(uint16_t));
+ assert(ctx.error == KNOT_EOK);
+ return ctx.position;
+ }
+ wire_ctx_skip(&ctx, len);
+ }
+
+ assert(ctx.error == KNOT_EOK && wire_ctx_available(&ctx) == 0);
+
+ return NULL;
+}
+
+static int db_set(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ knot_db_val_t *key,
+ knot_db_val_t *data,
+ bool multi)
+{
+ if (!multi) {
+ if (data->len > CONF_MAX_DATA_LEN) {
+ return KNOT_ERANGE;
+ }
+
+ // Insert new (overwrite old) data.
+ return conf->api->insert(txn, key, data, 0);
+ }
+
+ knot_db_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 (find_data(data, &d) != NULL) {
+ return KNOT_EOK;
+ }
+ } else {
+ return ret;
+ }
+
+ // Prepare buffer for all data.
+ size_t new_len = d.len + sizeof(uint16_t) + data->len;
+ if (new_len > CONF_MAX_DATA_LEN) {
+ return KNOT_ESPACE;
+ }
+
+ uint8_t *new_data = malloc(new_len);
+ if (new_data == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ wire_ctx_t ctx = wire_ctx_init(new_data, new_len);
+
+ // Copy current data array.
+ wire_ctx_write(&ctx, d.data, d.len);
+ // Copy length prefix for the new data item.
+ wire_ctx_write_u16(&ctx, data->len);
+ // Copy the new data item.
+ wire_ctx_write(&ctx, data->data, data->len);
+
+ assert(ctx.error == KNOT_EOK && wire_ctx_available(&ctx) == 0);
+
+ 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;
+}
+
+int conf_db_set(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key0,
+ const yp_name_t *key1,
+ const uint8_t *id,
+ size_t id_len,
+ const uint8_t *data,
+ size_t data_len)
+{
+ if (conf == NULL || txn == NULL || key0 == NULL ||
+ (id == NULL && id_len > 0) || (data == NULL && data_len > 0)) {
+ return KNOT_EINVAL;
+ }
+
+ // Check for valid keys.
+ const yp_item_t *item = yp_schema_find(key1 != NULL ? key1 : key0,
+ key1 != NULL ? key0 : NULL,
+ conf->schema);
+ if (item == NULL) {
+ return KNOT_YP_EINVAL_ITEM;
+ }
+
+ // Ignore alone key0 insertion.
+ if (key1 == NULL && id_len == 0) {
+ return KNOT_EOK;
+ }
+
+ // Ignore group id as a key1.
+ if (item->parent != NULL && (item->parent->flags & YP_FMULTI) != 0 &&
+ item->parent->var.g.id == item) {
+ key1 = NULL;
+ }
+
+ uint8_t k[CONF_MAX_KEY_LEN] = { 0 };
+ knot_db_val_t key = { k, CONF_MIN_KEY_LEN };
+
+ // Set key0 code.
+ int ret = db_code(conf, txn, KEY0_ROOT, key0, DB_SET, &k[KEY0_POS]);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ // Set id part.
+ if (id_len > 0) {
+ if (id_len > YP_MAX_ID_LEN) {
+ return KNOT_YP_EINVAL_ID;
+ }
+ memcpy(k + CONF_MIN_KEY_LEN, id, id_len);
+ key.len += id_len;
+
+ k[KEY1_POS] = KEY1_ID;
+ knot_db_val_t val = { NULL };
+
+ // Insert id.
+ if (key1 == NULL) {
+ ret = conf->api->find(txn, &key, &val, 0);
+ if (ret == KNOT_EOK) {
+ return KNOT_CONF_EREDEFINE;
+ }
+ ret = db_set(conf, txn, &key, &val, false);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ // Check for existing id.
+ } else {
+ ret = conf->api->find(txn, &key, &val, 0);
+ if (ret != KNOT_EOK) {
+ return KNOT_YP_EINVAL_ID;
+ }
+ }
+ }
+
+ // Insert key1 data.
+ if (key1 != NULL) {
+ // Set key1 code.
+ ret = db_code(conf, txn, k[KEY0_POS], key1, DB_SET, &k[KEY1_POS]);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ knot_db_val_t val = { (uint8_t *)data, data_len };
+ ret = db_set(conf, txn, &key, &val, item->flags & YP_FMULTI);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+static int db_unset(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ knot_db_val_t *key,
+ knot_db_val_t *data,
+ bool multi)
+{
+ // No item data can be zero length.
+ if (data->len == 0) {
+ return conf->api->del(txn, key);
+ }
+
+ knot_db_val_t d;
+
+ int ret = conf->api->find(txn, key, &d, 0);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ // Process singlevalued data.
+ if (!multi) {
+ if (d.len != data->len ||
+ memcmp((uint8_t *)d.data, data->data, d.len) != 0) {
+ return KNOT_ENOENT;
+ }
+ return conf->api->del(txn, key);
+ }
+
+ // Check if the data exists.
+ uint8_t *pos = find_data(data, &d);
+ if (pos == NULL) {
+ return KNOT_ENOENT;
+ }
+
+ // Prepare buffer for reduced data.
+ size_t total_len = d.len - sizeof(uint16_t) - data->len;
+ if (total_len == 0) {
+ return conf->api->del(txn, key);
+ }
+
+ uint8_t *new_data = malloc(total_len);
+ if (new_data == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ size_t new_len = 0;
+
+ // Copy leading data block.
+ assert(pos >= (uint8_t *)d.data);
+ size_t head_len = pos - (uint8_t *)d.data;
+ if (head_len > 0) {
+ memcpy(new_data, d.data, head_len);
+ new_len += head_len;
+ }
+
+ pos += sizeof(uint16_t) + data->len;
+
+ // Copy trailing data block.
+ assert(pos <= (uint8_t *)d.data + d.len);
+ size_t tail_len = (uint8_t *)d.data + d.len - pos;
+ if (tail_len > 0) {
+ memcpy(new_data + new_len, pos, tail_len);
+ new_len += tail_len;
+ }
+
+ d.data = new_data;
+ d.len = new_len;
+
+ // Insert reduced data.
+ ret = conf->api->insert(txn, key, &d, 0);
+
+ free(new_data);
+
+ return ret;
+}
+
+int conf_db_unset(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key0,
+ const yp_name_t *key1,
+ const uint8_t *id,
+ size_t id_len,
+ const uint8_t *data,
+ size_t data_len,
+ bool delete_key1)
+{
+ if (conf == NULL || txn == NULL || key0 == NULL ||
+ (id == NULL && id_len > 0) || (data == NULL && data_len > 0)) {
+ return KNOT_EINVAL;
+ }
+
+ // Check for valid keys.
+ const yp_item_t *item = yp_schema_find(key1 != NULL ? key1 : key0,
+ key1 != NULL ? key0 : NULL,
+ conf->schema);
+ if (item == NULL) {
+ return KNOT_YP_EINVAL_ITEM;
+ }
+
+ // Delete the key0.
+ if (key1 == NULL && id_len == 0) {
+ return db_code(conf, txn, KEY0_ROOT, key0, DB_DEL, NULL);
+ }
+
+ // Ignore group id as a key1.
+ if (item->parent != NULL && (item->parent->flags & YP_FMULTI) != 0 &&
+ item->parent->var.g.id == item) {
+ key1 = NULL;
+ }
+
+ uint8_t k[CONF_MAX_KEY_LEN] = { 0 };
+ knot_db_val_t key = { k, CONF_MIN_KEY_LEN };
+
+ // Set the key0 code.
+ int ret = db_code(conf, txn, KEY0_ROOT, key0, DB_GET, &k[KEY0_POS]);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ // Set the id part.
+ if (id_len > 0) {
+ if (id_len > YP_MAX_ID_LEN) {
+ return KNOT_YP_EINVAL_ID;
+ }
+ memcpy(k + CONF_MIN_KEY_LEN, id, id_len);
+ key.len += id_len;
+
+ k[KEY1_POS] = KEY1_ID;
+ knot_db_val_t val = { NULL };
+
+ // Delete the id.
+ if (key1 == NULL) {
+ return conf->api->del(txn, &key);
+ // Check for existing id.
+ } else {
+ ret = conf->api->find(txn, &key, &val, 0);
+ if (ret != KNOT_EOK) {
+ return KNOT_YP_EINVAL_ID;
+ }
+ }
+ }
+
+ if (key1 != NULL) {
+ // Set the key1 code.
+ ret = db_code(conf, txn, k[KEY0_POS], key1, DB_GET, &k[KEY1_POS]);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ // Delete the key1.
+ if (data_len == 0 && delete_key1) {
+ ret = db_code(conf, txn, k[KEY0_POS], key1, DB_DEL, NULL);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ // Delete the item data.
+ } else {
+ knot_db_val_t val = { (uint8_t *)data, data_len };
+ ret = db_unset(conf, txn, &key, &val, item->flags & YP_FMULTI);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+int conf_db_get(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key0,
+ const yp_name_t *key1,
+ const uint8_t *id,
+ size_t id_len,
+ conf_val_t *data)
+{
+ conf_val_t out = { NULL };
+
+ if (conf == NULL || txn == NULL || key0 == NULL ||
+ (id == NULL && id_len > 0)) {
+ out.code = KNOT_EINVAL;
+ goto get_error;
+ }
+
+ // Check for valid keys.
+ out.item = yp_schema_find(key1 != NULL ? key1 : key0,
+ key1 != NULL ? key0 : NULL,
+ conf->schema);
+ if (out.item == NULL) {
+ out.code = KNOT_YP_EINVAL_ITEM;
+ goto get_error;
+ }
+
+ // At least key1 or id must be specified.
+ if (key1 == NULL && id_len == 0) {
+ out.code = KNOT_EINVAL;
+ goto get_error;
+ }
+
+ // Ignore group id as a key1.
+ if (out.item->parent != NULL && (out.item->parent->flags & YP_FMULTI) != 0 &&
+ out.item->parent->var.g.id == out.item) {
+ key1 = NULL;
+ }
+
+ uint8_t k[CONF_MAX_KEY_LEN] = { 0 };
+ knot_db_val_t key = { k, CONF_MIN_KEY_LEN };
+ knot_db_val_t val = { NULL };
+
+ // Set the key0 code.
+ out.code = db_code(conf, txn, KEY0_ROOT, key0, DB_GET, &k[KEY0_POS]);
+ if (out.code != KNOT_EOK) {
+ if (id_len > 0) {
+ out.code = KNOT_YP_EINVAL_ID;
+ }
+ goto get_error;
+ }
+
+ // Set the id part.
+ if (id_len > 0) {
+ if (id_len > YP_MAX_ID_LEN) {
+ out.code = KNOT_YP_EINVAL_ID;
+ goto get_error;
+ }
+ memcpy(k + CONF_MIN_KEY_LEN, id, id_len);
+ key.len += id_len;
+
+ k[KEY1_POS] = KEY1_ID;
+
+ // Check for existing id.
+ out.code = conf->api->find(txn, &key, &val, 0);
+ if (out.code != KNOT_EOK) {
+ out.code = KNOT_YP_EINVAL_ID;
+ goto get_error;
+ }
+ }
+
+ // Set the key1 code.
+ if (key1 != NULL) {
+ out.code = db_code(conf, txn, k[KEY0_POS], key1, DB_GET, &k[KEY1_POS]);
+ if (out.code != KNOT_EOK) {
+ goto get_error;
+ }
+ }
+
+ // Get the data.
+ out.code = conf->api->find(txn, &key, &val, 0);
+ if (out.code == KNOT_EOK) {
+ out.blob = val.data;
+ out.blob_len = val.len;
+ }
+get_error:
+ // Set the output.
+ if (data != NULL) {
+ *data = out;
+ }
+
+ return out.code;
+}
+
+static int check_iter(
+ conf_t *conf,
+ conf_iter_t *iter)
+{
+ knot_db_val_t key;
+
+ // Get the current key.
+ int ret = conf->api->iter_key(iter->iter, &key);
+ if (ret != KNOT_EOK) {
+ return KNOT_ENOENT;
+ }
+ uint8_t *key_data = (uint8_t *)key.data;
+
+ // Check for key overflow.
+ if (key_data[KEY0_POS] != iter->key0_code || key_data[KEY1_POS] != KEY1_ID) {
+ return KNOT_EOF;
+ }
+
+ return KNOT_EOK;
+}
+
+int conf_db_iter_begin(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key0,
+ conf_iter_t *iter)
+{
+ conf_iter_t out = { NULL };
+
+ if (conf == NULL || txn == NULL || key0 == NULL || iter == NULL) {
+ out.code = KNOT_EINVAL;
+ goto iter_begin_error;
+ }
+
+ // Look-up group id item in the schema.
+ const yp_item_t *grp = yp_schema_find(key0, NULL, conf->schema);
+ if (grp == NULL) {
+ out.code = KNOT_YP_EINVAL_ITEM;
+ goto iter_begin_error;
+ }
+ if (grp->type != YP_TGRP || (grp->flags & YP_FMULTI) == 0) {
+ out.code = KNOT_ENOTSUP;
+ goto iter_begin_error;
+ }
+ out.item = grp->var.g.id;
+
+ // Get key0 code.
+ out.code = db_code(conf, txn, KEY0_ROOT, key0, DB_GET, &out.key0_code);
+ if (out.code != KNOT_EOK) {
+ goto iter_begin_error;
+ }
+
+ // Prepare key prefix.
+ uint8_t k[2] = { out.key0_code, KEY1_ID };
+ knot_db_val_t key = { k, sizeof(k) };
+
+ // Get the data.
+ out.iter = conf->api->iter_begin(txn, KNOT_DB_NOOP);
+ out.iter = conf->api->iter_seek(out.iter, &key, KNOT_DB_GEQ);
+
+ // Check for no section id.
+ out.code = check_iter(conf, &out);
+ if (out.code != KNOT_EOK) {
+ out.code = KNOT_ENOENT; // Treat all errors as no entry.
+ conf_db_iter_finish(conf, &out);
+ goto iter_begin_error;
+ }
+
+iter_begin_error:
+ // Set the output.
+ if (iter != NULL) {
+ *iter = out;
+ }
+
+ return out.code;
+}
+
+int conf_db_iter_next(
+ conf_t *conf,
+ conf_iter_t *iter)
+{
+ if (conf == NULL || iter == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ if (iter->code != KNOT_EOK) {
+ return iter->code;
+ }
+ assert(iter->iter != NULL);
+
+ // Move to the next key-value.
+ iter->iter = conf->api->iter_next(iter->iter);
+ if (iter->iter == NULL) {
+ conf_db_iter_finish(conf, iter);
+ iter->code = KNOT_EOF;
+ return iter->code;
+ }
+
+ // Check for key overflow.
+ iter->code = check_iter(conf, iter);
+ if (iter->code != KNOT_EOK) {
+ conf_db_iter_finish(conf, iter);
+ return iter->code;
+ }
+
+ return KNOT_EOK;
+}
+
+int conf_db_iter_id(
+ conf_t *conf,
+ conf_iter_t *iter,
+ const uint8_t **data,
+ size_t *data_len)
+{
+ if (conf == NULL || iter == NULL || iter->iter == NULL ||
+ data == NULL || data_len == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ knot_db_val_t key;
+ int ret = conf->api->iter_key(iter->iter, &key);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ *data = (uint8_t *)key.data + CONF_MIN_KEY_LEN;
+ *data_len = key.len - CONF_MIN_KEY_LEN;
+
+ return KNOT_EOK;
+}
+
+int conf_db_iter_del(
+ conf_t *conf,
+ conf_iter_t *iter)
+{
+ if (conf == NULL || iter == NULL || iter->iter == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ return knot_db_lmdb_iter_del(iter->iter);
+}
+
+void conf_db_iter_finish(
+ conf_t *conf,
+ conf_iter_t *iter)
+{
+ if (conf == NULL || iter == NULL) {
+ return;
+ }
+
+ if (iter->iter != NULL) {
+ conf->api->iter_finish(iter->iter);
+ iter->iter = NULL;
+ }
+}
+
+int conf_db_raw_dump(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const char *file_name)
+{
+ if (conf == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ // Use the current config read transaction if not specified.
+ if (txn == NULL) {
+ txn = &conf->read_txn;
+ }
+
+ FILE *fp = stdout;
+ if (file_name != NULL) {
+ fp = fopen(file_name, "w");
+ if (fp == NULL) {
+ return KNOT_ERROR;
+ }
+ }
+
+ int ret = KNOT_EOK;
+
+ knot_db_iter_t *it = conf->api->iter_begin(txn, KNOT_DB_FIRST);
+ while (it != NULL) {
+ knot_db_val_t key;
+ ret = conf->api->iter_key(it, &key);
+ if (ret != KNOT_EOK) {
+ break;
+ }
+
+ knot_db_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] == 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] == 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 0000000..927200e
--- /dev/null
+++ b/src/knot/conf/confdb.h
@@ -0,0 +1,230 @@
+/* Copyright (C) 2018 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 <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "knot/conf/conf.h"
+#include "libknot/libknot.h"
+#include "libknot/yparser/ypschema.h"
+
+/*! Current version of the configuration database structure. */
+#define CONF_DB_VERSION 2
+/*! 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
+
+/*!
+ * Initializes the configuration DB if empty.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ * \param[in] purge Purge the DB indicator.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_db_init(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ bool purge
+);
+
+/*!
+ * Checks the configuration DB and returns the number of items.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ *
+ * \return Error code, KNOT_EOK if ok and empty, > 0 number of records.
+ */
+int conf_db_check(
+ conf_t *conf,
+ knot_db_txn_t *txn
+);
+
+/*!
+ * Sets the item with data in the configuration DB.
+ *
+ * Singlevalued data is rewritten, multivalued data is appended.
+ *
+ * \note Setting of key0 without key1 has no effect.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ * \param[in] key0 Section name.
+ * \param[in] key1 Item name.
+ * \param[in] id Section identifier.
+ * \param[in] id_len Length of the section identifier.
+ * \param[in] data Item data.
+ * \param[in] data_len Length of the item data.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_db_set(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key0,
+ const yp_name_t *key1,
+ const uint8_t *id,
+ size_t id_len,
+ const uint8_t *data,
+ size_t data_len
+);
+
+/*!
+ * Unsets the item data in the configuration DB.
+ *
+ * If no data is provided, the whole item is remove.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ * \param[in] key0 Section name.
+ * \param[in] key1 Item name.
+ * \param[in] id Section identifier.
+ * \param[in] id_len Length of the section identifier.
+ * \param[in] data Item data.
+ * \param[in] data_len Length of the item data.
+ * \param[in] delete_key1 Set to unregister the item from the DB.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_db_unset(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key0,
+ const yp_name_t *key1,
+ const uint8_t *id,
+ size_t id_len,
+ const uint8_t *data,
+ size_t data_len,
+ bool delete_key1
+);
+
+/*!
+ * Gets the item data from the configuration DB.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ * \param[in] key0 Section name.
+ * \param[in] key1 Item name.
+ * \param[in] id Section identifier.
+ * \param[in] id_len Length of the section identifier.
+ * \param[out] data Item data.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_db_get(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key0,
+ const yp_name_t *key1,
+ const uint8_t *id,
+ size_t id_len,
+ conf_val_t *data
+);
+
+/*!
+ * Gets a configuration DB section iterator.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ * \param[in] key0 Section name.
+ * \param[out] iter Section iterator.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_db_iter_begin(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const yp_name_t *key0,
+ conf_iter_t *iter
+);
+
+/*!
+ * Moves the section iterator to the next identifier.
+ *
+ * \param[in] conf Configuration.
+ * \param[in,out] iter Section iterator.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_db_iter_next(
+ conf_t *conf,
+ conf_iter_t *iter
+);
+
+/*!
+ * Gets the current section iterator value (identifier).
+ *
+ * \param[in] conf Configuration.
+ * \param[in] iter Section iterator.
+ * \param[out] data Identifier.
+ * \param[out] data_len Length of the identifier.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_db_iter_id(
+ conf_t *conf,
+ conf_iter_t *iter,
+ const uint8_t **data,
+ size_t *data_len
+);
+
+/*!
+ * Deletes the current section iterator value (identifier).
+ *
+ * \param[in] conf Configuration.
+ * \param[in,out] iter Section iterator.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_db_iter_del(
+ conf_t *conf,
+ conf_iter_t *iter
+);
+
+/*!
+ * Deletes the section iterator.
+ *
+ * \param[in] conf Configuration.
+ * \param[in,out] iter Section iterator.
+ */
+void conf_db_iter_finish(
+ conf_t *conf,
+ conf_iter_t *iter
+);
+
+/*!
+ * Dumps the configuration DB in the textual form.
+ *
+ * \note This function is intended for debugging.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] txn Configuration DB transaction.
+ * \param[in] file_name File name to dump to (NULL to dump to stdout).
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_db_raw_dump(
+ conf_t *conf,
+ knot_db_txn_t *txn,
+ const char *file_name
+);
diff --git a/src/knot/conf/confio.c b/src/knot/conf/confio.c
new file mode 100644
index 0000000..817f693
--- /dev/null
+++ b/src/knot/conf/confio.c
@@ -0,0 +1,1612 @@
+/* Copyright (C) 2022 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+
+#include "knot/common/log.h"
+#include "knot/conf/confdb.h"
+#include "knot/conf/confio.h"
+#include "knot/conf/module.h"
+#include "knot/conf/tools.h"
+
+#define FCN(io) (io->fcn != NULL) ? io->fcn(io) : KNOT_EOK;
+
+static void io_reset_val(
+ conf_io_t *io,
+ const yp_item_t *key0,
+ const yp_item_t *key1,
+ const uint8_t *id,
+ size_t id_len,
+ bool id_as_data,
+ conf_val_t *val)
+{
+ io->key0 = key0;
+ io->key1 = key1;
+ io->id = id;
+ io->id_len = id_len;
+ io->id_as_data = id_as_data;
+ io->data.val = val;
+ io->data.bin = NULL;
+}
+
+static void io_reset_bin(
+ conf_io_t *io,
+ const yp_item_t *key0,
+ const yp_item_t *key1,
+ const uint8_t *id,
+ size_t id_len,
+ const uint8_t *bin,
+ size_t bin_len)
+{
+ io_reset_val(io, key0, key1, id, id_len, false, NULL);
+ io->data.bin = bin;
+ io->data.bin_len = bin_len;
+}
+
+int conf_io_begin(
+ bool child)
+{
+ assert(conf() != NULL);
+
+ if (conf()->io.txn != NULL && !child) {
+ return KNOT_TXN_EEXISTS;
+ } else if (conf()->io.txn == NULL && child) {
+ return KNOT_TXN_ENOTEXISTS;
+ }
+
+ knot_db_txn_t *parent = conf()->io.txn;
+ knot_db_txn_t *txn = (parent == NULL) ? conf()->io.txn_stack : parent + 1;
+ if (txn >= conf()->io.txn_stack + CONF_MAX_TXN_DEPTH) {
+ return KNOT_TXN_EEXISTS;
+ }
+
+ if (conf()->filename != NULL && !child) {
+ log_ctl_notice("control, persistent configuration database "
+ "not available");
+ }
+
+ // Start the writing transaction.
+ int ret = knot_db_lmdb_txn_begin(conf()->db, txn, parent, 0);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ conf()->io.txn = txn;
+
+ // Reset master transaction flags.
+ if (!child) {
+ conf()->io.flags = CONF_IO_FACTIVE;
+ if (conf()->io.zones != NULL) {
+ trie_clear(conf()->io.zones);
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+int conf_io_commit(
+ bool child)
+{
+ assert(conf() != NULL);
+
+ if (conf()->io.txn == NULL ||
+ (child && conf()->io.txn == conf()->io.txn_stack)) {
+ return KNOT_TXN_ENOTEXISTS;
+ }
+
+ knot_db_txn_t *txn = child ? conf()->io.txn : conf()->io.txn_stack;
+
+ // Commit the writing transaction.
+ int ret = conf()->api->txn_commit(txn);
+
+ conf()->io.txn = child ? txn - 1 : NULL;
+
+ return ret;
+}
+
+void conf_io_abort(
+ bool child)
+{
+ assert(conf() != NULL);
+
+ if (conf()->io.txn == NULL ||
+ (child && conf()->io.txn == conf()->io.txn_stack)) {
+ return;
+ }
+
+ knot_db_txn_t *txn = child ? conf()->io.txn : conf()->io.txn_stack;
+
+ // Abort the writing transaction.
+ conf()->api->txn_abort(txn);
+ conf()->io.txn = child ? txn - 1 : NULL;
+
+ // Reset master transaction flags.
+ if (!child) {
+ conf()->io.flags = YP_FNONE;
+ if (conf()->io.zones != NULL) {
+ trie_clear(conf()->io.zones);
+ }
+ }
+}
+
+static int list_section(
+ const yp_item_t *items,
+ const yp_item_t **item,
+ conf_io_t *io)
+{
+ for (*item = items; (*item)->name != NULL; (*item)++) {
+ int ret = FCN(io);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+int conf_io_list(
+ const char *key0,
+ const char *key1,
+ const char *id,
+ bool list_schema,
+ bool get_current,
+ conf_io_t *io)
+{
+ if (io == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ assert(conf() != NULL);
+
+ if (conf()->io.txn == NULL && !get_current) {
+ return KNOT_TXN_ENOTEXISTS;
+ }
+
+ // List schema sections by default.
+ if (key0 == NULL) {
+ io_reset_val(io, NULL, NULL, NULL, 0, false, NULL);
+
+ return list_section(conf()->schema, &io->key0, io);
+ }
+
+ yp_check_ctx_t *ctx = yp_schema_check_init(&conf()->schema);
+ if (ctx == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ // Check the input.
+ int ret = yp_schema_check_str(ctx, key0, key1, id, NULL);
+ if (ret != KNOT_EOK) {
+ goto list_error;
+ }
+
+ knot_db_txn_t *txn = get_current ? &conf()->read_txn : conf()->io.txn;
+
+ yp_node_t *node = &ctx->nodes[ctx->current];
+ yp_node_t *parent = node->parent;
+
+ // List group items.
+ if (list_schema && key1 == NULL) {
+ if (node->item->type != YP_TGRP) { // Ignore non-group section.
+ ret = KNOT_EOK;
+ goto list_error;
+ }
+
+ io_reset_val(io, node->item, NULL, NULL, 0, false, NULL);
+
+ ret = list_section(node->item->sub_items, &io->key1, io);
+ // List option item values.
+ } else if (list_schema) {
+ if (node->item->type != YP_TOPT) { // Ignore non-option item.
+ ret = KNOT_EOK;
+ goto list_error;
+ }
+
+ for (const knot_lookup_t *o = node->item->var.o.opts; o->name != NULL; o++) {
+ uint8_t val = o->id;
+ io_reset_bin(io, parent->item, node->item, parent->id,
+ parent->id_len, &val, sizeof(val));
+ ret = FCN(io);
+ if (ret != KNOT_EOK) {
+ goto list_error;
+ }
+ }
+ // List group identifiers.
+ } else if (parent == NULL) {
+ if (node->item->type != YP_TGRP) { // Ignore non-group section.
+ ret = KNOT_EOK;
+ goto list_error;
+ }
+
+ // If key1 != NULL, it's used for a value completion (zone.domain).
+ io_reset_val(io, node->item, NULL, NULL, 0, key1 != NULL, NULL);
+
+ conf_iter_t iter;
+ ret = conf_db_iter_begin(conf(), txn, io->key0->name, &iter);
+ switch (ret) {
+ case KNOT_EOK:
+ break;
+ case KNOT_ENOENT:
+ ret = KNOT_EOK;
+ goto list_error;
+ default:
+ goto list_error;
+ }
+
+ while (ret == KNOT_EOK) {
+ // Set the section identifier.
+ ret = conf_db_iter_id(conf(), &iter, &io->id, &io->id_len);
+ if (ret != KNOT_EOK) {
+ conf_db_iter_finish(conf(), &iter);
+ goto list_error;
+ }
+
+ ret = FCN(io);
+ if (ret != KNOT_EOK) {
+ conf_db_iter_finish(conf(), &iter);
+ goto list_error;
+ }
+
+ ret = conf_db_iter_next(conf(), &iter);
+ }
+ ret = KNOT_EOK;
+ // List item values.
+ } else {
+ io_reset_val(io, parent->item, node->item, parent->id,
+ parent->id_len, false, NULL);
+
+ // Get the item value.
+ conf_val_t data;
+ ret = conf_db_get(conf(), txn, io->key0->name, io->key1->name,
+ io->id, io->id_len, &data);
+ switch (ret) {
+ case KNOT_EOK:
+ break;
+ case KNOT_ENOENT:
+ ret = KNOT_EOK;
+ goto list_error;
+ default:
+ goto list_error;
+ }
+
+ io->data.val = &data;
+
+ ret = FCN(io);
+ if (ret != KNOT_EOK) {
+ goto list_error;
+ }
+ }
+list_error:
+ yp_schema_check_deinit(ctx);
+
+ return ret;
+}
+
+static int diff_item(
+ conf_io_t *io)
+{
+ // Process an identifier item.
+ if ((io->key0->flags & YP_FMULTI) != 0 && io->key0->var.g.id == io->key1) {
+ bool old_id, new_id;
+
+ // Check if a removed identifier.
+ int ret = conf_db_get(conf(), &conf()->read_txn, io->key0->name,
+ NULL, io->id, io->id_len, NULL);
+ switch (ret) {
+ case KNOT_EOK:
+ old_id = true;
+ break;
+ case KNOT_ENOENT:
+ case KNOT_YP_EINVAL_ID:
+ old_id = false;
+ break;
+ default:
+ return ret;
+ }
+
+ // Check if an added identifier.
+ ret = conf_db_get(conf(), conf()->io.txn, io->key0->name, NULL,
+ io->id, io->id_len, NULL);
+ switch (ret) {
+ case KNOT_EOK:
+ new_id = true;
+ break;
+ case KNOT_ENOENT:
+ case KNOT_YP_EINVAL_ID:
+ new_id = false;
+ break;
+ default:
+ return ret;
+ }
+
+ // Check if valid identifier.
+ if (!old_id && !new_id) {
+ return KNOT_YP_EINVAL_ID;
+ }
+
+ if (old_id != new_id) {
+ io->id_as_data = true;
+ io->type = old_id ? OLD : NEW;
+
+ // Process the callback.
+ ret = FCN(io);
+
+ // Reset the modified parameters.
+ io->id_as_data = false;
+ io->type = NONE;
+
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ return KNOT_EOK;
+ }
+
+ conf_val_t old_val, new_val;
+
+ // Get the old item value.
+ conf_db_get(conf(), &conf()->read_txn, io->key0->name, io->key1->name,
+ io->id, io->id_len, &old_val);
+ switch (old_val.code) {
+ case KNOT_EOK:
+ break;
+ case KNOT_ENOENT:
+ case KNOT_YP_EINVAL_ID:
+ break;
+ default:
+ return old_val.code;
+ }
+
+ // Get the new item value.
+ conf_db_get(conf(), conf()->io.txn, io->key0->name, io->key1->name,
+ io->id, io->id_len, &new_val);
+ switch (new_val.code) {
+ case KNOT_EOK:
+ break;
+ case KNOT_ENOENT:
+ case KNOT_YP_EINVAL_ID:
+ if (old_val.code != KNOT_EOK) {
+ return KNOT_EOK;
+ }
+ break;
+ default:
+ return new_val.code;
+ }
+
+ // Process the value difference.
+ if (old_val.code != KNOT_EOK) {
+ io->data.val = &new_val;
+ io->type = NEW;
+ int ret = FCN(io);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ } else if (new_val.code != KNOT_EOK) {
+ io->data.val = &old_val;
+ io->type = OLD;
+ int ret = FCN(io);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ } else if (!conf_val_equal(&old_val, &new_val)) {
+ io->data.val = &old_val;
+ io->type = OLD;
+ int ret = FCN(io);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ io->data.val = &new_val;
+ io->type = NEW;
+ ret = FCN(io);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ // Reset the modified parameters.
+ io->data.val = NULL;
+ io->type = NONE;
+
+ return KNOT_EOK;
+}
+
+static int diff_section(
+ conf_io_t *io)
+{
+ // Get the value for the specified item.
+ if (io->key1 != NULL) {
+ return diff_item(io);
+ }
+
+ // Get the values for all items.
+ for (yp_item_t *i = io->key0->sub_items; i->name != NULL; i++) {
+ io->key1 = i;
+
+ int ret = diff_item(io);
+
+ // Reset the modified parameters.
+ io->key1 = NULL;
+
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+static int diff_iter_section(
+ conf_io_t *io)
+{
+ // First compare the section with the old and common identifiers.
+ conf_iter_t iter;
+ int ret = conf_db_iter_begin(conf(), &conf()->read_txn, io->key0->name,
+ &iter);
+ switch (ret) {
+ case KNOT_EOK:
+ break;
+ case KNOT_ENOENT:
+ // Continue to the second step.
+ ret = KNOT_EOF;
+ break;
+ default:
+ return ret;
+ }
+
+ while (ret == KNOT_EOK) {
+ ret = conf_db_iter_id(conf(), &iter, &io->id, &io->id_len);
+ if (ret != KNOT_EOK) {
+ conf_db_iter_finish(conf(), &iter);
+ return ret;
+ }
+
+ ret = diff_section(io);
+ if (ret != KNOT_EOK) {
+ conf_db_iter_finish(conf(), &iter);
+ return ret;
+ }
+
+ ret = conf_db_iter_next(conf(), &iter);
+ }
+ if (ret != KNOT_EOF) {
+ return ret;
+ }
+
+ // Second compare the section with the new identifiers.
+ ret = conf_db_iter_begin(conf(), conf()->io.txn, io->key0->name, &iter);
+ switch (ret) {
+ case KNOT_EOK:
+ break;
+ case KNOT_ENOENT:
+ return KNOT_EOK;
+ default:
+ return ret;
+ }
+
+ while (ret == KNOT_EOK) {
+ ret = conf_db_iter_id(conf(), &iter, &io->id, &io->id_len);
+ if (ret != KNOT_EOK) {
+ conf_db_iter_finish(conf(), &iter);
+ return ret;
+ }
+
+ // Ignore old and common identifiers.
+ ret = conf_db_get(conf(), &conf()->read_txn, io->key0->name,
+ NULL, io->id, io->id_len, NULL);
+ switch (ret) {
+ case KNOT_EOK:
+ ret = conf_db_iter_next(conf(), &iter);
+ continue;
+ case KNOT_ENOENT:
+ case KNOT_YP_EINVAL_ID:
+ break;
+ default:
+ conf_db_iter_finish(conf(), &iter);
+ return ret;
+ }
+
+ ret = diff_section(io);
+ if (ret != KNOT_EOK) {
+ conf_db_iter_finish(conf(), &iter);
+ return ret;
+ }
+
+ ret = conf_db_iter_next(conf(), &iter);
+ }
+ if (ret != KNOT_EOF) {
+ return ret;
+ }
+
+ return KNOT_EOK;
+}
+
+static int diff_zone_section(
+ conf_io_t *io)
+{
+ assert(io->key0->flags & CONF_IO_FZONE);
+
+ if (conf()->io.zones == NULL) {
+ return KNOT_EOK;
+ }
+
+ trie_it_t *it = trie_it_begin(conf()->io.zones);
+ for (; !trie_it_finished(it); trie_it_next(it)) {
+ io->id = (const uint8_t *)trie_it_key(it, &io->id_len);
+
+ // Get the difference for specific zone.
+ int ret = diff_section(io);
+ if (ret != KNOT_EOK) {
+ trie_it_free(it);
+ return ret;
+ }
+ }
+ trie_it_free(it);
+
+ return KNOT_EOK;
+}
+
+int conf_io_diff(
+ const char *key0,
+ const char *key1,
+ const char *id,
+ conf_io_t *io)
+{
+ if (io == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ assert(conf() != NULL);
+
+ if (conf()->io.txn == NULL) {
+ return KNOT_TXN_ENOTEXISTS;
+ }
+
+ // Compare all sections by default.
+ if (key0 == NULL) {
+ for (yp_item_t *i = conf()->schema; i->name != NULL; i++) {
+ // Skip non-group item.
+ if (i->type != YP_TGRP) {
+ continue;
+ }
+
+ int ret = conf_io_diff(i->name + 1, key1, NULL, io);
+
+ // Reset parameters after each section.
+ io_reset_val(io, NULL, NULL, NULL, 0, false, NULL);
+
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ return KNOT_EOK;
+ }
+
+ yp_check_ctx_t *ctx = yp_schema_check_init(&conf()->schema);
+ if (ctx == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ // Check the input.
+ int ret = yp_schema_check_str(ctx, key0, key1, id, NULL);
+ if (ret != KNOT_EOK) {
+ goto diff_error;
+ }
+
+ yp_node_t *node = &ctx->nodes[ctx->current];
+ yp_node_t *parent = node->parent;
+
+ // Key1 is not a group identifier.
+ if (parent != NULL) {
+ io_reset_val(io, parent->item, node->item, parent->id,
+ parent->id_len, false, NULL);
+ // Key1 is a group identifier.
+ } else if (key1 != NULL && strlen(key1) != 0) {
+ assert(node->item->type == YP_TGRP &&
+ (node->item->flags & YP_FMULTI) != 0);
+
+ io_reset_val(io, node->item, node->item->var.g.id, node->id,
+ node->id_len, true, NULL);
+ // No key1 specified.
+ } else {
+ io_reset_val(io, node->item, NULL, node->id, node->id_len,
+ false, NULL);
+ }
+
+ // Check for a non-group item.
+ if (io->key0->type != YP_TGRP) {
+ ret = KNOT_ENOTSUP;
+ goto diff_error;
+ }
+
+ // Compare the section with all identifiers by default.
+ if ((io->key0->flags & YP_FMULTI) != 0 && io->id_len == 0) {
+ // The zone section has an optimized diff.
+ if (io->key0->flags & CONF_IO_FZONE) {
+ // Full diff by default.
+ if (!(conf()->io.flags & CONF_IO_FACTIVE)) {
+ ret = diff_iter_section(io);
+ // Full diff if all zones changed.
+ } else if (conf()->io.flags & CONF_IO_FDIFF_ZONES) {
+ ret = diff_iter_section(io);
+ // Optimized diff for specific zones.
+ } else {
+ ret = diff_zone_section(io);
+ }
+ } else {
+ ret = diff_iter_section(io);
+ }
+
+ goto diff_error;
+ }
+
+ // Compare the section with a possible identifier.
+ ret = diff_section(io);
+diff_error:
+ yp_schema_check_deinit(ctx);
+
+ return ret;
+}
+
+static int get_section(
+ knot_db_txn_t *txn,
+ conf_io_t *io)
+{
+ conf_val_t data;
+
+ // Get the value for the specified item.
+ if (io->key1 != NULL) {
+ if (!io->id_as_data) {
+ // Get the item value.
+ conf_db_get(conf(), txn, io->key0->name, io->key1->name,
+ io->id, io->id_len, &data);
+ switch (data.code) {
+ case KNOT_EOK:
+ break;
+ case KNOT_ENOENT:
+ return KNOT_EOK;
+ default:
+ return data.code;
+ }
+
+ io->data.val = &data;
+ }
+
+ // Process the callback.
+ int ret = FCN(io);
+
+ // Reset the modified parameters.
+ io->data.val = NULL;
+
+ return ret;
+ }
+
+ // Get the values for all section items by default.
+ for (yp_item_t *i = io->key0->sub_items; i->name != NULL; i++) {
+ // Process the (first) identifier item.
+ if ((io->key0->flags & YP_FMULTI) != 0 && io->key0->var.g.id == i) {
+ // Check if existing identifier.
+ conf_db_get(conf(), txn, io->key0->name, NULL, io->id,
+ io->id_len, &data);
+ switch (data.code) {
+ case KNOT_EOK:
+ break;
+ case KNOT_ENOENT:
+ continue;
+ default:
+ return data.code;
+ }
+
+ io->key1 = i;
+ io->id_as_data = true;
+
+ // Process the callback.
+ int ret = FCN(io);
+
+ // Reset the modified parameters.
+ io->key1 = NULL;
+ io->id_as_data = false;
+
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ continue;
+ }
+
+ // Get the item value.
+ conf_db_get(conf(), txn, io->key0->name, i->name, io->id,
+ io->id_len, &data);
+ switch (data.code) {
+ case KNOT_EOK:
+ break;
+ case KNOT_ENOENT:
+ continue;
+ default:
+ return data.code;
+ }
+
+ io->key1 = i;
+ io->data.val = &data;
+
+ // Process the callback.
+ int ret = FCN(io);
+
+ // Reset the modified parameters.
+ io->key1 = NULL;
+ io->data.val = NULL;
+
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+int conf_io_get(
+ const char *key0,
+ const char *key1,
+ const char *id,
+ bool get_current,
+ conf_io_t *io)
+{
+ if (io == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ assert(conf() != NULL);
+
+ if (conf()->io.txn == NULL && !get_current) {
+ return KNOT_TXN_ENOTEXISTS;
+ }
+
+ // List all sections by default.
+ if (key0 == NULL) {
+ for (yp_item_t *i = conf()->schema; i->name != NULL; i++) {
+ // Skip non-group item.
+ if (i->type != YP_TGRP) {
+ continue;
+ }
+
+ int ret = conf_io_get(i->name + 1, key1, NULL,
+ get_current, io);
+ // Reset parameters after each section.
+ io_reset_val(io, NULL, NULL, NULL, 0, false, NULL);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ return KNOT_EOK;
+ }
+
+ yp_check_ctx_t *ctx = yp_schema_check_init(&conf()->schema);
+ if (ctx == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ // Check the input.
+ int ret = yp_schema_check_str(ctx, key0, key1, id, NULL);
+ if (ret != KNOT_EOK) {
+ goto get_error;
+ }
+
+ yp_node_t *node = &ctx->nodes[ctx->current];
+ yp_node_t *parent = node->parent;
+
+ // Key1 is not a group identifier.
+ if (parent != NULL) {
+ io_reset_val(io, parent->item, node->item, parent->id,
+ parent->id_len, false, NULL);
+ // Key1 is a group identifier.
+ } else if (key1 != NULL && strlen(key1) != 0) {
+ assert(node->item->type == YP_TGRP &&
+ (node->item->flags & YP_FMULTI) != 0);
+
+ io_reset_val(io, node->item, node->item->var.g.id, node->id,
+ node->id_len, true, NULL);
+ // No key1 specified.
+ } else {
+ io_reset_val(io, node->item, NULL, node->id, node->id_len, false,
+ NULL);
+ }
+
+ knot_db_txn_t *txn = get_current ? &conf()->read_txn : conf()->io.txn;
+
+ // Check for a non-group item.
+ if (io->key0->type != YP_TGRP) {
+ ret = KNOT_ENOTSUP;
+ goto get_error;
+ }
+
+ // List the section with all identifiers by default.
+ if ((io->key0->flags & YP_FMULTI) != 0 && io->id_len == 0) {
+ conf_iter_t iter;
+ ret = conf_db_iter_begin(conf(), txn, io->key0->name, &iter);
+ switch (ret) {
+ case KNOT_EOK:
+ break;
+ case KNOT_ENOENT:
+ ret = KNOT_EOK;
+ goto get_error;
+ default:
+ goto get_error;
+ }
+
+ while (ret == KNOT_EOK) {
+ // Set the section identifier.
+ ret = conf_db_iter_id(conf(), &iter, &io->id, &io->id_len);
+ if (ret != KNOT_EOK) {
+ conf_db_iter_finish(conf(), &iter);
+ goto get_error;
+ }
+
+ ret = get_section(txn, io);
+ if (ret != KNOT_EOK) {
+ conf_db_iter_finish(conf(), &iter);
+ goto get_error;
+ }
+
+ ret = conf_db_iter_next(conf(), &iter);
+ }
+
+ ret = KNOT_EOK;
+ goto get_error;
+ }
+
+ // List the section with a possible identifier.
+ ret = get_section(txn, io);
+get_error:
+ yp_schema_check_deinit(ctx);
+
+ return ret;
+}
+
+static void upd_changes(
+ const conf_io_t *io,
+ conf_io_type_t type,
+ yp_flag_t flags,
+ bool any_id)
+{
+ // Update common flags.
+ conf()->io.flags |= flags;
+
+ // Return if not important change.
+ if (type == CONF_IO_TNONE) {
+ return;
+ }
+
+ // Update reference item.
+ if (flags & CONF_IO_FREF) {
+ // Expected an identifier, which cannot be changed.
+ assert(type != CONF_IO_TCHANGE);
+
+ // Re-check and reload all zones if a reference has been removed.
+ if (type == CONF_IO_TUNSET) {
+ conf()->io.flags |= CONF_IO_FCHECK_ZONES | CONF_IO_FRLD_ZONES;
+ }
+ return;
+ // Return if no specific zone operation.
+ } else if (!(flags & CONF_IO_FZONE)) {
+ return;
+ }
+
+ // Don't process each zone individually, process all instead.
+ if (any_id) {
+ // Diff all zone changes.
+ conf()->io.flags |= CONF_IO_FCHECK_ZONES | CONF_IO_FDIFF_ZONES;
+
+ // Reload just with important changes.
+ if (flags & CONF_IO_FRLD_ZONE) {
+ conf()->io.flags |= CONF_IO_FRLD_ZONES;
+ }
+ return;
+ }
+
+ // Prepare zone changes storage if it doesn't exist.
+ trie_t *zones = conf()->io.zones;
+ if (zones == NULL) {
+ zones = trie_create(NULL);
+ if (zones == NULL) {
+ return;
+ }
+ conf()->io.zones = zones;
+ }
+
+ // Get zone status or create new.
+ trie_val_t *val = trie_get_ins(zones, io->id, io->id_len);
+ conf_io_type_t *current = (conf_io_type_t *)val;
+
+ switch (type) {
+ case CONF_IO_TSET:
+ // Revert remove zone, but don't remove (probably changed).
+ if (*current & CONF_IO_TUNSET) {
+ *current &= ~CONF_IO_TUNSET;
+ } else {
+ // Must be a new zone.
+ assert(*current == CONF_IO_TNONE);
+ // Mark added zone.
+ *current = type;
+ }
+ break;
+ case CONF_IO_TUNSET:
+ if (*current & CONF_IO_TSET) {
+ // Remove inserted zone -> no change.
+ trie_del(zones, io->id, io->id_len, NULL);
+ } else {
+ // Remove existing zone.
+ *current |= type;
+ }
+ break;
+ case CONF_IO_TCHANGE:
+ *current |= type;
+ // Mark zone to reload if required.
+ if (flags & CONF_IO_FRLD_ZONE) {
+ *current |= CONF_IO_TRELOAD;
+ }
+ break;
+ case CONF_IO_TRELOAD:
+ default:
+ assert(0);
+ }
+}
+
+static int set_item(
+ conf_io_t *io)
+{
+ int ret = conf_db_set(conf(), conf()->io.txn, io->key0->name,
+ (io->key1 != NULL) ? io->key1->name : NULL,
+ io->id, io->id_len, io->data.bin, io->data.bin_len);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ // Postpone group callbacks to config check.
+ if (io->key0->type == YP_TGRP && io->id_len == 0) {
+ return KNOT_EOK;
+ }
+
+ knotd_conf_check_extra_t extra = {
+ .conf = conf(),
+ .txn = conf()->io.txn
+ };
+ knotd_conf_check_args_t args = {
+ .item = (io->key1 != NULL) ? io->key1 :
+ ((io->id_len == 0) ? io->key0 : io->key0->var.g.id),
+ .id = io->id,
+ .id_len = io->id_len,
+ .data = io->data.bin,
+ .data_len = io->data.bin_len,
+ .extra = &extra
+ };
+
+ // Call the item callbacks (include, item check, mod-id check).
+ ret = conf_exec_callbacks(&args);
+ if (ret != KNOT_EOK) {
+ CONF_LOG(LOG_DEBUG, "item '%s' (%s)", args.item->name + 1,
+ args.err_str != NULL ? args.err_str : knot_strerror(ret));
+ }
+
+ return ret;
+}
+
+int conf_io_set(
+ const char *key0,
+ const char *key1,
+ const char *id,
+ const char *data)
+{
+ assert(conf() != NULL);
+
+ if (conf()->io.txn == NULL) {
+ return KNOT_TXN_ENOTEXISTS;
+ }
+
+ // At least key0 must be specified.
+ if (key0 == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ yp_check_ctx_t *ctx = yp_schema_check_init(&conf()->schema);
+ if (ctx == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ // Check the input.
+ int ret = yp_schema_check_str(ctx, key0, key1, id, data);
+ if (ret != KNOT_EOK) {
+ goto set_error;
+ }
+
+ yp_node_t *node = &ctx->nodes[ctx->current];
+ yp_node_t *parent = node->parent;
+
+ yp_flag_t upd_flags = node->item->flags;
+ conf_io_type_t upd_type = CONF_IO_TNONE;
+
+ conf_io_t io = { NULL };
+
+ // Key1 is not a group identifier.
+ if (parent != NULL) {
+ if (node->data_len == 0) {
+ ret = KNOT_YP_ENODATA;
+ goto set_error;
+ }
+ upd_type = CONF_IO_TCHANGE;
+ upd_flags |= parent->item->flags;
+ io_reset_bin(&io, parent->item, node->item, parent->id,
+ parent->id_len, node->data, node->data_len);
+ // A group identifier or whole group.
+ } else if (node->item->type == YP_TGRP) {
+ upd_type = CONF_IO_TSET;
+ if ((node->item->flags & YP_FMULTI) != 0) {
+ if (node->id_len == 0) {
+ ret = KNOT_YP_ENOID;
+ goto set_error;
+ }
+ upd_flags |= node->item->var.g.id->flags;
+ } else {
+ ret = KNOT_ENOTSUP;
+ goto set_error;
+ }
+ assert(node->data_len == 0);
+ io_reset_bin(&io, node->item, NULL, node->id, node->id_len,
+ NULL, 0);
+ // A non-group item with data (include).
+ } else if (node->data_len > 0) {
+ io_reset_bin(&io, node->item, NULL, NULL, 0, node->data,
+ node->data_len);
+ } else {
+ ret = KNOT_YP_ENODATA;
+ goto set_error;
+ }
+
+ // Set the item for all identifiers by default.
+ if (io.key0->type == YP_TGRP && io.key1 != NULL &&
+ (io.key0->flags & YP_FMULTI) != 0 && io.id_len == 0) {
+ conf_iter_t iter;
+ ret = conf_db_iter_begin(conf(), conf()->io.txn, io.key0->name,
+ &iter);
+ switch (ret) {
+ case KNOT_EOK:
+ break;
+ case KNOT_ENOENT:
+ ret = KNOT_EOK;
+ goto set_error;
+ default:
+ goto set_error;
+ }
+
+ uint8_t copied_id[YP_MAX_ID_LEN];
+ io.id = copied_id;
+ while (ret == KNOT_EOK) {
+ // Get the identifier and copy it because of next DB update.
+ const uint8_t *tmp_id;
+ ret = conf_db_iter_id(conf(), &iter, &tmp_id, &io.id_len);
+ if (ret != KNOT_EOK) {
+ conf_db_iter_finish(conf(), &iter);
+ goto set_error;
+ }
+ memcpy(copied_id, tmp_id, io.id_len);
+
+ // Set the data.
+ ret = set_item(&io);
+ if (ret != KNOT_EOK) {
+ conf_db_iter_finish(conf(), &iter);
+ goto set_error;
+ }
+
+ ret = conf_db_iter_next(conf(), &iter);
+ }
+ if (ret != KNOT_EOF) {
+ goto set_error;
+ }
+
+ upd_changes(&io, upd_type, upd_flags, true);
+
+ ret = KNOT_EOK;
+ goto set_error;
+ }
+
+ // Set the item with a possible identifier.
+ ret = set_item(&io);
+
+ if (ret == KNOT_EOK) {
+ upd_changes(&io, upd_type, upd_flags, false);
+ }
+set_error:
+ yp_schema_check_deinit(ctx);
+
+ return ret;
+}
+
+static int unset_section_data(
+ conf_io_t *io)
+{
+ // Unset the value for the specified item.
+ if (io->key1 != NULL) {
+ return conf_db_unset(conf(), conf()->io.txn, io->key0->name,
+ io->key1->name, io->id, io->id_len,
+ io->data.bin, io->data.bin_len, false);
+ }
+
+ // Unset the whole section by default.
+ for (yp_item_t *i = io->key0->sub_items; i->name != NULL; i++) {
+ // Skip the identifier item.
+ if ((io->key0->flags & YP_FMULTI) != 0 && io->key0->var.g.id == i) {
+ continue;
+ }
+
+ int ret = conf_db_unset(conf(), conf()->io.txn, io->key0->name,
+ i->name, io->id, io->id_len, io->data.bin,
+ io->data.bin_len, false);
+ switch (ret) {
+ case KNOT_EOK:
+ case KNOT_ENOENT:
+ continue;
+ default:
+ return ret;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+static int unset_section(
+ const yp_item_t *key0)
+{
+ // Unset the section items.
+ for (yp_item_t *i = key0->sub_items; i->name != NULL; i++) {
+ // Skip the identifier item.
+ if ((key0->flags & YP_FMULTI) != 0 && key0->var.g.id == i) {
+ continue;
+ }
+
+ int ret = conf_db_unset(conf(), conf()->io.txn, key0->name,
+ i->name, NULL, 0, NULL, 0, true);
+ switch (ret) {
+ case KNOT_EOK:
+ case KNOT_ENOENT:
+ continue;
+ default:
+ return ret;
+ }
+ }
+
+ // Unset the section.
+ int ret = conf_db_unset(conf(), conf()->io.txn, key0->name, NULL, NULL,
+ 0, NULL, 0, false);
+ switch (ret) {
+ case KNOT_EOK:
+ case KNOT_ENOENT:
+ return KNOT_EOK;
+ default:
+ return ret;
+ }
+}
+
+int conf_io_unset(
+ const char *key0,
+ const char *key1,
+ const char *id,
+ const char *data)
+{
+ assert(conf() != NULL);
+
+ if (conf()->io.txn == NULL) {
+ return KNOT_TXN_ENOTEXISTS;
+ }
+
+ // Unset all sections by default.
+ if (key0 == NULL) {
+ for (yp_item_t *i = conf()->schema; i->name != NULL; i++) {
+ // Skip non-group item.
+ if (i->type != YP_TGRP) {
+ continue;
+ }
+
+ int ret = conf_io_unset(i->name + 1, key1, NULL, NULL);
+ switch (ret) {
+ case KNOT_EOK:
+ case KNOT_ENOENT:
+ break;
+ default:
+ return ret;
+ }
+ }
+
+ return KNOT_EOK;
+ }
+
+ yp_check_ctx_t *ctx = yp_schema_check_init(&conf()->schema);
+ if (ctx == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ // Check the input.
+ int ret = yp_schema_check_str(ctx, key0, key1, id, data);
+ if (ret != KNOT_EOK) {
+ goto unset_error;
+ }
+
+ yp_node_t *node = &ctx->nodes[ctx->current];
+ yp_node_t *parent = node->parent;
+
+ yp_flag_t upd_flags = node->item->flags;
+ conf_io_type_t upd_type = CONF_IO_TNONE;
+
+ conf_io_t io = { NULL };
+
+ // Key1 is not a group identifier.
+ if (parent != NULL) {
+ upd_type = CONF_IO_TCHANGE;
+ upd_flags |= parent->item->flags;
+ io_reset_bin(&io, parent->item, node->item, parent->id,
+ parent->id_len, node->data, node->data_len);
+ // A group identifier or whole group.
+ } else if (node->item->type == YP_TGRP) {
+ upd_type = CONF_IO_TUNSET;
+ if ((node->item->flags & YP_FMULTI) != 0) {
+ upd_flags |= node->item->var.g.id->flags;
+ }
+ assert(node->data_len == 0);
+ io_reset_bin(&io, node->item, NULL, node->id, node->id_len,
+ NULL, 0);
+ // A non-group item (include).
+ } else {
+ ret = KNOT_ENOTSUP;
+ goto unset_error;
+ }
+
+ // Unset the section with all identifiers by default.
+ if ((io.key0->flags & YP_FMULTI) != 0 && io.id_len == 0) {
+ conf_iter_t iter;
+ ret = conf_db_iter_begin(conf(), conf()->io.txn, io.key0->name,
+ &iter);
+ switch (ret) {
+ case KNOT_EOK:
+ break;
+ case KNOT_ENOENT:
+ ret = KNOT_EOK;
+ goto unset_error;
+ default:
+ goto unset_error;
+ }
+
+ uint8_t copied_id[YP_MAX_ID_LEN];
+ io.id = copied_id;
+ while (ret == KNOT_EOK) {
+ // Get the identifier and copy it because of next DB update.
+ const uint8_t *tmp_id;
+ ret = conf_db_iter_id(conf(), &iter, &tmp_id, &io.id_len);
+ if (ret != KNOT_EOK) {
+ conf_db_iter_finish(conf(), &iter);
+ goto unset_error;
+ }
+ memcpy(copied_id, tmp_id, io.id_len);
+
+ // Unset the section data.
+ ret = unset_section_data(&io);
+ switch (ret) {
+ case KNOT_EOK:
+ case KNOT_ENOENT:
+ break;
+ default:
+ conf_db_iter_finish(conf(), &iter);
+ goto unset_error;
+ }
+
+ ret = conf_db_iter_next(conf(), &iter);
+ }
+ if (ret != KNOT_EOF) {
+ goto unset_error;
+ }
+
+ if (io.key1 == NULL) {
+ // Unset all identifiers.
+ ret = conf_db_iter_begin(conf(), conf()->io.txn,
+ io.key0->name, &iter);
+ switch (ret) {
+ case KNOT_EOK:
+ break;
+ case KNOT_ENOENT:
+ ret = KNOT_EOK;
+ goto unset_error;
+ default:
+ goto unset_error;
+ }
+
+ while (ret == KNOT_EOK) {
+ ret = conf_db_iter_del(conf(), &iter);
+ if (ret != KNOT_EOK) {
+ conf_db_iter_finish(conf(), &iter);
+ goto unset_error;
+ }
+
+ ret = conf_db_iter_next(conf(), &iter);
+ }
+ if (ret != KNOT_EOF) {
+ goto unset_error;
+ }
+
+ // Unset the section.
+ ret = unset_section(io.key0);
+ if (ret != KNOT_EOK) {
+ goto unset_error;
+ }
+ }
+
+ upd_changes(&io, upd_type, upd_flags, true);
+
+ ret = KNOT_EOK;
+ goto unset_error;
+ }
+
+ // Unset the section data.
+ ret = unset_section_data(&io);
+ if (ret != KNOT_EOK) {
+ goto unset_error;
+ }
+
+ if (io.key1 == NULL) {
+ // Unset the identifier.
+ if (io.id_len != 0) {
+ ret = conf_db_unset(conf(), conf()->io.txn, io.key0->name,
+ NULL, io.id, io.id_len, NULL, 0, false);
+ if (ret != KNOT_EOK) {
+ goto unset_error;
+ }
+ // Unset the section.
+ } else {
+ ret = unset_section(io.key0);
+ if (ret != KNOT_EOK) {
+ goto unset_error;
+ }
+ }
+ }
+
+ if (ret == KNOT_EOK) {
+ upd_changes(&io, upd_type, upd_flags, false);
+ }
+unset_error:
+ yp_schema_check_deinit(ctx);
+
+ return ret;
+}
+
+static int check_section(
+ const yp_item_t *group,
+ const uint8_t *id,
+ size_t id_len,
+ conf_io_t *io)
+{
+ knotd_conf_check_extra_t extra = {
+ .conf = conf(),
+ .txn = conf()->io.txn,
+ .check = true
+ };
+ knotd_conf_check_args_t args = {
+ .id = id,
+ .id_len = id_len,
+ .extra = &extra
+ };
+
+ bool non_empty = false;
+
+ conf_val_t bin; // Must be in the scope of the error processing.
+ for (yp_item_t *item = group->sub_items; item->name != NULL; item++) {
+ args.item = item;
+
+ // Check the identifier.
+ if ((group->flags & YP_FMULTI) != 0 && group->var.g.id == item) {
+ io->error.code = conf_exec_callbacks(&args);
+ if (io->error.code != KNOT_EOK) {
+ io_reset_val(io, group, item, NULL, 0, false, NULL);
+ goto check_section_error;
+ }
+ continue;
+ }
+
+ // Get the item value.
+ conf_db_get(conf(), conf()->io.txn, group->name, item->name, id,
+ id_len, &bin);
+ if (bin.code == KNOT_ENOENT) {
+ continue;
+ } else if (bin.code != KNOT_EOK) {
+ return bin.code;
+ }
+
+ non_empty = true;
+
+ // Check the item value(s).
+ size_t values = conf_val_count(&bin);
+ for (size_t i = 1; i <= values; i++) {
+ conf_val(&bin);
+ args.data = bin.data;
+ args.data_len = bin.len;
+
+ io->error.code = conf_exec_callbacks(&args);
+ if (io->error.code != KNOT_EOK) {
+ io_reset_val(io, group, item, id, id_len, false,
+ &bin);
+ io->data.index = i;
+ goto check_section_error;
+ }
+
+ if (values > 1) {
+ conf_val_next(&bin);
+ }
+ }
+ }
+
+ // Check the whole section if not empty.
+ if (id != NULL || non_empty) {
+ args.item = group;
+ args.data = NULL;
+ args.data_len = 0;
+
+ io->error.code = conf_exec_callbacks(&args);
+ if (io->error.code != KNOT_EOK) {
+ io_reset_val(io, group, NULL, id, id_len, false, NULL);
+ goto check_section_error;
+ }
+ }
+
+ return KNOT_EOK;
+
+check_section_error:
+ io->error.str = args.err_str;
+ int ret = FCN(io);
+ if (ret == KNOT_EOK) {
+ return io->error.code;
+ }
+ return ret;
+}
+
+static int check_iter_section(
+ const yp_item_t *item,
+ conf_io_t *io)
+{
+ // Iterate over all identifiers.
+ conf_iter_t iter;
+ int ret = conf_db_iter_begin(conf(), conf()->io.txn, item->name, &iter);
+ switch (ret) {
+ case KNOT_EOK:
+ break;
+ case KNOT_ENOENT:
+ return KNOT_EOK;
+ default:
+ return ret;
+ }
+
+ while (ret == KNOT_EOK) {
+ size_t id_len;
+ const uint8_t *id;
+ ret = conf_db_iter_id(conf(), &iter, &id, &id_len);
+ if (ret != KNOT_EOK) {
+ conf_db_iter_finish(conf(), &iter);
+ return ret;
+ }
+
+ // Check specific section item.
+ ret = check_section(item, id, id_len, io);
+ if (ret != KNOT_EOK) {
+ conf_db_iter_finish(conf(), &iter);
+ return ret;
+ }
+
+ ret = conf_db_iter_next(conf(), &iter);
+ }
+ if (ret != KNOT_EOF) {
+ return ret;
+ }
+
+ return KNOT_EOK;
+}
+
+static int check_zone_section(
+ const yp_item_t *item,
+ conf_io_t *io)
+{
+ assert(item->flags & CONF_IO_FZONE);
+
+ if (conf()->io.zones == NULL) {
+ return KNOT_EOK;
+ }
+
+ trie_it_t *it = trie_it_begin(conf()->io.zones);
+ for (; !trie_it_finished(it); trie_it_next(it)) {
+ size_t id_len;
+ const uint8_t *id = (const uint8_t *)trie_it_key(it, &id_len);
+
+ conf_io_type_t type = conf_io_trie_val(it);
+ if (type == CONF_IO_TUNSET) {
+ // Nothing to check.
+ continue;
+ }
+
+ // Check specific zone.
+ int ret = check_section(item, id, id_len, io);
+ if (ret != KNOT_EOK) {
+ trie_it_free(it);
+ return ret;
+ }
+ }
+ trie_it_free(it);
+
+ return KNOT_EOK;
+}
+
+int conf_io_check(
+ conf_io_t *io)
+{
+ if (io == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ assert(conf() != NULL);
+
+ if (conf()->io.txn == NULL) {
+ return KNOT_TXN_ENOTEXISTS;
+ }
+
+ int ret;
+
+ // Iterate over the schema.
+ for (yp_item_t *item = conf()->schema; item->name != NULL; item++) {
+ // Skip non-group items (include).
+ if (item->type != YP_TGRP) {
+ continue;
+ }
+
+ // Check simple group without identifiers.
+ if ((item->flags & YP_FMULTI) == 0) {
+ ret = check_section(item, NULL, 0, io);
+ if (ret != KNOT_EOK) {
+ goto check_error;
+ }
+ continue;
+ }
+
+ // The zone section has an optimized check.
+ if (item->flags & CONF_IO_FZONE) {
+ // Full check by default.
+ if (!(conf()->io.flags & CONF_IO_FACTIVE)) {
+ ret = check_iter_section(item, io);
+ // Full check if all zones changed.
+ } else if (conf()->io.flags & CONF_IO_FCHECK_ZONES) {
+ ret = check_iter_section(item, io);
+ // Optimized check for specific zones.
+ } else {
+ ret = check_zone_section(item, io);
+ }
+ } else {
+ ret = check_iter_section(item, io);
+ }
+ if (ret != KNOT_EOK) {
+ goto check_error;
+ }
+ }
+
+ ret = KNOT_EOK;
+check_error:
+ conf_mod_load_purge(conf(), true);
+
+ return ret;
+}
diff --git a/src/knot/conf/confio.h b/src/knot/conf/confio.h
new file mode 100644
index 0000000..be08e97
--- /dev/null
+++ b/src/knot/conf/confio.h
@@ -0,0 +1,231 @@
+/* Copyright (C) 2022 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 <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "knot/conf/conf.h"
+
+/*! Configuration schema additional flags for dynamic changes. */
+#define CONF_IO_FACTIVE YP_FUSR1 /*!< Active confio transaction indicator. */
+#define CONF_IO_FZONE YP_FUSR2 /*!< Zone section indicator. */
+#define CONF_IO_FREF YP_FUSR3 /*!< Possibly referenced id from a zone. */
+#define CONF_IO_FDIFF_ZONES YP_FUSR4 /*!< All zones config has changed. */
+#define CONF_IO_FCHECK_ZONES YP_FUSR5 /*!< All zones config needs to check. */
+#define CONF_IO_FRLD_SRV YP_FUSR6 /*!< Reload server. */
+#define CONF_IO_FRLD_LOG YP_FUSR7 /*!< Reload logging. */
+#define CONF_IO_FRLD_MOD YP_FUSR8 /*!< Reload global modules. */
+#define CONF_IO_FRLD_ZONE YP_FUSR9 /*!< Reload a specific zone. */
+#define CONF_IO_FRLD_ZONES YP_FUSR10 /*!< Reload all zones. */
+#define CONF_IO_FRLD_ALL (CONF_IO_FRLD_SRV | CONF_IO_FRLD_LOG | \
+ CONF_IO_FRLD_MOD | CONF_IO_FRLD_ZONES)
+
+/*! Zone configuration change type. */
+typedef enum {
+ CONF_IO_TNONE = 0, /*!< Unspecified. */
+ CONF_IO_TSET = 1 << 0, /*!< Zone added. */
+ CONF_IO_TUNSET = 1 << 1, /*!< Zone removed. */
+ CONF_IO_TCHANGE = 1 << 2, /*!< Zone has changed configuration. */
+ CONF_IO_TRELOAD = 1 << 3, /*!< Zone must be reloaded. */
+} conf_io_type_t;
+
+/*! Configuration interface output. */
+typedef struct conf_io conf_io_t;
+struct conf_io {
+ /*! Section. */
+ const yp_item_t *key0;
+ /*! Section item. */
+ const yp_item_t *key1;
+ /*! Section identifier. */
+ const uint8_t *id;
+ /*! Section identifier length. */
+ size_t id_len;
+ /*! Consider item identifier as item data. */
+ bool id_as_data;
+
+ enum {
+ /*! Default item state. */
+ NONE,
+ /*! New item indicator. */
+ NEW,
+ /*! Old item indicator. */
+ OLD
+ } type;
+
+ struct {
+ /*! Section item data (NULL if not used). */
+ conf_val_t *val;
+ /*! Index of data value to format (counted from 1, 0 means all). */
+ size_t index;
+ /*! Binary data value (NULL if not used). */
+ const uint8_t *bin;
+ /*! Length of the binary data value. */
+ size_t bin_len;
+ } data;
+
+ struct {
+ /*! Edit operation return code. */
+ int code;
+ /*! Edit operation return error message. */
+ const char *str;
+ } error;
+
+ /*! Optional processing callback. */
+ int (*fcn)(conf_io_t *);
+ /*! Miscellaneous data useful for the callback. */
+ void *misc;
+};
+
+inline static conf_io_type_t conf_io_trie_val(trie_it_t *it)
+{
+ return (conf_io_type_t)(uintptr_t)(*trie_it_val(it));
+}
+
+/*!
+ * Starts new writing transaction.
+ *
+ * \param[in] child Nested transaction indicator.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_io_begin(
+ bool child
+);
+
+/*!
+ * Commits the current writing transaction.
+ *
+ * \note Remember to call conf_refresh to publish the changes into the common
+ * configuration.
+ *
+ * \param[in] child Nested transaction indicator.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_io_commit(
+ bool child
+);
+
+/*!
+ * Aborts the current writing transaction.
+ *
+ * \param[in] child Nested transaction indicator.
+ */
+void conf_io_abort(
+ bool child
+);
+
+/*!
+ * Gets the configuration sections list or section items list.
+ *
+ * \param[in] key0 Section name (NULL to get section list).
+ * \param[in] key1 Item name (non-NULL to get value list).
+ * \param[in] id Section identifier name if needed for value list.
+ * \param[in] list_schema List schema items or option values.
+ * \param[in] get_current The current configuration or the active transaction switch.
+ * \param[out] io Operation output.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_io_list(
+ const char *key0,
+ const char *key1,
+ const char *id,
+ bool list_schema,
+ bool get_current,
+ conf_io_t *io
+);
+
+/*!
+ * Gets the configuration difference between the current configuration and
+ * the active transaction.
+ *
+ * \param[in] key0 Section name (NULL to diff all sections).
+ * \param[in] key1 Item name (NULL to diff all section items).
+ * \param[in] id Section identifier name (NULL to consider all section identifiers).
+ * \param[out] io Operation output.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_io_diff(
+ const char *key0,
+ const char *key1,
+ const char *id,
+ conf_io_t *io
+);
+
+/*!
+ * Gets the configuration item(s) value(s).
+ *
+ * \param[in] key0 Section name (NULL to get all sections).
+ * \param[in] key1 Item name (NULL to get all section items).
+ * \param[in] id Section identifier name (NULL to consider all section identifiers).
+ * \param[in] get_current The current configuration or the active transaction switch.
+ * \param[out] io Operation output.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_io_get(
+ const char *key0,
+ const char *key1,
+ const char *id,
+ bool get_current,
+ conf_io_t *io
+);
+
+/*!
+ * Sets the configuration item(s) value.
+ *
+ * \param[in] key0 Section name.
+ * \param[in] key1 Item name (NULL to add identifier only).
+ * \param[in] id Section identifier name (NULL to consider all section identifiers).
+ * \param[in] data Item data to set/add.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_io_set(
+ const char *key0,
+ const char *key1,
+ const char *id,
+ const char *data
+);
+
+/*!
+ * Unsets the configuration item(s) value(s).
+ *
+ * \param[in] key0 Section name (NULL to unset all sections).
+ * \param[in] key1 Item name (NULL to unset the whole section).
+ * \param[in] id Section identifier name (NULL to consider all section identifiers).
+ * \param[in] data Item data (NULL to unset all data).
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_io_unset(
+ const char *key0,
+ const char *key1,
+ const char *id,
+ const char *data
+);
+
+/*!
+ * Checks the configuration database semantics in the current writing transaction.
+ *
+ * \param[out] io Operation output.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_io_check(
+ conf_io_t *io
+);
diff --git a/src/knot/conf/migration.c b/src/knot/conf/migration.c
new file mode 100644
index 0000000..7e881b6
--- /dev/null
+++ b/src/knot/conf/migration.c
@@ -0,0 +1,81 @@
+/* Copyright (C) 2017 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "knot/common/log.h"
+#include "knot/conf/migration.h"
+#include "knot/conf/confdb.h"
+
+/*
+static void try_unset(conf_t *conf, knot_db_txn_t *txn, yp_name_t *key0, yp_name_t *key1)
+{
+ int ret = conf_db_unset(conf, txn, key0, key1, NULL, 0, NULL, 0, true);
+ if (ret != KNOT_EOK && ret != KNOT_ENOENT) {
+ log_warning("conf, migration, failed to unset '%s%s%s' (%s)",
+ key0 + 1,
+ (key1 != NULL) ? "/" : "",
+ (key1 != NULL) ? key1 + 1 : "",
+ knot_strerror(ret));
+ }
+}
+
+#define check_set(conf, txn, key0, key1, id, id_len, data, data_len) \
+ ret = conf_db_set(conf, txn, key0, key1, id, id_len, data, data_len); \
+ if (ret != KNOT_EOK && ret != KNOT_CONF_EREDEFINE) { \
+ log_error("conf, migration, failed to set '%s%s%s' (%s)", \
+ key0 + 1, \
+ (key1 != NULL) ? "/" : "", \
+ (key1 != NULL) ? key1 + 1 : "", \
+ knot_strerror(ret)); \
+ return ret; \
+ }
+
+static int migrate_(
+ conf_t *conf,
+ knot_db_txn_t *txn)
+{
+ return KNOT_EOK;
+}
+*/
+
+int conf_migrate(
+ conf_t *conf)
+{
+ return KNOT_EOK;
+ /*
+ if (conf == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ knot_db_txn_t txn;
+ int ret = conf->api->txn_begin(conf->db, &txn, 0);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ ret = migrate_(conf, &txn);
+ if (ret != KNOT_EOK) {
+ conf->api->txn_abort(&txn);
+ return ret;
+ }
+
+ ret = conf->api->txn_commit(&txn);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ return conf_refresh_txn(conf);
+ */
+}
diff --git a/src/knot/conf/migration.h b/src/knot/conf/migration.h
new file mode 100644
index 0000000..f8d0793
--- /dev/null
+++ b/src/knot/conf/migration.h
@@ -0,0 +1,30 @@
+/* Copyright (C) 2017 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 <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "knot/conf/base.h"
+
+/*!
+ * Migrates from an old configuration schema.
+ *
+ * \param[in] conf Configuration.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_migrate(
+ conf_t *conf
+);
diff --git a/src/knot/conf/module.c b/src/knot/conf/module.c
new file mode 100644
index 0000000..d5d9642
--- /dev/null
+++ b/src/knot/conf/module.c
@@ -0,0 +1,509 @@
+/* Copyright (C) 2023 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <glob.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <urcu.h>
+
+#include "knot/conf/conf.h"
+#include "knot/conf/confio.h"
+#include "knot/conf/module.h"
+#include "knot/common/log.h"
+#include "knot/modules/static_modules.h"
+#include "knot/nameserver/query_module.h"
+#include "contrib/openbsd/strlcat.h"
+#include "contrib/string.h"
+
+#define LIB_EXTENSION ".so"
+
+knot_dynarray_define(mod, module_t *, DYNARRAY_VISIBILITY_NORMAL)
+knot_dynarray_define(old_schema, yp_item_t *, DYNARRAY_VISIBILITY_NORMAL)
+
+static module_t STATIC_MODULES[] = {
+ STATIC_MODULES_INIT
+ { NULL }
+};
+
+module_t *conf_mod_find(
+ conf_t *conf,
+ const char *name,
+ size_t len,
+ bool temporary)
+{
+ if (conf == NULL || name == NULL) {
+ return NULL;
+ }
+
+ // First, search in static modules.
+ for (module_t *mod = STATIC_MODULES; mod->api != NULL; mod++) {
+ if (strncmp(name, mod->api->name, len) == 0) {
+ return mod;
+ }
+ }
+
+ module_type_t excluded_type = temporary ? MOD_EXPLICIT : MOD_TEMPORARY;
+
+ // Second, search in dynamic modules.
+ knot_dynarray_foreach(mod, module_t *, module, conf->modules) {
+ if ((*module) != NULL && (*module)->type != excluded_type &&
+ strncmp(name, (*module)->api->name, len) == 0) {
+ return (*module);
+ }
+ }
+
+ return NULL;
+}
+
+static int mod_load(
+ conf_t *conf,
+ module_t *mod)
+{
+ static const yp_item_t module_common[] = {
+ { C_ID, YP_TSTR, YP_VNONE, CONF_IO_FREF },
+ { C_COMMENT, YP_TSTR, YP_VNONE },
+ { NULL }
+ };
+
+ yp_item_t *sub_items = NULL;
+
+ int ret;
+ if (mod->api->config != NULL) {
+ ret = yp_schema_merge(&sub_items, module_common, mod->api->config);
+ } else {
+ ret = yp_schema_copy(&sub_items, module_common);
+ }
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ /* Synthesise module config section name. */
+ const size_t name_len = strlen(mod->api->name);
+ if (name_len > YP_MAX_ITEM_NAME_LEN) {
+ return KNOT_YP_EINVAL_ITEM;
+ }
+ char name[1 + YP_MAX_ITEM_NAME_LEN + 1];
+ name[0] = name_len;
+ memcpy(name + 1, mod->api->name, name_len + 1);
+
+ const yp_item_t schema[] = {
+ { name, YP_TGRP, YP_VGRP = { sub_items },
+ YP_FALLOC | YP_FMULTI | CONF_IO_FRLD_MOD | CONF_IO_FRLD_ZONES,
+ { mod->api->config_check } },
+ { NULL }
+ };
+
+ yp_item_t *merged = NULL;
+ ret = yp_schema_merge(&merged, conf->schema, schema);
+ yp_schema_free(sub_items);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ // Update configuration schema (with lazy free).
+ yp_item_t **current_schema = &conf->schema;
+ yp_item_t *old_schema = rcu_xchg_pointer(current_schema, merged);
+ synchronize_rcu();
+ old_schema_dynarray_add(&conf->old_schemas, &old_schema);
+
+ return KNOT_EOK;
+}
+
+int conf_mod_load_common(
+ conf_t *conf)
+{
+ if (conf == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ int ret = KNOT_EOK;
+
+ // First, load static modules.
+ for (module_t *mod = STATIC_MODULES; mod->api != NULL; mod++) {
+ ret = mod_load(conf, mod);
+ if (ret != KNOT_EOK) {
+ log_error("module '%s', failed to load (%s)",
+ mod->api->name, knot_strerror(ret));
+ break;
+ }
+
+ log_debug("module '%s', loaded static", mod->api->name);
+ }
+
+ // Second, try to load implicit shared modules if configured.
+ if (strlen(MODULE_DIR) > 0) {
+ struct stat path_stat;
+ glob_t glob_buf = { 0 };
+
+ char *path = sprintf_alloc("%s/*%s", MODULE_DIR, LIB_EXTENSION);
+ if (path == NULL) {
+ ret = KNOT_ENOMEM;
+ } else if (stat(MODULE_DIR, &path_stat) != 0 ||
+ !S_ISDIR(path_stat.st_mode)) {
+ if (errno == ENOENT) {
+ // Module directory doesn't exist.
+ ret = KNOT_EOK;
+ } else {
+ log_error("module, invalid directory '%s'",
+ MODULE_DIR);
+ ret = KNOT_EINVAL;
+ }
+ } else if (access(MODULE_DIR, F_OK | R_OK) != 0) {
+ log_error("module, failed to access directory '%s'",
+ MODULE_DIR);
+ ret = KNOT_EACCES;
+ } else {
+ ret = glob(path, 0, NULL, &glob_buf);
+ if (ret != 0 && ret != GLOB_NOMATCH) {
+ log_error("module, failed to read directory '%s'",
+ MODULE_DIR);
+ ret = KNOT_EACCES;
+ } else {
+ ret = KNOT_EOK;
+ }
+ }
+
+ // Process each module in the directory.
+ for (size_t i = 0; i < glob_buf.gl_pathc; i++) {
+ (void)conf_mod_load_extra(conf, NULL, glob_buf.gl_pathv[i],
+ MOD_IMPLICIT);
+ }
+
+ globfree(&glob_buf);
+ free(path);
+ }
+
+ conf_mod_load_purge(conf, false);
+
+ return ret;
+}
+
+int conf_mod_load_extra(
+ conf_t *conf,
+ const char *mod_name,
+ const char *file_name,
+ module_type_t type)
+{
+ if (conf == NULL || (mod_name == NULL && file_name == NULL)) {
+ return KNOT_EINVAL;
+ }
+
+ // Synthesize module file name if not specified.
+ char *tmp_name = NULL;
+ if (file_name == NULL) {
+ tmp_name = sprintf_alloc("%s/%s%s", MODULE_INSTDIR,
+ mod_name + strlen(KNOTD_MOD_NAME_PREFIX),
+ LIB_EXTENSION);
+ if (tmp_name == NULL) {
+ return KNOT_ENOMEM;
+ }
+ file_name = tmp_name;
+ }
+
+ void *handle = dlopen(file_name, RTLD_NOW | RTLD_LOCAL);
+ if (handle == NULL) {
+ log_error("module, failed to open '%s' (%s)", file_name, dlerror());
+ free(tmp_name);
+ return KNOT_ENOENT;
+ }
+ (void)dlerror();
+
+ knotd_mod_api_t *api = dlsym(handle, "knotd_mod_api");
+ if (api == NULL) {
+ char *err = dlerror();
+ if (err == NULL) {
+ err = "empty symbol";
+ }
+ log_error("module, invalid library '%s' (%s)", file_name, err);
+ dlclose(handle);
+ free(tmp_name);
+ return KNOT_ENOENT;
+ }
+ free(tmp_name);
+
+ if (api->version != KNOTD_MOD_ABI_VERSION) {
+ log_error("module '%s', incompatible version", api->name);
+ dlclose(handle);
+ return KNOT_ENOTSUP;
+ }
+
+ if (api->name == NULL || (mod_name != NULL && strcmp(api->name, mod_name) != 0)) {
+ log_error("module '%s', module name mismatch", api->name);
+ dlclose(handle);
+ return KNOT_ENOTSUP;
+ }
+
+ // Check if the module is already loaded.
+ module_t *found = conf_mod_find(conf, api->name, strlen(api->name),
+ type == MOD_TEMPORARY);
+ if (found != NULL) {
+ log_error("module '%s', duplicate module", api->name);
+ dlclose(handle);
+ return KNOT_EEXIST;
+ }
+
+ module_t *mod = calloc(1, sizeof(*mod));
+ if (mod == NULL) {
+ dlclose(handle);
+ return KNOT_ENOMEM;
+ }
+ mod->api = api;
+ mod->lib_handle = handle;
+ mod->type = type;
+
+ int ret = mod_load(conf, mod);
+ if (ret != KNOT_EOK) {
+ log_error("module '%s', failed to load (%s)", api->name,
+ knot_strerror(ret));
+ dlclose(handle);
+ free(mod);
+ return ret;
+ }
+
+ mod_dynarray_add(&conf->modules, &mod);
+
+ log_debug("module '%s', loaded shared", api->name);
+
+ return KNOT_EOK;
+}
+
+static void unload_shared(
+ module_t *mod)
+{
+ if (mod != NULL) {
+ assert(mod->lib_handle);
+ (void)dlclose(mod->lib_handle);
+ free(mod);
+ }
+}
+
+void conf_mod_load_purge(
+ conf_t *conf,
+ bool temporary)
+{
+ if (conf == NULL) {
+ return;
+ }
+
+ // Switch the current temporary schema with the initial one.
+ if (temporary && conf->old_schemas.size > 0) {
+ yp_item_t **current_schema = &conf->schema;
+ yp_item_t **initial = &(conf->old_schemas.arr(&conf->old_schemas))[0];
+
+ yp_item_t *old_schema = rcu_xchg_pointer(current_schema, *initial);
+ synchronize_rcu();
+ *initial = old_schema;
+ }
+
+ knot_dynarray_foreach(old_schema, yp_item_t *, schema, conf->old_schemas) {
+ yp_schema_free(*schema);
+ }
+ old_schema_dynarray_free(&conf->old_schemas);
+
+ knot_dynarray_foreach(mod, module_t *, module, conf->modules) {
+ if ((*module) != NULL && (*module)->type == MOD_TEMPORARY) {
+ unload_shared((*module));
+ *module = NULL; // Cannot remove from dynarray.
+ }
+ }
+}
+
+void conf_mod_unload_shared(
+ conf_t *conf)
+{
+ if (conf == NULL) {
+ return;
+ }
+
+ knot_dynarray_foreach(mod, module_t *, module, conf->modules) {
+ unload_shared((*module));
+ }
+ mod_dynarray_free(&conf->modules);
+}
+
+#define LOG_ARGS(mod_id, msg) "module '%s%s%.*s', " msg, \
+ mod_id->name + 1, (mod_id->len > 0) ? "/" : "", (int)mod_id->len, \
+ mod_id->data
+
+#define MOD_ID_LOG(zone, level, mod_id, msg, ...) \
+ if (zone != NULL) \
+ log_zone_##level(zone, LOG_ARGS(mod_id, msg), ##__VA_ARGS__); \
+ else \
+ log_##level(LOG_ARGS(mod_id, msg), ##__VA_ARGS__);
+
+void conf_activate_modules(
+ conf_t *conf,
+ struct server *server,
+ const knot_dname_t *zone_name,
+ list_t *query_modules,
+ struct query_plan **query_plan)
+{
+ int ret = KNOT_EOK;
+
+ if (conf == NULL || query_modules == NULL || query_plan == NULL) {
+ ret = KNOT_EINVAL;
+ goto activate_error;
+ }
+
+ conf_val_t val;
+
+ // Get list of associated modules.
+ if (zone_name != NULL) {
+ val = conf_zone_get(conf, C_MODULE, zone_name);
+ } else {
+ val = conf_default_get(conf, C_GLOBAL_MODULE);
+ }
+
+ switch (val.code) {
+ case KNOT_EOK:
+ break;
+ case KNOT_ENOENT: // Check if a module is configured at all.
+ case KNOT_YP_EINVAL_ID:
+ return;
+ default:
+ ret = val.code;
+ goto activate_error;
+ }
+
+ // Create query plan.
+ *query_plan = query_plan_create();
+ if (*query_plan == NULL) {
+ ret = KNOT_ENOMEM;
+ goto activate_error;
+ }
+
+ // Initialize query modules list.
+ init_list(query_modules);
+
+ // Open the modules.
+ while (val.code == KNOT_EOK) {
+ conf_mod_id_t *mod_id = conf_mod_id(&val);
+ if (mod_id == NULL) {
+ ret = KNOT_ENOMEM;
+ goto activate_error;
+ }
+
+ // Open the module.
+ knotd_mod_t *mod = query_module_open(conf, server, mod_id, *query_plan,
+ zone_name);
+ if (mod == NULL) {
+ MOD_ID_LOG(zone_name, error, mod_id, "failed to open");
+ conf_free_mod_id(mod_id);
+ goto skip_module;
+ }
+
+ // Check the module scope.
+ if ((zone_name == NULL && !(mod->api->flags & KNOTD_MOD_FLAG_SCOPE_GLOBAL)) ||
+ (zone_name != NULL && !(mod->api->flags & KNOTD_MOD_FLAG_SCOPE_ZONE))) {
+ MOD_ID_LOG(zone_name, error, mod_id, "out of scope");
+ query_module_close(mod);
+ goto skip_module;
+ }
+
+ // Check if the module is loadable.
+ if (mod->api->load == NULL) {
+ MOD_ID_LOG(zone_name, debug, mod_id, "empty module, not loaded");
+ query_module_close(mod);
+ goto skip_module;
+ }
+
+ // Load the module.
+ ret = mod->api->load(mod);
+ if (ret != KNOT_EOK) {
+ MOD_ID_LOG(zone_name, error, mod_id, "failed to load (%s)",
+ knot_strerror(ret));
+ query_module_close(mod);
+ goto skip_module;
+ }
+ mod->config = NULL; // Invalidate the current config.
+
+ add_tail(query_modules, &mod->node);
+skip_module:
+ conf_val_next(&val);
+ }
+
+ return;
+activate_error:
+ CONF_LOG(LOG_ERR, "failed to activate modules (%s)", knot_strerror(ret));
+}
+
+void conf_deactivate_modules(
+ list_t *query_modules,
+ struct query_plan **query_plan)
+{
+ if (query_modules == NULL || query_plan == NULL) {
+ return;
+ }
+
+ // Free query plan.
+ query_plan_free(*query_plan);
+ *query_plan = NULL;
+
+ // Free query modules list.
+ knotd_mod_t *mod, *next;
+ WALK_LIST_DELSAFE(mod, next, *query_modules) {
+ if (mod->api->unload != NULL) {
+ mod->api->unload(mod);
+ }
+ query_module_close(mod);
+ }
+ init_list(query_modules);
+}
+
+void conf_reset_modules(
+ conf_t *conf,
+ list_t *query_modules,
+ struct query_plan **query_plan)
+{
+ if (query_modules == NULL || query_plan == NULL) {
+ return;
+ }
+
+ struct query_plan *new_plan = query_plan_create();
+ if (new_plan == NULL) {
+ CONF_LOG(LOG_ERR, "failed to activate modules (%s)", knot_strerror(KNOT_ENOMEM));
+ return;
+ }
+
+ struct query_plan *old_plan = rcu_xchg_pointer(query_plan, NULL);
+ synchronize_rcu();
+ query_plan_free(old_plan);
+
+ knotd_mod_t *mod;
+ WALK_LIST(mod, *query_modules) {
+ if (mod->api->unload != NULL) {
+ mod->api->unload(mod);
+ }
+ query_module_reset(conf, mod, new_plan);
+ }
+
+ knotd_mod_t *next;
+ WALK_LIST_DELSAFE(mod, next, *query_modules) {
+ int ret = mod->api->load(mod);
+ if (ret != KNOT_EOK) {
+ MOD_ID_LOG(mod->zone, error, mod->id, "failed to load (%s)",
+ knot_strerror(ret));
+ rem_node(&mod->node);
+ query_module_close(mod);
+ continue;
+ }
+ mod->config = NULL; // Invalidate the current config.
+ }
+
+ (void)rcu_xchg_pointer(query_plan, new_plan);
+}
diff --git a/src/knot/conf/module.h b/src/knot/conf/module.h
new file mode 100644
index 0000000..a821792
--- /dev/null
+++ b/src/knot/conf/module.h
@@ -0,0 +1,126 @@
+/* Copyright (C) 2023 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 <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "knot/conf/base.h"
+
+struct server;
+
+/*!
+ * Finds specific module in static or dynamic modules.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] name Module name.
+ * \param[in] len Module name length.
+ * \param[in] temporary Find only a temporary module indication.
+ *
+ * \return Module, NULL if not found.
+ */
+module_t *conf_mod_find(
+ conf_t *conf,
+ const char *name,
+ size_t len,
+ bool temporary
+);
+
+/*!
+ * Loads common static and shared modules.
+ *
+ * \param[in] conf Configuration.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_mod_load_common(
+ conf_t *conf
+);
+
+/*!
+ * Loads extra shared module.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] mod_name Module name.
+ * \param[in] file_name Shared library file name.
+ * \param[in] type Type of module.
+ *
+ * \return Error code, KNOT_EOK if success.
+ */
+int conf_mod_load_extra(
+ conf_t *conf,
+ const char *mod_name,
+ const char *file_name,
+ module_type_t type
+);
+
+/*!
+ * Purges temporary schemas and modules after all modules loading.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] temporary Purge only temporary modules indication.
+ */
+void conf_mod_load_purge(
+ conf_t *conf,
+ bool temporary
+);
+
+/*!
+ * Unloads all shared modules.
+ *
+ * \param[in] conf Configuration.
+ */
+void conf_mod_unload_shared(
+ conf_t *conf
+);
+
+/*!
+ * Activates configured query modules for the specified zone or for all zones.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] zone_name Zone name, NULL for all zones.
+ * \param[in] query_modules Destination query modules list.
+ * \param[in] query_plan Destination query plan.
+ */
+void conf_activate_modules(
+ conf_t *conf,
+ struct server *server,
+ const knot_dname_t *zone_name,
+ list_t *query_modules,
+ struct query_plan **query_plan
+);
+
+/*!
+ * Deactivates query modules list.
+ *
+ * \param[in] query_modules Destination query modules list.
+ * \param[in] query_plan Destination query plan.
+ */
+void conf_deactivate_modules(
+ list_t *query_modules,
+ struct query_plan **query_plan
+);
+
+/*!
+ * Re-activates query modules in list.
+ *
+ * \param[in] conf Configuration.
+ * \param[in] query_modules Query module list.
+ * \param[in] query_plan Query plan.
+ */
+void conf_reset_modules(
+ conf_t *conf,
+ list_t *query_modules,
+ struct query_plan **query_plan
+);
diff --git a/src/knot/conf/schema.c b/src/knot/conf/schema.c
new file mode 100644
index 0000000..d8472b3
--- /dev/null
+++ b/src/knot/conf/schema.c
@@ -0,0 +1,530 @@
+/* Copyright (C) 2023 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "knot/conf/schema.h"
+#include "knot/conf/confio.h"
+#include "knot/conf/tools.h"
+#include "knot/common/log.h"
+#include "knot/updates/acl.h"
+#include "libknot/rrtype/opt.h"
+#include "libdnssec/tsig.h"
+#include "libdnssec/key.h"
+
+#define HOURS(x) ((x) * 3600)
+#define DAYS(x) ((x) * HOURS(24))
+
+#define KILO(x) (1024LLU * (x))
+#define MEGA(x) (KILO(1024) * (x))
+#define GIGA(x) (MEGA(1024) * (x))
+#define TERA(x) (GIGA(1024) * (x))
+
+#define VIRT_MEM_TOP_32BIT MEGA(500)
+#define VIRT_MEM_LIMIT(x) (((sizeof(void *) < 8) && ((x) > VIRT_MEM_TOP_32BIT)) \
+ ? VIRT_MEM_TOP_32BIT : (x))
+
+static const knot_lookup_t keystore_backends[] = {
+ { KEYSTORE_BACKEND_PEM, "pem" },
+ { KEYSTORE_BACKEND_PKCS11, "pkcs11" },
+ { 0, NULL }
+};
+
+static const knot_lookup_t tsig_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 knot_lookup_t dnssec_key_algs[] = {
+ { DNSSEC_KEY_ALGORITHM_RSA_SHA1, "rsasha1" },
+ { DNSSEC_KEY_ALGORITHM_RSA_SHA1_NSEC3, "rsasha1-nsec3-sha1" },
+ { DNSSEC_KEY_ALGORITHM_RSA_SHA256, "rsasha256" },
+ { DNSSEC_KEY_ALGORITHM_RSA_SHA512, "rsasha512" },
+ { DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256, "ecdsap256sha256" },
+ { DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384, "ecdsap384sha384" },
+#ifdef HAVE_ED25519
+ { DNSSEC_KEY_ALGORITHM_ED25519, "ed25519" },
+#endif
+#ifdef HAVE_ED448
+ { DNSSEC_KEY_ALGORITHM_ED448, "ed448" },
+#endif
+ { 0, NULL }
+};
+
+static const knot_lookup_t unsafe_operation[] = {
+ { UNSAFE_NONE, "none" },
+ { UNSAFE_KEYSET, "no-check-keyset" },
+ { UNSAFE_DNSKEY, "no-update-dnskey" },
+ { UNSAFE_NSEC, "no-update-nsec" },
+ { UNSAFE_EXPIRED, "no-update-expired" },
+ { 0, NULL }
+};
+
+static const knot_lookup_t cds_cdnskey[] = {
+ { CDS_CDNSKEY_NONE, "none" },
+ { CDS_CDNSKEY_EMPTY, "delete-dnssec" },
+ { CDS_CDNSKEY_ROLLOVER, "rollover" },
+ { CDS_CDNSKEY_ALWAYS, "always" },
+ { CDS_CDNSKEY_DOUBLE_DS, "double-ds" },
+ { 0, NULL }
+};
+
+static const knot_lookup_t dnskey_mgmt[] = {
+ { DNSKEY_MGMT_FULL, "full" },
+ { DNSKEY_MGMT_INCREMENTAL, "incremental" },
+ { 0, NULL }
+};
+
+static const knot_lookup_t cds_digesttype[] = {
+ { DNSSEC_KEY_DIGEST_SHA256, "sha256" },
+ { DNSSEC_KEY_DIGEST_SHA384, "sha384" },
+ { 0, NULL }
+};
+
+const knot_lookup_t acl_actions[] = {
+ { ACL_ACTION_QUERY, "query" },
+ { ACL_ACTION_NOTIFY, "notify" },
+ { ACL_ACTION_TRANSFER, "transfer" },
+ { ACL_ACTION_UPDATE, "update" },
+ { 0, NULL }
+};
+
+static const knot_lookup_t acl_update_owner[] = {
+ { ACL_UPDATE_OWNER_KEY, "key" },
+ { ACL_UPDATE_OWNER_ZONE, "zone" },
+ { ACL_UPDATE_OWNER_NAME, "name" },
+ { 0, NULL }
+};
+
+static const knot_lookup_t acl_update_owner_match[] = {
+ { ACL_UPDATE_MATCH_SUBEQ, "sub-or-equal" },
+ { ACL_UPDATE_MATCH_EQ, "equal" },
+ { ACL_UPDATE_MATCH_SUB, "sub" },
+ { 0, NULL }
+};
+
+static const knot_lookup_t serial_policies[] = {
+ { SERIAL_POLICY_INCREMENT, "increment" },
+ { SERIAL_POLICY_UNIXTIME, "unixtime" },
+ { SERIAL_POLICY_DATESERIAL, "dateserial" },
+ { 0, NULL }
+};
+
+static const knot_lookup_t semantic_checks[] = {
+ { SEMCHECKS_OFF, "off" },
+ { SEMCHECKS_OFF, "false" },
+ { SEMCHECKS_ON, "on" },
+ { SEMCHECKS_ON, "true" },
+ { SEMCHECKS_SOFT, "soft" },
+ { 0, NULL }
+};
+
+static const knot_lookup_t zone_digest[] = {
+ { ZONE_DIGEST_NONE, "none" },
+ { ZONE_DIGEST_SHA384, "zonemd-sha384" },
+ { ZONE_DIGEST_SHA512, "zonemd-sha512" },
+ { ZONE_DIGEST_REMOVE, "remove" },
+ { 0, NULL }
+};
+
+static const knot_lookup_t journal_content[] = {
+ { JOURNAL_CONTENT_NONE, "none" },
+ { JOURNAL_CONTENT_CHANGES, "changes" },
+ { JOURNAL_CONTENT_ALL, "all" },
+ { 0, NULL }
+};
+
+static const knot_lookup_t zonefile_load[] = {
+ { ZONEFILE_LOAD_NONE, "none" },
+ { ZONEFILE_LOAD_DIFF, "difference" },
+ { ZONEFILE_LOAD_DIFSE, "difference-no-serial" },
+ { ZONEFILE_LOAD_WHOLE, "whole" },
+ { 0, NULL }
+};
+
+static const knot_lookup_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 knot_lookup_t journal_modes[] = {
+ { JOURNAL_MODE_ROBUST, "robust" },
+ { JOURNAL_MODE_ASYNC, "asynchronous" },
+ { 0, NULL }
+};
+
+static const knot_lookup_t catalog_roles[] = {
+ { CATALOG_ROLE_NONE, "none" },
+ { CATALOG_ROLE_INTERPRET, "interpret" },
+ { CATALOG_ROLE_GENERATE, "generate" },
+ { CATALOG_ROLE_MEMBER, "member" },
+ { 0, NULL }
+};
+
+static const knot_lookup_t dbus_events[] = {
+ { DBUS_EVENT_NONE, "none" },
+ { DBUS_EVENT_RUNNING, "running" },
+ { DBUS_EVENT_ZONE_UPDATED, "zone-updated" },
+ { DBUS_EVENT_ZONE_SUBMISSION, "ksk-submission" },
+ { DBUS_EVENT_ZONE_INVALID, "dnssec-invalid" },
+ { 0, NULL }
+};
+
+static const yp_item_t desc_module[] = {
+ { C_ID, YP_TSTR, YP_VNONE, YP_FNONE, { check_module_id } },
+ { C_FILE, YP_TSTR, YP_VNONE },
+ { C_COMMENT, YP_TSTR, YP_VNONE },
+ { NULL }
+};
+
+static const yp_item_t desc_server[] = {
+ { C_IDENT, YP_TSTR, YP_VNONE },
+ { C_VERSION, YP_TSTR, YP_VNONE },
+ { C_NSID, YP_THEX, YP_VNONE },
+ { C_RUNDIR, YP_TSTR, YP_VSTR = { RUN_DIR } },
+ { C_USER, YP_TSTR, YP_VNONE },
+ { C_PIDFILE, YP_TSTR, YP_VSTR = { "knot.pid" } },
+ { C_UDP_WORKERS, YP_TINT, YP_VINT = { 1, CONF_MAX_UDP_WORKERS, YP_NIL } },
+ { C_TCP_WORKERS, YP_TINT, YP_VINT = { 1, CONF_MAX_TCP_WORKERS, YP_NIL } },
+ { C_BG_WORKERS, YP_TINT, YP_VINT = { 1, CONF_MAX_BG_WORKERS, YP_NIL } },
+ { C_ASYNC_START, YP_TBOOL, YP_VNONE },
+ { C_TCP_IDLE_TIMEOUT, YP_TINT, YP_VINT = { 1, INT32_MAX, 10, YP_STIME } },
+ { C_TCP_IO_TIMEOUT, YP_TINT, YP_VINT = { 0, INT32_MAX, 500 } },
+ { C_TCP_RMT_IO_TIMEOUT, YP_TINT, YP_VINT = { 0, INT32_MAX, 5000 } },
+ { C_TCP_MAX_CLIENTS, YP_TINT, YP_VINT = { 0, INT32_MAX, YP_NIL } },
+ { C_TCP_REUSEPORT, YP_TBOOL, YP_VNONE },
+ { C_TCP_FASTOPEN, YP_TBOOL, YP_VNONE },
+ { C_QUIC_MAX_CLIENTS, YP_TINT, YP_VINT = { 128, INT32_MAX, 10000 } },
+ { C_QUIC_OUTBUF_MAX_SIZE, YP_TINT, YP_VINT = { MEGA(1), SSIZE_MAX, MEGA(100), YP_SSIZE } },
+ { C_QUIC_IDLE_CLOSE, YP_TINT, YP_VINT = { 1, INT32_MAX, 4, YP_STIME } },
+ { C_RMT_POOL_LIMIT, YP_TINT, YP_VINT = { 0, INT32_MAX, 0 } },
+ { C_RMT_POOL_TIMEOUT, YP_TINT, YP_VINT = { 1, INT32_MAX, 5, YP_STIME } },
+ { C_RMT_RETRY_DELAY, YP_TINT, YP_VINT = { 0, INT32_MAX, 0 } },
+ { C_SOCKET_AFFINITY, YP_TBOOL, YP_VNONE },
+ { C_UDP_MAX_PAYLOAD, YP_TINT, YP_VINT = { KNOT_EDNS_MIN_DNSSEC_PAYLOAD,
+ KNOT_EDNS_MAX_UDP_PAYLOAD,
+ 1232, YP_SSIZE } },
+ { C_UDP_MAX_PAYLOAD_IPV4, YP_TINT, YP_VINT = { KNOT_EDNS_MIN_DNSSEC_PAYLOAD,
+ KNOT_EDNS_MAX_UDP_PAYLOAD,
+ 1232, YP_SSIZE } },
+ { C_UDP_MAX_PAYLOAD_IPV6, YP_TINT, YP_VINT = { KNOT_EDNS_MIN_DNSSEC_PAYLOAD,
+ KNOT_EDNS_MAX_UDP_PAYLOAD,
+ 1232, YP_SSIZE } },
+ { C_CERT_FILE, YP_TSTR, YP_VNONE, YP_FNONE, { check_file } },
+ { C_KEY_FILE, YP_TSTR, YP_VNONE, YP_FNONE, { check_file } },
+ { C_ECS, YP_TBOOL, YP_VNONE },
+ { C_ANS_ROTATION, YP_TBOOL, YP_VNONE },
+ { C_AUTO_ACL, YP_TBOOL, YP_VNONE },
+ { C_PROXY_ALLOWLIST, YP_TNET, YP_VNONE, YP_FMULTI},
+ { C_DBUS_EVENT, YP_TOPT, YP_VOPT = { dbus_events, DBUS_EVENT_NONE }, YP_FMULTI },
+ { C_DBUS_INIT_DELAY, YP_TINT, YP_VINT = { 0, INT32_MAX, 1, YP_STIME } },
+ { C_LISTEN, YP_TADDR, YP_VADDR = { 53 }, YP_FMULTI, { check_listen } },
+ { C_COMMENT, YP_TSTR, YP_VNONE },
+ // Legacy items.
+ { C_LISTEN_XDP, YP_TADDR, YP_VADDR = { 0 }, YP_FMULTI, { legacy_item } },
+ { C_MAX_TCP_CLIENTS, YP_TINT, YP_VINT = { 0, INT32_MAX, 0 }, YP_FNONE, { legacy_item } },
+ { C_TCP_HSHAKE_TIMEOUT, YP_TINT, YP_VINT = { 0, INT32_MAX, 0, YP_STIME }, YP_FNONE, { legacy_item } },
+ { C_TCP_REPLY_TIMEOUT, YP_TINT, YP_VINT = { 0, INT32_MAX, 0, YP_STIME }, YP_FNONE, { legacy_item } },
+ { C_MAX_UDP_PAYLOAD, YP_TINT, YP_VINT = { 0, INT32_MAX, 0, YP_SSIZE }, YP_FNONE, { legacy_item } },
+ { C_MAX_IPV4_UDP_PAYLOAD, YP_TINT, YP_VINT = { 0, INT32_MAX, 0, YP_SSIZE }, YP_FNONE, { legacy_item } },
+ { C_MAX_IPV6_UDP_PAYLOAD, YP_TINT, YP_VINT = { 0, INT32_MAX, 0, YP_SSIZE }, YP_FNONE, { legacy_item } },
+ { NULL }
+};
+
+static const yp_item_t desc_xdp[] = {
+ { C_LISTEN, YP_TADDR, YP_VADDR = { 53 }, YP_FMULTI, { check_xdp_listen } },
+ { C_UDP, YP_TBOOL, YP_VBOOL = { true } },
+ { C_TCP, YP_TBOOL, YP_VNONE },
+ { C_QUIC, YP_TBOOL, YP_VNONE },
+ { C_QUIC_PORT, YP_TINT, YP_VINT = { 1, 65535, 853 } },
+ { C_QUIC_LOG, YP_TBOOL, YP_VNONE },
+ { C_TCP_MAX_CLIENTS, YP_TINT, YP_VINT = { 1024, INT32_MAX, 1000000 } },
+ { C_TCP_INBUF_MAX_SIZE, YP_TINT, YP_VINT = { MEGA(1), SSIZE_MAX, MEGA(100), YP_SSIZE } },
+ { C_TCP_OUTBUF_MAX_SIZE, YP_TINT, YP_VINT = { MEGA(1), SSIZE_MAX, MEGA(100), YP_SSIZE } },
+ { C_TCP_IDLE_CLOSE, YP_TINT, YP_VINT = { 1, INT32_MAX, 10, YP_STIME } },
+ { C_TCP_IDLE_RESET, YP_TINT, YP_VINT = { 1, INT32_MAX, 20, YP_STIME } },
+ { C_TCP_RESEND, YP_TINT, YP_VINT = { 1, INT32_MAX, 5, YP_STIME } },
+ { C_ROUTE_CHECK, YP_TBOOL, YP_VNONE },
+ { C_COMMENT, YP_TSTR, YP_VNONE },
+ { NULL }
+};
+
+static const yp_item_t desc_control[] = {
+ { C_LISTEN, YP_TSTR, YP_VSTR = { "knot.sock" } },
+ { C_TIMEOUT, YP_TINT, YP_VINT = { 0, INT32_MAX / 1000, 5, YP_STIME } },
+ { C_COMMENT, YP_TSTR, YP_VNONE },
+ { NULL }
+};
+
+static const yp_item_t desc_log[] = {
+ { C_TARGET, YP_TSTR, YP_VNONE },
+ { C_SERVER, YP_TOPT, YP_VOPT = { log_severities, 0 } },
+ { C_CTL, 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 }
+};
+
+static const yp_item_t desc_stats[] = {
+ { C_TIMER, YP_TINT, YP_VINT = { 1, UINT32_MAX, 0, YP_STIME } },
+ { C_FILE, YP_TSTR, YP_VSTR = { "stats.yaml" } },
+ { C_APPEND, YP_TBOOL, YP_VNONE },
+ { C_COMMENT, YP_TSTR, YP_VNONE },
+ { NULL }
+};
+
+static const yp_item_t desc_database[] = {
+ { C_STORAGE, YP_TSTR, YP_VSTR = { STORAGE_DIR } },
+ { C_JOURNAL_DB, YP_TSTR, YP_VSTR = { "journal" } },
+ { C_JOURNAL_DB_MODE, YP_TOPT, YP_VOPT = { journal_modes, JOURNAL_MODE_ROBUST } },
+ { C_JOURNAL_DB_MAX_SIZE, YP_TINT, YP_VINT = { MEGA(1), VIRT_MEM_LIMIT(TERA(100)),
+ VIRT_MEM_LIMIT(GIGA(20)), YP_SSIZE } },
+ { C_KASP_DB, YP_TSTR, YP_VSTR = { "keys" } },
+ { C_KASP_DB_MAX_SIZE, YP_TINT, YP_VINT = { MEGA(5), VIRT_MEM_LIMIT(GIGA(100)),
+ MEGA(500), YP_SSIZE } },
+ { C_TIMER_DB, YP_TSTR, YP_VSTR = { "timers" } },
+ { C_TIMER_DB_MAX_SIZE, YP_TINT, YP_VINT = { MEGA(1), VIRT_MEM_LIMIT(GIGA(100)),
+ MEGA(100), YP_SSIZE } },
+ { C_CATALOG_DB, YP_TSTR, YP_VSTR = { "catalog" } },
+ { C_CATALOG_DB_MAX_SIZE, YP_TINT, YP_VINT = { MEGA(5), VIRT_MEM_LIMIT(GIGA(100)),
+ VIRT_MEM_LIMIT(GIGA(20)), YP_SSIZE } },
+ { C_COMMENT, YP_TSTR, YP_VNONE },
+ { NULL }
+};
+
+static const yp_item_t desc_keystore[] = {
+ { C_ID, YP_TSTR, YP_VNONE },
+ { C_BACKEND, YP_TOPT, YP_VOPT = { keystore_backends, KEYSTORE_BACKEND_PEM },
+ CONF_IO_FRLD_ZONES },
+ { C_CONFIG, YP_TSTR, YP_VSTR = { "keys" }, CONF_IO_FRLD_ZONES },
+ { C_KEY_LABEL, YP_TBOOL, YP_VNONE },
+ { 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 = { tsig_key_algs, DNSSEC_TSIG_UNKNOWN } },
+ { C_SECRET, YP_TB64, YP_VNONE },
+ { C_COMMENT, YP_TSTR, YP_VNONE },
+ { NULL }
+};
+
+static const yp_item_t desc_remote[] = {
+ { C_ID, YP_TSTR, YP_VNONE, CONF_IO_FREF },
+ { C_ADDR, YP_TADDR, YP_VADDR = { 53 }, YP_FMULTI },
+ { C_VIA, YP_TADDR, YP_VNONE, YP_FMULTI },
+ { C_KEY, YP_TREF, YP_VREF = { C_KEY }, YP_FNONE, { check_ref } },
+ { C_BLOCK_NOTIFY_XFR, YP_TBOOL, YP_VNONE },
+ { C_NO_EDNS, YP_TBOOL, YP_VNONE },
+ { C_AUTO_ACL, YP_TBOOL, YP_VBOOL = { true } },
+ { C_COMMENT, YP_TSTR, YP_VNONE },
+ { NULL }
+};
+
+static const yp_item_t desc_remotes[] = {
+ { C_ID, YP_TSTR, YP_VNONE, CONF_IO_FREF },
+ { C_RMT, YP_TREF, YP_VREF = { C_RMT }, YP_FMULTI, { check_ref } },
+ { C_COMMENT, YP_TSTR, YP_VNONE },
+ { NULL }
+};
+
+static const yp_item_t desc_acl[] = {
+ { C_ID, YP_TSTR, YP_VNONE, CONF_IO_FREF },
+ { C_ADDR, YP_TNET, YP_VNONE, YP_FMULTI },
+ { C_KEY, YP_TREF, YP_VREF = { C_KEY }, YP_FMULTI, { check_ref } },
+ { C_RMT, YP_TREF, YP_VREF = { C_RMT, C_RMTS }, YP_FMULTI, { check_ref } },
+ { C_ACTION, YP_TOPT, YP_VOPT = { acl_actions, ACL_ACTION_QUERY }, YP_FMULTI },
+ { C_DENY, YP_TBOOL, YP_VNONE },
+ { C_UPDATE_TYPE, YP_TDATA, YP_VDATA = { 0, NULL, rrtype_to_bin, rrtype_to_txt },
+ YP_FMULTI, },
+ { C_UPDATE_OWNER, YP_TOPT, YP_VOPT = { acl_update_owner, ACL_UPDATE_OWNER_NONE } },
+ { C_UPDATE_OWNER_MATCH, YP_TOPT, YP_VOPT = { acl_update_owner_match, ACL_UPDATE_MATCH_SUBEQ } },
+ { C_UPDATE_OWNER_NAME, YP_TDATA, YP_VDATA = { 0, NULL, rdname_to_bin, rdname_to_txt },
+ YP_FMULTI, },
+ { C_COMMENT, YP_TSTR, YP_VNONE },
+ { NULL }
+};
+
+static const yp_item_t desc_submission[] = {
+ { C_ID, YP_TSTR, YP_VNONE },
+ { C_PARENT, YP_TREF, YP_VREF = { C_RMT, C_RMTS }, YP_FMULTI, { check_ref } },
+ { C_CHK_INTERVAL, YP_TINT, YP_VINT = { 1, UINT32_MAX, HOURS(1), YP_STIME } },
+ { C_TIMEOUT, YP_TINT, YP_VINT = { 0, UINT32_MAX, 0, YP_STIME },
+ CONF_IO_FRLD_ZONES },
+ { C_PARENT_DELAY, YP_TINT, YP_VINT = { 0, UINT32_MAX, 0, YP_STIME } },
+ { C_COMMENT, YP_TSTR, YP_VNONE },
+ { NULL }
+};
+
+static const yp_item_t desc_policy[] = {
+ { C_ID, YP_TSTR, YP_VNONE, CONF_IO_FREF },
+ { C_KEYSTORE, YP_TREF, YP_VREF = { C_KEYSTORE }, CONF_IO_FRLD_ZONES,
+ { check_ref_dflt } },
+ { C_MANUAL, YP_TBOOL, YP_VNONE, CONF_IO_FRLD_ZONES },
+ { C_SINGLE_TYPE_SIGNING, YP_TBOOL, YP_VNONE, CONF_IO_FRLD_ZONES },
+ { C_ALG, YP_TOPT, YP_VOPT = { dnssec_key_algs,
+ DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256 },
+ CONF_IO_FRLD_ZONES },
+ { C_KSK_SIZE, YP_TINT, YP_VINT = { 0, UINT16_MAX, YP_NIL, YP_SSIZE },
+ CONF_IO_FRLD_ZONES },
+ { C_ZSK_SIZE, YP_TINT, YP_VINT = { 0, UINT16_MAX, YP_NIL, YP_SSIZE },
+ CONF_IO_FRLD_ZONES },
+ { C_KSK_SHARED, YP_TBOOL, YP_VNONE, CONF_IO_FRLD_ZONES },
+ { C_DNSKEY_TTL, YP_TINT, YP_VINT = { 0, INT32_MAX, YP_NIL, YP_STIME },
+ CONF_IO_FRLD_ZONES },
+ { C_ZONE_MAX_TTL, YP_TINT, YP_VINT = { 0, INT32_MAX, YP_NIL, YP_STIME },
+ CONF_IO_FRLD_ZONES },
+ { C_KSK_LIFETIME, YP_TINT, YP_VINT = { 0, UINT32_MAX, 0, YP_STIME },
+ CONF_IO_FRLD_ZONES },
+ { C_ZSK_LIFETIME, YP_TINT, YP_VINT = { 0, UINT32_MAX, DAYS(30), YP_STIME },
+ CONF_IO_FRLD_ZONES },
+ { C_DELETE_DELAY, YP_TINT, YP_VINT = { 0, UINT32_MAX, 0, YP_STIME } },
+ { C_PROPAG_DELAY, YP_TINT, YP_VINT = { 0, INT32_MAX, HOURS(1), YP_STIME },
+ CONF_IO_FRLD_ZONES },
+ { C_RRSIG_LIFETIME, YP_TINT, YP_VINT = { 1, INT32_MAX, DAYS(14), YP_STIME },
+ CONF_IO_FRLD_ZONES },
+ { C_RRSIG_REFRESH, YP_TINT, YP_VINT = { 1, INT32_MAX, YP_NIL, YP_STIME },
+ CONF_IO_FRLD_ZONES },
+ { C_RRSIG_PREREFRESH, YP_TINT, YP_VINT = { 0, INT32_MAX, HOURS(1), YP_STIME },
+ CONF_IO_FRLD_ZONES },
+ { C_REPRO_SIGNING, YP_TBOOL, YP_VNONE, CONF_IO_FRLD_ZONES },
+ { C_NSEC3, YP_TBOOL, YP_VNONE, CONF_IO_FRLD_ZONES },
+ { C_NSEC3_ITER, YP_TINT, YP_VINT = { 0, UINT16_MAX, 0 }, CONF_IO_FRLD_ZONES },
+ { C_NSEC3_OPT_OUT, YP_TBOOL, YP_VNONE, CONF_IO_FRLD_ZONES },
+ { C_NSEC3_SALT_LEN, YP_TINT, YP_VINT = { 0, UINT8_MAX, 8 }, CONF_IO_FRLD_ZONES },
+ { C_NSEC3_SALT_LIFETIME, YP_TINT, YP_VINT = { -1, UINT32_MAX, DAYS(30), YP_STIME },
+ CONF_IO_FRLD_ZONES },
+ { C_SIGNING_THREADS, YP_TINT, YP_VINT = { 1, UINT16_MAX, 1 } },
+ { C_KSK_SBM, YP_TREF, YP_VREF = { C_SBM }, CONF_IO_FRLD_ZONES,
+ { check_ref } },
+ { C_DS_PUSH, YP_TREF, YP_VREF = { C_RMT, C_RMTS }, YP_FMULTI | CONF_IO_FRLD_ZONES,
+ { check_ref } },
+ { C_CDS_CDNSKEY, YP_TOPT, YP_VOPT = { cds_cdnskey, CDS_CDNSKEY_ROLLOVER },
+ CONF_IO_FRLD_ZONES },
+ { C_CDS_DIGESTTYPE, YP_TOPT, YP_VOPT = { cds_digesttype, DNSSEC_KEY_DIGEST_SHA256 },
+ CONF_IO_FRLD_ZONES },
+ { C_DNSKEY_MGMT, YP_TOPT, YP_VOPT = { dnskey_mgmt, DNSKEY_MGMT_FULL },
+ CONF_IO_FRLD_ZONES },
+ { C_OFFLINE_KSK, YP_TBOOL, YP_VNONE, CONF_IO_FRLD_ZONES },
+ { C_UNSAFE_OPERATION, YP_TOPT, YP_VOPT = { unsafe_operation, UNSAFE_NONE }, YP_FMULTI },
+ { C_COMMENT, YP_TSTR, YP_VNONE },
+ { NULL }
+};
+
+#define ZONE_ITEMS(FLAGS) \
+ { C_STORAGE, YP_TSTR, YP_VSTR = { STORAGE_DIR }, FLAGS }, \
+ { C_FILE, YP_TSTR, YP_VNONE, FLAGS }, \
+ { C_MASTER, YP_TREF, YP_VREF = { C_RMT, C_RMTS }, YP_FMULTI, { check_ref } }, \
+ { C_DDNS_MASTER, YP_TREF, YP_VREF = { C_RMT }, YP_FNONE, { check_ref } }, \
+ { C_NOTIFY, YP_TREF, YP_VREF = { C_RMT, C_RMTS }, YP_FMULTI, { check_ref } }, \
+ { C_ACL, YP_TREF, YP_VREF = { C_ACL }, YP_FMULTI, { check_ref } }, \
+ { C_PROVIDE_IXFR, YP_TBOOL, YP_VBOOL = { true } }, \
+ { C_SEM_CHECKS, YP_TOPT, YP_VOPT = { semantic_checks, SEMCHECKS_OFF }, FLAGS }, \
+ { C_ZONEFILE_SYNC, YP_TINT, YP_VINT = { -1, INT32_MAX, 0, YP_STIME } }, \
+ { C_ZONEFILE_LOAD, YP_TOPT, YP_VOPT = { zonefile_load, ZONEFILE_LOAD_WHOLE } }, \
+ { C_JOURNAL_CONTENT, YP_TOPT, YP_VOPT = { journal_content, JOURNAL_CONTENT_CHANGES }, FLAGS }, \
+ { C_JOURNAL_MAX_USAGE, YP_TINT, YP_VINT = { KILO(40), SSIZE_MAX, MEGA(100), YP_SSIZE } }, \
+ { C_JOURNAL_MAX_DEPTH, YP_TINT, YP_VINT = { 2, SSIZE_MAX, 20 } }, \
+ { C_ZONE_MAX_SIZE, YP_TINT, YP_VINT = { 0, SSIZE_MAX, SSIZE_MAX, YP_SSIZE }, FLAGS }, \
+ { C_ADJUST_THR, YP_TINT, YP_VINT = { 1, UINT16_MAX, 1 } }, \
+ { C_DNSSEC_SIGNING, YP_TBOOL, YP_VNONE, FLAGS }, \
+ { C_DNSSEC_VALIDATION, YP_TBOOL, YP_VNONE, FLAGS }, \
+ { C_DNSSEC_POLICY, YP_TREF, YP_VREF = { C_POLICY }, FLAGS, { check_ref_dflt } }, \
+ { C_DS_PUSH, YP_TREF, YP_VREF = { C_RMT, C_RMTS }, YP_FMULTI | FLAGS, \
+ { check_ref } }, \
+ { C_SERIAL_POLICY, YP_TOPT, YP_VOPT = { serial_policies, SERIAL_POLICY_INCREMENT } }, \
+ { C_ZONEMD_GENERATE, YP_TOPT, YP_VOPT = { zone_digest, ZONE_DIGEST_NONE }, FLAGS }, \
+ { C_ZONEMD_VERIFY, YP_TBOOL, YP_VNONE, FLAGS }, \
+ { C_REFRESH_MIN_INTERVAL,YP_TINT, YP_VINT = { 2, UINT32_MAX, 2, YP_STIME } }, \
+ { C_REFRESH_MAX_INTERVAL,YP_TINT, YP_VINT = { 2, UINT32_MAX, UINT32_MAX, YP_STIME } }, \
+ { C_RETRY_MIN_INTERVAL, YP_TINT, YP_VINT = { 1, UINT32_MAX, 1, YP_STIME } }, \
+ { C_RETRY_MAX_INTERVAL, YP_TINT, YP_VINT = { 1, UINT32_MAX, UINT32_MAX, YP_STIME } }, \
+ { C_EXPIRE_MIN_INTERVAL, YP_TINT, YP_VINT = { 3, UINT32_MAX, 3, YP_STIME } }, \
+ { C_EXPIRE_MAX_INTERVAL, YP_TINT, YP_VINT = { 3, UINT32_MAX, UINT32_MAX, YP_STIME } }, \
+ { C_CATALOG_ROLE, YP_TOPT, YP_VOPT = { catalog_roles, CATALOG_ROLE_NONE }, FLAGS }, \
+ { C_CATALOG_TPL, YP_TREF, YP_VREF = { C_TPL }, YP_FMULTI | FLAGS, { check_ref } }, \
+ { C_CATALOG_ZONE, YP_TDNAME,YP_VNONE, FLAGS | CONF_IO_FRLD_ZONES }, \
+ { C_CATALOG_GROUP, YP_TSTR, YP_VNONE, FLAGS | CONF_IO_FRLD_ZONES, { check_catalog_group } }, \
+ { C_MODULE, YP_TDATA, YP_VDATA = { 0, NULL, mod_id_to_bin, mod_id_to_txt }, \
+ YP_FMULTI | FLAGS, { check_modref } }, \
+ { C_COMMENT, YP_TSTR, YP_VNONE }, \
+ /* Legacy items.*/ \
+ { C_DISABLE_ANY, YP_TBOOL, YP_VNONE, YP_FNONE, { legacy_item } }, \
+ { C_MAX_ZONE_SIZE, YP_TINT, YP_VINT = { 0, SSIZE_MAX, 0, YP_SSIZE }, YP_FNONE, { legacy_item } }, \
+ { C_MAX_JOURNAL_USAGE, YP_TINT, YP_VINT = { 0, SSIZE_MAX, 0, YP_SSIZE }, YP_FNONE, { legacy_item } }, \
+ { C_MAX_JOURNAL_DEPTH, YP_TINT, YP_VINT = { 0, SSIZE_MAX, 0 }, YP_FNONE, { legacy_item } }, \
+ { C_MAX_REFRESH_INTERVAL,YP_TINT, YP_VINT = { 0, SSIZE_MAX, 0, YP_STIME }, YP_FNONE, { legacy_item } }, \
+ { C_MIN_REFRESH_INTERVAL,YP_TINT, YP_VINT = { 0, SSIZE_MAX, 0, YP_STIME }, YP_FNONE, { legacy_item } }, \
+
+static const yp_item_t desc_template[] = {
+ { C_ID, YP_TSTR, YP_VNONE, CONF_IO_FREF },
+ { C_GLOBAL_MODULE, YP_TDATA, YP_VDATA = { 0, NULL, mod_id_to_bin, mod_id_to_txt },
+ YP_FMULTI | CONF_IO_FRLD_MOD, { check_modref } },
+ ZONE_ITEMS(CONF_IO_FRLD_ZONES)
+ // Legacy items.
+ { C_TIMER_DB, YP_TSTR, YP_VSTR = { "" }, YP_FNONE, { legacy_item } },
+ { C_MAX_TIMER_DB_SIZE, YP_TINT, YP_VINT = { 0, SSIZE_MAX, 0, YP_SSIZE }, YP_FNONE, { legacy_item } },
+ { C_JOURNAL_DB, YP_TSTR, YP_VSTR = { "" }, YP_FNONE, { legacy_item } },
+ { C_JOURNAL_DB_MODE, YP_TOPT, YP_VOPT = { journal_modes, 0 }, YP_FNONE, { legacy_item } },
+ { C_MAX_JOURNAL_DB_SIZE, YP_TINT, YP_VINT = { 0, SSIZE_MAX, 0, YP_SSIZE }, YP_FNONE, { legacy_item } },
+ { C_KASP_DB, YP_TSTR, YP_VSTR = { "" }, YP_FNONE, { legacy_item } },
+ { C_MAX_KASP_DB_SIZE, YP_TINT, YP_VINT = { 0, SSIZE_MAX, 0, YP_SSIZE }, YP_FNONE, { legacy_item } },
+ { NULL }
+};
+
+static const yp_item_t desc_zone[] = {
+ { C_DOMAIN, YP_TDNAME, YP_VNONE, CONF_IO_FRLD_ZONE },
+ { C_TPL, YP_TREF, YP_VREF = { C_TPL }, CONF_IO_FRLD_ZONE, { check_ref } },
+ ZONE_ITEMS(CONF_IO_FRLD_ZONE)
+ { NULL }
+};
+
+const yp_item_t conf_schema[] = {
+ { C_MODULE, YP_TGRP, YP_VGRP = { desc_module }, YP_FMULTI | CONF_IO_FRLD_ALL |
+ CONF_IO_FCHECK_ZONES, { load_module } },
+ { C_SRV, YP_TGRP, YP_VGRP = { desc_server }, CONF_IO_FRLD_SRV, { check_server } },
+ { C_XDP, YP_TGRP, YP_VGRP = { desc_xdp }, CONF_IO_FRLD_SRV, { check_xdp } },
+ { C_CTL, YP_TGRP, YP_VGRP = { desc_control } },
+ { C_LOG, YP_TGRP, YP_VGRP = { desc_log }, YP_FMULTI | CONF_IO_FRLD_LOG },
+ { C_STATS, YP_TGRP, YP_VGRP = { desc_stats }, CONF_IO_FRLD_SRV },
+ { C_DB, YP_TGRP, YP_VGRP = { desc_database }, CONF_IO_FRLD_SRV, { check_database } },
+ { C_KEYSTORE, YP_TGRP, YP_VGRP = { desc_keystore }, YP_FMULTI, { check_keystore } },
+ { C_KEY, YP_TGRP, YP_VGRP = { desc_key }, YP_FMULTI, { check_key } },
+ { C_RMT, YP_TGRP, YP_VGRP = { desc_remote }, YP_FMULTI, { check_remote } },
+ { C_RMTS, YP_TGRP, YP_VGRP = { desc_remotes }, YP_FMULTI, { check_remotes } },
+ { C_ACL, YP_TGRP, YP_VGRP = { desc_acl }, YP_FMULTI, { check_acl } },
+ { C_SBM, YP_TGRP, YP_VGRP = { desc_submission }, YP_FMULTI },
+ { C_POLICY, YP_TGRP, YP_VGRP = { desc_policy }, YP_FMULTI, { check_policy } },
+ { C_TPL, YP_TGRP, YP_VGRP = { desc_template }, YP_FMULTI, { check_template } },
+ { C_ZONE, YP_TGRP, YP_VGRP = { desc_zone }, YP_FMULTI | CONF_IO_FZONE, { check_zone } },
+ { C_INCL, YP_TSTR, YP_VNONE, CONF_IO_FDIFF_ZONES | CONF_IO_FRLD_ALL, { include_file } },
+ { NULL }
+};
diff --git a/src/knot/conf/schema.h b/src/knot/conf/schema.h
new file mode 100644
index 0000000..5850acc
--- /dev/null
+++ b/src/knot/conf/schema.h
@@ -0,0 +1,279 @@
+/* Copyright (C) 2023 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 <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "libknot/lookup.h"
+#include "libknot/yparser/ypschema.h"
+
+#define C_ACL "\x03""acl"
+#define C_ACTION "\x06""action"
+#define C_ADDR "\x07""address"
+#define C_ADJUST_THR "\x0E""adjust-threads"
+#define C_ALG "\x09""algorithm"
+#define C_ANS_ROTATION "\x0F""answer-rotation"
+#define C_ANY "\x03""any"
+#define C_APPEND "\x06""append"
+#define C_ASYNC_START "\x0B""async-start"
+#define C_AUTO_ACL "\x0D""automatic-acl"
+#define C_BACKEND "\x07""backend"
+#define C_BG_WORKERS "\x12""background-workers"
+#define C_BLOCK_NOTIFY_XFR "\x1B""block-notify-after-transfer"
+#define C_CATALOG_DB "\x0A""catalog-db"
+#define C_CATALOG_DB_MAX_SIZE "\x13""catalog-db-max-size"
+#define C_CATALOG_GROUP "\x0D""catalog-group"
+#define C_CATALOG_ROLE "\x0C""catalog-role"
+#define C_CATALOG_TPL "\x10""catalog-template"
+#define C_CATALOG_ZONE "\x0C""catalog-zone"
+#define C_CDS_CDNSKEY "\x13""cds-cdnskey-publish"
+#define C_CDS_DIGESTTYPE "\x0F""cds-digest-type"
+#define C_CERT_FILE "\x09""cert-file"
+#define C_CHK_INTERVAL "\x0E""check-interval"
+#define C_COMMENT "\x07""comment"
+#define C_CONFIG "\x06""config"
+#define C_CTL "\x07""control"
+#define C_DB "\x08""database"
+#define C_DBUS_EVENT "\x0A""dbus-event"
+#define C_DBUS_INIT_DELAY "\x0F""dbus-init-delay"
+#define C_DDNS_MASTER "\x0B""ddns-master"
+#define C_DENY "\x04""deny"
+#define C_DNSKEY_MGMT "\x11""dnskey-management"
+#define C_DNSKEY_TTL "\x0A""dnskey-ttl"
+#define C_DNSSEC_POLICY "\x0D""dnssec-policy"
+#define C_DNSSEC_SIGNING "\x0E""dnssec-signing"
+#define C_DNSSEC_VALIDATION "\x11""dnssec-validation"
+#define C_DOMAIN "\x06""domain"
+#define C_DS_PUSH "\x07""ds-push"
+#define C_ECS "\x12""edns-client-subnet"
+#define C_EXPIRE_MAX_INTERVAL "\x13""expire-max-interval"
+#define C_EXPIRE_MIN_INTERVAL "\x13""expire-min-interval"
+#define C_FILE "\x04""file"
+#define C_GLOBAL_MODULE "\x0D""global-module"
+#define C_ID "\x02""id"
+#define C_IDENT "\x08""identity"
+#define C_INCL "\x07""include"
+#define C_JOURNAL_CONTENT "\x0F""journal-content"
+#define C_JOURNAL_DB "\x0A""journal-db"
+#define C_JOURNAL_DB_MAX_SIZE "\x13""journal-db-max-size"
+#define C_JOURNAL_DB_MODE "\x0F""journal-db-mode"
+#define C_JOURNAL_MAX_DEPTH "\x11""journal-max-depth"
+#define C_JOURNAL_MAX_USAGE "\x11""journal-max-usage"
+#define C_KASP_DB "\x07""kasp-db"
+#define C_KASP_DB_MAX_SIZE "\x10""kasp-db-max-size"
+#define C_DELETE_DELAY "\x0C""delete-delay"
+#define C_KEY "\x03""key"
+#define C_KEYSTORE "\x08""keystore"
+#define C_KEY_FILE "\x08""key-file"
+#define C_KEY_LABEL "\x09""key-label"
+#define C_KSK_LIFETIME "\x0C""ksk-lifetime"
+#define C_KSK_SBM "\x0E""ksk-submission"
+#define C_KSK_SHARED "\x0a""ksk-shared"
+#define C_KSK_SIZE "\x08""ksk-size"
+#define C_LISTEN "\x06""listen"
+#define C_LOG "\x03""log"
+#define C_MANUAL "\x06""manual"
+#define C_MASTER "\x06""master"
+#define C_MODULE "\x06""module"
+#define C_NO_EDNS "\x07""no-edns"
+#define C_NOTIFY "\x06""notify"
+#define C_NSEC3 "\x05""nsec3"
+#define C_NSEC3_ITER "\x10""nsec3-iterations"
+#define C_NSEC3_OPT_OUT "\x0D""nsec3-opt-out"
+#define C_NSEC3_SALT_LEN "\x11""nsec3-salt-length"
+#define C_NSEC3_SALT_LIFETIME "\x13""nsec3-salt-lifetime"
+#define C_NSID "\x04""nsid"
+#define C_OFFLINE_KSK "\x0B""offline-ksk"
+#define C_PARENT "\x06""parent"
+#define C_PARENT_DELAY "\x0C""parent-delay"
+#define C_PIDFILE "\x07""pidfile"
+#define C_POLICY "\x06""policy"
+#define C_PROPAG_DELAY "\x11""propagation-delay"
+#define C_PROVIDE_IXFR "\x0C""provide-ixfr"
+#define C_PROXY_ALLOWLIST "\x0F""proxy-allowlist"
+#define C_QUIC "\x04""quic"
+#define C_QUIC_IDLE_CLOSE "\x17""quic-idle-close-timeout"
+#define C_QUIC_LOG "\x08""quic-log"
+#define C_QUIC_MAX_CLIENTS "\x10""quic-max-clients"
+#define C_QUIC_OUTBUF_MAX_SIZE "\x14""quic-outbuf-max-size"
+#define C_QUIC_PORT "\x09""quic-port"
+#define C_REFRESH_MAX_INTERVAL "\x14""refresh-max-interval"
+#define C_REFRESH_MIN_INTERVAL "\x14""refresh-min-interval"
+#define C_REPRO_SIGNING "\x14""reproducible-signing"
+#define C_RETRY_MAX_INTERVAL "\x12""retry-max-interval"
+#define C_RETRY_MIN_INTERVAL "\x12""retry-min-interval"
+#define C_RMT "\x06""remote"
+#define C_RMTS "\x07""remotes"
+#define C_RMT_POOL_LIMIT "\x11""remote-pool-limit"
+#define C_RMT_POOL_TIMEOUT "\x13""remote-pool-timeout"
+#define C_RMT_RETRY_DELAY "\x12""remote-retry-delay"
+#define C_ROUTE_CHECK "\x0B""route-check"
+#define C_RRSIG_LIFETIME "\x0E""rrsig-lifetime"
+#define C_RRSIG_PREREFRESH "\x11""rrsig-pre-refresh"
+#define C_RRSIG_REFRESH "\x0D""rrsig-refresh"
+#define C_RUNDIR "\x06""rundir"
+#define C_SBM "\x0A""submission"
+#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_SIGNING_THREADS "\x0F""signing-threads"
+#define C_SINGLE_TYPE_SIGNING "\x13""single-type-signing"
+#define C_SOCKET_AFFINITY "\x0F""socket-affinity"
+#define C_SRV "\x06""server"
+#define C_STATS "\x0A""statistics"
+#define C_STORAGE "\x07""storage"
+#define C_TARGET "\x06""target"
+#define C_TCP "\x03""tcp"
+#define C_TCP_FASTOPEN "\x0C""tcp-fastopen"
+#define C_TCP_IDLE_CLOSE "\x16""tcp-idle-close-timeout"
+#define C_TCP_IDLE_RESET "\x16""tcp-idle-reset-timeout"
+#define C_TCP_IDLE_TIMEOUT "\x10""tcp-idle-timeout"
+#define C_TCP_INBUF_MAX_SIZE "\x12""tcp-inbuf-max-size"
+#define C_TCP_IO_TIMEOUT "\x0E""tcp-io-timeout"
+#define C_TCP_MAX_CLIENTS "\x0F""tcp-max-clients"
+#define C_TCP_OUTBUF_MAX_SIZE "\x13""tcp-outbuf-max-size"
+#define C_TCP_RESEND "\x12""tcp-resend-timeout"
+#define C_TCP_REUSEPORT "\x0D""tcp-reuseport"
+#define C_TCP_RMT_IO_TIMEOUT "\x15""tcp-remote-io-timeout"
+#define C_TCP_WORKERS "\x0B""tcp-workers"
+#define C_TIMEOUT "\x07""timeout"
+#define C_TIMER "\x05""timer"
+#define C_TIMER_DB "\x08""timer-db"
+#define C_TIMER_DB_MAX_SIZE "\x11""timer-db-max-size"
+#define C_TPL "\x08""template"
+#define C_UDP "\x03""udp"
+#define C_UDP_MAX_PAYLOAD "\x0F""udp-max-payload"
+#define C_UDP_MAX_PAYLOAD_IPV4 "\x14""udp-max-payload-ipv4"
+#define C_UDP_MAX_PAYLOAD_IPV6 "\x14""udp-max-payload-ipv6"
+#define C_UDP_WORKERS "\x0B""udp-workers"
+#define C_UNSAFE_OPERATION "\x10""unsafe-operation"
+#define C_UPDATE_OWNER "\x0C""update-owner"
+#define C_UPDATE_OWNER_MATCH "\x12""update-owner-match"
+#define C_UPDATE_OWNER_NAME "\x11""update-owner-name"
+#define C_UPDATE_TYPE "\x0B""update-type"
+#define C_USER "\x04""user"
+#define C_VERSION "\x07""version"
+#define C_VIA "\x03""via"
+#define C_XDP "\x03""xdp"
+#define C_ZONE "\x04""zone"
+#define C_ZONEFILE_LOAD "\x0D""zonefile-load"
+#define C_ZONEFILE_SYNC "\x0D""zonefile-sync"
+#define C_ZONEMD_GENERATE "\x0F""zonemd-generate"
+#define C_ZONEMD_VERIFY "\x0D""zonemd-verify"
+#define C_ZONE_MAX_SIZE "\x0D""zone-max-size"
+#define C_ZONE_MAX_TTL "\x0C""zone-max-ttl"
+#define C_ZSK_LIFETIME "\x0C""zsk-lifetime"
+#define C_ZSK_SIZE "\x08""zsk-size"
+
+// Legacy items.
+#define C_DISABLE_ANY "\x0B""disable-any"
+#define C_LISTEN_XDP "\x0A""listen-xdp"
+#define C_MAX_TIMER_DB_SIZE "\x11""max-timer-db-size"
+#define C_MAX_JOURNAL_DB_SIZE "\x13""max-journal-db-size"
+#define C_MAX_KASP_DB_SIZE "\x10""max-kasp-db-size"
+#define C_TCP_HSHAKE_TIMEOUT "\x15""tcp-handshake-timeout"
+#define C_TCP_REPLY_TIMEOUT "\x11""tcp-reply-timeout"
+#define C_MAX_TCP_CLIENTS "\x0F""max-tcp-clients"
+#define C_MAX_UDP_PAYLOAD "\x0F""max-udp-payload"
+#define C_MAX_IPV4_UDP_PAYLOAD "\x14""max-ipv4-udp-payload"
+#define C_MAX_IPV6_UDP_PAYLOAD "\x14""max-ipv6-udp-payload"
+#define C_MAX_ZONE_SIZE "\x0D""max-zone-size"
+#define C_MAX_REFRESH_INTERVAL "\x14""max-refresh-interval"
+#define C_MIN_REFRESH_INTERVAL "\x14""min-refresh-interval"
+#define C_MAX_JOURNAL_DEPTH "\x11""max-journal-depth"
+#define C_MAX_JOURNAL_USAGE "\x11""max-journal-usage"
+
+enum {
+ KEYSTORE_BACKEND_PEM = 1,
+ KEYSTORE_BACKEND_PKCS11 = 2,
+};
+
+enum {
+ UNSAFE_NONE = 0,
+ UNSAFE_KEYSET = (1 << 0),
+ UNSAFE_DNSKEY = (1 << 1),
+ UNSAFE_NSEC = (1 << 2),
+ UNSAFE_EXPIRED = (1 << 3),
+};
+
+enum {
+ CDS_CDNSKEY_NONE = 0,
+ CDS_CDNSKEY_EMPTY = 1,
+ CDS_CDNSKEY_ROLLOVER = 2,
+ CDS_CDNSKEY_ALWAYS = 3,
+ CDS_CDNSKEY_DOUBLE_DS = 4,
+};
+
+enum {
+ DNSKEY_MGMT_FULL = 0,
+ DNSKEY_MGMT_INCREMENTAL = 1,
+};
+
+enum {
+ SERIAL_POLICY_INCREMENT = 1,
+ SERIAL_POLICY_UNIXTIME = 2,
+ SERIAL_POLICY_DATESERIAL = 3,
+};
+
+enum {
+ SEMCHECKS_OFF = 0,
+ SEMCHECKS_ON = 1,
+ SEMCHECKS_SOFT = 2,
+};
+
+enum {
+ ZONE_DIGEST_NONE = 0,
+ ZONE_DIGEST_SHA384 = 1,
+ ZONE_DIGEST_SHA512 = 2,
+ ZONE_DIGEST_REMOVE = 255,
+};
+
+enum {
+ JOURNAL_CONTENT_NONE = 0,
+ JOURNAL_CONTENT_CHANGES = 1,
+ JOURNAL_CONTENT_ALL = 2,
+};
+
+enum {
+ JOURNAL_MODE_ROBUST = 0, // Robust journal DB disk synchronization.
+ JOURNAL_MODE_ASYNC = 1, // Asynchronous journal DB disk synchronization.
+};
+
+enum {
+ ZONEFILE_LOAD_NONE = 0,
+ ZONEFILE_LOAD_DIFF = 1,
+ ZONEFILE_LOAD_WHOLE = 2,
+ ZONEFILE_LOAD_DIFSE = 3,
+};
+
+enum {
+ CATALOG_ROLE_NONE = 0,
+ CATALOG_ROLE_INTERPRET = 1,
+ CATALOG_ROLE_GENERATE = 2,
+ CATALOG_ROLE_MEMBER = 3,
+};
+
+enum {
+ DBUS_EVENT_NONE = 0,
+ DBUS_EVENT_RUNNING = (1 << 0),
+ DBUS_EVENT_ZONE_UPDATED = (1 << 1),
+ DBUS_EVENT_ZONE_SUBMISSION = (1 << 2),
+ DBUS_EVENT_ZONE_INVALID = (1 << 3),
+};
+
+extern const knot_lookup_t acl_actions[];
+
+extern const yp_item_t conf_schema[];
diff --git a/src/knot/conf/tools.c b/src/knot/conf/tools.c
new file mode 100644
index 0000000..6823a05
--- /dev/null
+++ b/src/knot/conf/tools.c
@@ -0,0 +1,1069 @@
+/* Copyright (C) 2022 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <glob.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>
+#ifdef ENABLE_XDP
+#include <netinet/in.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/udp.h>
+#endif
+
+#include "libdnssec/key.h"
+#include "knot/catalog/catalog_db.h"
+#include "knot/conf/tools.h"
+#include "knot/conf/conf.h"
+#include "knot/conf/module.h"
+#include "knot/conf/schema.h"
+#include "knot/common/log.h"
+#include "libknot/errcode.h"
+#include "libknot/yparser/yptrafo.h"
+#include "libknot/xdp.h"
+#include "contrib/files.h"
+#include "contrib/sockaddr.h"
+#include "contrib/string.h"
+#include "contrib/wire_ctx.h"
+
+#define MAX_INCLUDE_DEPTH 5
+
+char check_str[1024];
+
+int legacy_item(
+ knotd_conf_check_args_t *args)
+{
+ CONF_LOG(LOG_NOTICE, "line %zu, option '%s.%s' is obsolete and has no effect",
+ args->extra->line, args->item->parent->name + 1,
+ args->item->name + 1);
+
+ return KNOT_EOK;
+}
+
+static bool is_default_id(
+ const uint8_t *id,
+ size_t id_len)
+{
+ return id_len == CONF_DEFAULT_ID[0] &&
+ memcmp(id, CONF_DEFAULT_ID + 1, id_len) == 0;
+}
+
+int conf_exec_callbacks(
+ knotd_conf_check_args_t *args)
+{
+ if (args == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ for (size_t i = 0; i < YP_MAX_MISC_COUNT; i++) {
+ int (*fcn)(knotd_conf_check_args_t *) = args->item->misc[i];
+ if (fcn == NULL) {
+ break;
+ }
+
+ int ret = fcn(args);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+int mod_id_to_bin(
+ YP_TXT_BIN_PARAMS)
+{
+ YP_CHECK_PARAMS_BIN;
+
+ // Check for "mod_name/mod_id" format.
+ const uint8_t *pos = (uint8_t *)strchr((char *)in->position, '/');
+ if (pos == in->position) {
+ // Missing module name.
+ return KNOT_EINVAL;
+ } else if (pos >= stop - 1) {
+ // Missing module identifier after slash.
+ return KNOT_EINVAL;
+ }
+
+ // Write mod_name in the yp_name_t format.
+ uint8_t name_len = (pos != NULL) ? (pos - in->position) :
+ wire_ctx_available(in);
+ wire_ctx_write_u8(out, name_len);
+ wire_ctx_write(out, in->position, name_len);
+ wire_ctx_skip(in, name_len);
+
+ // Check for mod_id.
+ if (pos != NULL) {
+ // Skip the separator.
+ wire_ctx_skip(in, sizeof(uint8_t));
+
+ // Write mod_id as a zero terminated string.
+ int ret = yp_str_to_bin(in, out, stop);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ YP_CHECK_RET;
+}
+
+int mod_id_to_txt(
+ YP_BIN_TXT_PARAMS)
+{
+ YP_CHECK_PARAMS_TXT;
+
+ // Write mod_name.
+ uint8_t name_len = wire_ctx_read_u8(in);
+ wire_ctx_write(out, in->position, name_len);
+ wire_ctx_skip(in, name_len);
+
+ // Check for mod_id.
+ if (wire_ctx_available(in) > 0) {
+ // Write the separator.
+ wire_ctx_write_u8(out, '/');
+
+ // Write mod_id.
+ int ret = yp_str_to_txt(in, out);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+
+ YP_CHECK_RET;
+}
+
+int rrtype_to_bin(
+ YP_TXT_BIN_PARAMS)
+{
+ YP_CHECK_PARAMS_BIN;
+
+ uint16_t type;
+ int ret = knot_rrtype_from_string((char *)in->position, &type);
+ if (ret != 0) {
+ return KNOT_EINVAL;
+ }
+ wire_ctx_write_u64(out, type);
+
+ YP_CHECK_RET;
+}
+
+int rrtype_to_txt(
+ YP_BIN_TXT_PARAMS)
+{
+ YP_CHECK_PARAMS_TXT;
+
+ uint16_t type = (uint16_t)wire_ctx_read_u64(in);
+ int ret = knot_rrtype_to_string(type, (char *)out->position, out->size);
+ if (ret < 0) {
+ return KNOT_EINVAL;
+ }
+ wire_ctx_skip(out, ret);
+
+ YP_CHECK_RET;
+}
+
+int rdname_to_bin(
+ YP_TXT_BIN_PARAMS)
+{
+ YP_CHECK_PARAMS_BIN;
+
+ int ret = yp_dname_to_bin(in, out, stop);
+ if (ret == KNOT_EOK && in->wire[in->size - 1] != '.') {
+ // If non-FQDN, trim off the zero label.
+ wire_ctx_skip(out, -1);
+ }
+
+ YP_CHECK_RET;
+}
+
+int rdname_to_txt(
+ YP_BIN_TXT_PARAMS)
+{
+ YP_CHECK_PARAMS_TXT;
+
+ // Temporarily normalize the input.
+ if (in->wire[in->size - 1] == '\0') {
+ return yp_dname_to_txt(in, out);
+ }
+
+ knot_dname_storage_t full_name;
+ wire_ctx_t ctx = wire_ctx_init(full_name, sizeof(full_name));
+ wire_ctx_write(&ctx, in->wire, in->size);
+ wire_ctx_write(&ctx, "\0", 1);
+ wire_ctx_set_offset(&ctx, 0);
+
+ int ret = yp_dname_to_txt(&ctx, out);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ // Trim off the trailing dot.
+ wire_ctx_skip(out, -1);
+
+ YP_CHECK_RET;
+}
+
+int check_ref(
+ knotd_conf_check_args_t *args)
+{
+ const yp_item_t *ref = args->item->var.r.ref;
+ const yp_item_t *ref2 = args->item->var.r.grp_ref;
+
+ bool found1 = false, found2 = false;
+
+ // Try to find the id in the first section.
+ found1 = conf_rawid_exists_txn(args->extra->conf, args->extra->txn,
+ ref->name, args->data, args->data_len);
+ if (ref2 != NULL) {
+ // Try to find the id in the second section if supported.
+ found2 = conf_rawid_exists_txn(args->extra->conf, args->extra->txn,
+ ref2->name, args->data, args->data_len);
+ }
+
+ if (found1 == found2) {
+ if (found1) {
+ args->err_str = "ambiguous reference";
+ return KNOT_ENOENT;
+ } else {
+ args->err_str = "invalid reference";
+ return KNOT_ENOENT;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+int check_ref_dflt(
+ knotd_conf_check_args_t *args)
+{
+ if (check_ref(args) != KNOT_EOK && !is_default_id(args->data, args->data_len)) {
+ args->err_str = "invalid reference";
+ return KNOT_ENOENT;
+ }
+
+ return KNOT_EOK;
+}
+
+int check_listen(
+ knotd_conf_check_args_t *args)
+{
+ bool no_port;
+ struct sockaddr_storage ss = yp_addr(args->data, &no_port);
+ if (!no_port && sockaddr_port(&ss) == 0) {
+ args->err_str = "invalid port";
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
+int check_xdp_listen(
+ knotd_conf_check_args_t *args)
+{
+#ifndef ENABLE_XDP
+ args->err_str = "XDP is not available";
+ return KNOT_ENOTSUP;
+#else
+ bool no_port;
+ struct sockaddr_storage ss = yp_addr(args->data, &no_port);
+ conf_xdp_iface_t if_new;
+ int ret = conf_xdp_iface(&ss, &if_new);
+ if (ret != KNOT_EOK) {
+ args->err_str = "invalid XDP interface specification";
+ return ret;
+ } else if (!no_port && if_new.port == 0) {
+ args->err_str = "invalid port";
+ return KNOT_EINVAL;
+ }
+
+ conf_val_t xdp = conf_get_txn(args->extra->conf, args->extra->txn, C_XDP,
+ C_LISTEN);
+ size_t count = conf_val_count(&xdp);
+ while (xdp.code == KNOT_EOK && count-- > 1) {
+ struct sockaddr_storage addr = conf_addr(&xdp, NULL);
+ conf_xdp_iface_t if_prev;
+ ret = conf_xdp_iface(&addr, &if_prev);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ if (strcmp(if_new.name, if_prev.name) == 0) {
+ args->err_str = "duplicate XDP interface specification";
+ return KNOT_EINVAL;
+ }
+ conf_val_next(&xdp);
+ }
+
+ return KNOT_EOK;
+#endif
+}
+
+static int dir_exists(const char *dir)
+{
+ struct stat st;
+ if (stat(dir, &st) != 0) {
+ return knot_map_errno();
+ } else if (!S_ISDIR(st.st_mode)) {
+ return KNOT_ENOTDIR;
+ } else if (access(dir, W_OK) != 0) {
+ return knot_map_errno();
+ } else {
+ return KNOT_EOK;
+ }
+}
+
+static int dir_can_create(const char *dir)
+{
+ int ret = dir_exists(dir);
+ if (ret == KNOT_ENOENT) {
+ return KNOT_EOK;
+ } else {
+ return ret;
+ }
+}
+
+static void check_db(
+ knotd_conf_check_args_t *args,
+ const yp_name_t *db_type,
+ int (*check_fun)(const char *),
+ const char *desc)
+{
+ if (db_type != NULL) {
+ conf_val_t val = conf_get_txn(args->extra->conf, args->extra->txn,
+ C_DB, db_type);
+ if (val.code != KNOT_EOK) {
+ // Don't check implicit database values.
+ return;
+ }
+ }
+
+ char *db = conf_db_txn(args->extra->conf, args->extra->txn, db_type);
+ int ret = check_fun(db);
+ if (ret != KNOT_EOK) {
+ CONF_LOG(LOG_WARNING, "%s '%s' %s", desc, db,
+ (ret == KNOT_EACCES ? "not writable" : knot_strerror(ret)));
+ }
+ free(db);
+}
+
+int check_database(
+ knotd_conf_check_args_t *args)
+{
+ check_db(args, NULL, dir_exists, "database storage");
+ check_db(args, C_TIMER_DB, dir_can_create, "timer database");
+ check_db(args, C_JOURNAL_DB, dir_can_create, "journal database");
+ check_db(args, C_KASP_DB, dir_can_create, "KASP database");
+ check_db(args, C_CATALOG_DB, dir_can_create, "catalog database");
+
+ return KNOT_EOK;
+}
+
+int check_modref(
+ knotd_conf_check_args_t *args)
+{
+ const yp_name_t *mod_name = (const yp_name_t *)args->data;
+ const uint8_t *id = args->data + 1 + args->data[0];
+ size_t id_len = args->data_len - 1 - args->data[0];
+
+ // Check if the module is ever available.
+ const module_t *mod = conf_mod_find(args->extra->conf, mod_name + 1,
+ mod_name[0], args->extra->check);
+ if (mod == NULL) {
+ args->err_str = "unknown module";
+ return KNOT_EINVAL;
+ }
+
+ // Check if the module requires some configuration.
+ if (id_len == 0) {
+ if (mod->api->flags & KNOTD_MOD_FLAG_OPT_CONF) {
+ return KNOT_EOK;
+ } else {
+ args->err_str = "missing module configuration";
+ return KNOT_YP_ENOID;
+ }
+ }
+
+ // Try to find a module with the id.
+ if (!conf_rawid_exists_txn(args->extra->conf, args->extra->txn, mod_name,
+ id, id_len)) {
+ args->err_str = "invalid module reference";
+ return KNOT_ENOENT;
+ }
+
+ return KNOT_EOK;
+}
+
+int check_module_id(
+ knotd_conf_check_args_t *args)
+{
+ const size_t len = strlen(KNOTD_MOD_NAME_PREFIX);
+
+ if (strncmp((const char *)args->id, KNOTD_MOD_NAME_PREFIX, len) != 0) {
+ args->err_str = "required 'mod-' prefix";
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
+int check_file(
+ knotd_conf_check_args_t *args)
+{
+ char *path = abs_path((const char *)args->data, CONFIG_DIR);
+
+ struct stat st;
+ int ret = stat(path, &st);
+ free(path);
+
+ if (ret != 0) {
+ args->err_str = "invalid file";
+ return KNOT_EINVAL;
+ } else if(!S_ISREG(st.st_mode)) {
+ args->err_str = "not a file";
+ return KNOT_EINVAL;
+ } else {
+ return KNOT_EOK;
+ }
+}
+
+#define CHECK_LEGACY_NAME(section, old_item, new_item) { \
+ conf_val_t val = conf_get_txn(args->extra->conf, args->extra->txn, \
+ section, old_item); \
+ if (val.code == KNOT_EOK) { \
+ CONF_LOG(LOG_NOTICE, "option '%s.%s' has no effect, " \
+ "use option '%s.%s' instead", \
+ &section[1], &old_item[1], \
+ &section[1], &new_item[1]); \
+ } \
+}
+
+#define CHECK_LEGACY_NAME_ID(section, old_item, new_item) { \
+ conf_val_t val = conf_rawid_get_txn(args->extra->conf, args->extra->txn, \
+ section, old_item, args->id, args->id_len); \
+ if (val.code == KNOT_EOK) { \
+ CONF_LOG(LOG_NOTICE, "option '%s.%s' has no effect, " \
+ "use option '%s.%s' instead", \
+ &section[1], &old_item[1], \
+ &section[1], &new_item[1]); \
+ } \
+}
+
+static void check_mtu(knotd_conf_check_args_t *args, conf_val_t *xdp_listen)
+{
+#ifdef ENABLE_XDP
+ conf_val_t val = conf_get_txn(args->extra->conf, args->extra->txn,
+ C_SRV, C_UDP_MAX_PAYLOAD_IPV4);
+ if (val.code != KNOT_EOK) {
+ val = conf_get_txn(args->extra->conf, args->extra->txn,
+ C_SRV, C_UDP_MAX_PAYLOAD);
+ }
+ int64_t ipv4_max = conf_int(&val) + sizeof(struct udphdr) + 4 + // Eth. CRC
+ sizeof(struct iphdr) + sizeof(struct ethhdr);
+
+ val = conf_get_txn(args->extra->conf, args->extra->txn,
+ C_SRV, C_UDP_MAX_PAYLOAD_IPV6);
+ if (val.code != KNOT_EOK) {
+ val = conf_get_txn(args->extra->conf, args->extra->txn,
+ C_SRV, C_UDP_MAX_PAYLOAD);
+ }
+ int64_t ipv6_max = conf_int(&val) + sizeof(struct udphdr) + 4 + // Eth. CRC
+ sizeof(struct ipv6hdr) + sizeof(struct ethhdr);
+
+ if (ipv6_max > KNOT_XDP_MAX_MTU || ipv4_max > KNOT_XDP_MAX_MTU) {
+ CONF_LOG(LOG_WARNING, "maximum UDP payload not compatible with XDP MTU (%u)",
+ KNOT_XDP_MAX_MTU);
+ }
+
+ while (xdp_listen->code == KNOT_EOK) {
+ struct sockaddr_storage addr = conf_addr(xdp_listen, NULL);
+ conf_xdp_iface_t iface;
+ int ret = conf_xdp_iface(&addr, &iface);
+ if (ret != KNOT_EOK) {
+ CONF_LOG(LOG_WARNING, "failed to check XDP interface MTU");
+ return;
+ }
+ int mtu = knot_eth_mtu(iface.name);
+ if (mtu < 0) {
+ CONF_LOG(LOG_WARNING, "failed to read MTU of interface %s",
+ iface.name);
+ continue;
+ }
+ mtu += sizeof(struct ethhdr) + 4;
+ if (ipv6_max > mtu || ipv4_max > mtu) {
+ CONF_LOG(LOG_WARNING, "maximum UDP payload not compatible "
+ "with MTU of interface %s", iface.name);
+ }
+ conf_val_next(xdp_listen);
+ }
+#endif
+}
+
+int check_server(
+ knotd_conf_check_args_t *args)
+{
+ conf_val_t key_file = conf_get_txn(args->extra->conf, args->extra->txn,
+ C_SRV, C_KEY_FILE);
+ conf_val_t crt_file = conf_get_txn(args->extra->conf, args->extra->txn,
+ C_SRV, C_CERT_FILE);
+ if (key_file.code != crt_file.code) {
+ args->err_str = "both server certificate and key must be set";
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
+int check_xdp(
+ knotd_conf_check_args_t *args)
+{
+ conf_val_t xdp_listen = conf_get_txn(args->extra->conf, args->extra->txn,
+ C_XDP, C_LISTEN);
+ conf_val_t srv_listen = conf_get_txn(args->extra->conf, args->extra->txn,
+ C_SRV, C_LISTEN);
+ conf_val_t udp = conf_get_txn(args->extra->conf, args->extra->txn, C_XDP,
+ C_UDP);
+ conf_val_t tcp = conf_get_txn(args->extra->conf, args->extra->txn, C_XDP,
+ C_TCP);
+ conf_val_t quic = conf_get_txn(args->extra->conf, args->extra->txn, C_XDP,
+ C_QUIC);
+ if (xdp_listen.code == KNOT_EOK) {
+ if (!conf_bool(&udp) && !conf_bool(&tcp) && !conf_bool(&quic)) {
+ args->err_str = "XDP processing requires UDP, TCP, or QUIC enabled";
+ return KNOT_EINVAL;
+ }
+
+ if (srv_listen.code != KNOT_EOK && tcp.code != KNOT_EOK) {
+ CONF_LOG(LOG_WARNING, "TCP processing not available");
+ }
+ check_mtu(args, &xdp_listen);
+ }
+
+ if (conf_bool(&quic)) {
+#ifdef ENABLE_QUIC
+ conf_val_t port = conf_get_txn(args->extra->conf, args->extra->txn, C_XDP,
+ C_QUIC_PORT);
+ uint16_t quic_port = conf_int(&port);
+
+ while (xdp_listen.code == KNOT_EOK) {
+ conf_xdp_iface_t iface;
+ struct sockaddr_storage udp_addr = conf_addr(&xdp_listen, NULL);
+ if (conf_xdp_iface(&udp_addr, &iface) == KNOT_EOK && iface.port == quic_port) {
+ args->err_str = "QUIC has to listen on different port than UDP";
+ return KNOT_EINVAL;
+ }
+ conf_val_next(&xdp_listen);
+ }
+#else
+ args->err_str = "QUIC processing not available";
+ return KNOT_EINVAL;
+#endif // ENABLE_QUIC
+ }
+
+ return KNOT_EOK;
+}
+
+int check_keystore(
+ knotd_conf_check_args_t *args)
+{
+ conf_val_t backend = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_KEYSTORE,
+ C_BACKEND, args->id, args->id_len);
+ conf_val_t config = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_KEYSTORE,
+ C_CONFIG, args->id, args->id_len);
+ conf_val_t key_label = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_KEYSTORE,
+ C_KEY_LABEL, args->id, args->id_len);
+ if (conf_opt(&backend) == KEYSTORE_BACKEND_PKCS11 && conf_str(&config) == NULL) {
+ args->err_str = "no PKCS #11 configuration defined";
+ return KNOT_EINVAL;
+ }
+ if (conf_opt(&backend) != KEYSTORE_BACKEND_PKCS11 && conf_bool(&key_label)) {
+ args->err_str = "key labels not supported with the specified keystore";
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
+int check_policy(
+ knotd_conf_check_args_t *args)
+{
+ conf_val_t sts = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY,
+ C_SINGLE_TYPE_SIGNING, args->id, args->id_len);
+ conf_val_t alg = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY,
+ C_ALG, args->id, args->id_len);
+ conf_val_t ksk = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY,
+ C_KSK_SIZE, args->id, args->id_len);
+ conf_val_t zsk = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY,
+ C_ZSK_SIZE, args->id, args->id_len);
+ conf_val_t lifetime = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY,
+ C_RRSIG_LIFETIME, args->id, args->id_len);
+ conf_val_t refresh = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY,
+ C_RRSIG_REFRESH, args->id, args->id_len);
+ conf_val_t prerefresh = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY,
+ C_RRSIG_PREREFRESH, args->id, args->id_len);
+ conf_val_t prop_del = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY,
+ C_PROPAG_DELAY, args->id, args->id_len);
+ conf_val_t zsk_life = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY,
+ C_ZSK_LIFETIME, args->id, args->id_len);
+ conf_val_t ksk_life = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY,
+ C_KSK_LIFETIME, args->id, args->id_len);
+ conf_val_t dnskey_ttl = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY,
+ C_DNSKEY_TTL, args->id, args->id_len);
+ conf_val_t zone_max_ttl = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY,
+ C_ZONE_MAX_TTL, args->id, args->id_len);
+ conf_val_t nsec3 = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY,
+ C_NSEC3, args->id, args->id_len);
+ conf_val_t nsec3_iters = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY,
+ C_NSEC3_ITER, args->id, args->id_len);
+
+ unsigned algorithm = conf_opt(&alg);
+ if (algorithm < DNSSEC_KEY_ALGORITHM_RSA_SHA256) {
+ CONF_LOG(LOG_NOTICE, "algorithm %u is deprecated and shouldn't be used for DNSSEC signing",
+ algorithm);
+ }
+
+ int64_t ksk_size = conf_int(&ksk);
+ if (ksk_size != YP_NIL && !dnssec_algorithm_key_size_check(algorithm, ksk_size)) {
+ args->err_str = "KSK key size not compatible with the algorithm";
+ return KNOT_EINVAL;
+ }
+
+ int64_t zsk_size = conf_int(&zsk);
+ if (zsk_size != YP_NIL && !dnssec_algorithm_key_size_check(algorithm, zsk_size)) {
+ args->err_str = "ZSK key size not compatible with the algorithm";
+ return KNOT_EINVAL;
+ }
+
+ int64_t lifetime_val = conf_int(&lifetime);
+ int64_t refresh_val = conf_int(&refresh);
+ int64_t preref_val = conf_int(&prerefresh);
+ if (lifetime_val <= refresh_val + preref_val) {
+ args->err_str = "RRSIG refresh + pre-refresh has to be lower than RRSIG lifetime";
+ return KNOT_EINVAL;
+ }
+
+ bool sts_val = conf_bool(&sts);
+ int64_t prop_del_val = conf_int(&prop_del);
+ int64_t zsk_life_val = conf_int(&zsk_life);
+ int64_t ksk_life_val = conf_int(&ksk_life);
+ int64_t dnskey_ttl_val = conf_int(&dnskey_ttl);
+ if (dnskey_ttl_val == YP_NIL) {
+ dnskey_ttl_val = 0;
+ }
+ int64_t zone_max_ttl_val = conf_int(&zone_max_ttl);
+ if (zone_max_ttl_val == YP_NIL) {
+ zone_max_ttl_val = dnskey_ttl_val; // Better than 0.
+ }
+
+ if (sts_val) {
+ if (ksk_life_val != 0 && ksk_life_val < 2 * prop_del_val + dnskey_ttl_val + zone_max_ttl_val) {
+ args->err_str = "CSK lifetime too low according to propagation delay, DNSKEY TTL, "
+ "and maximum zone TTL";
+ return KNOT_EINVAL;
+ }
+ } else {
+ if (ksk_life_val != 0 && ksk_life_val < 2 * prop_del_val + 2 * dnskey_ttl_val) {
+ args->err_str = "KSK lifetime too low according to propagation delay and DNSKEY TTL";
+ return KNOT_EINVAL;
+ }
+ if (zsk_life_val != 0 && zsk_life_val < 2 * prop_del_val + dnskey_ttl_val + zone_max_ttl_val) {
+ args->err_str = "ZSK lifetime too low according to propagation delay, DNSKEY TTL, "
+ "and maximum zone TTL";
+ return KNOT_EINVAL;
+ }
+ }
+
+ conf_val_t cds_cdnskey = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY,
+ C_CDS_CDNSKEY, args->id, args->id_len);
+ conf_val_t ds_push = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY,
+ C_DS_PUSH, args->id, args->id_len);
+
+ if (conf_val_count(&ds_push) > 0 && conf_opt(&cds_cdnskey) == CDS_CDNSKEY_NONE) {
+ args->err_str = "DS push requires enabled CDS/CDNSKEY publication";
+ return KNOT_EINVAL;
+ }
+
+#ifndef HAVE_GNUTLS_REPRODUCIBLE
+ conf_val_t repro_sign = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY,
+ C_REPRO_SIGNING, args->id, args->id_len);
+ if (conf_bool(&repro_sign)) {
+ CONF_LOG(LOG_WARNING, "reproducible signing not available, signing normally");
+ }
+#endif
+
+ if (conf_bool(&nsec3)) {
+ uint16_t iters = conf_int(&nsec3_iters);
+ if (nsec3_iters.code != KNOT_EOK && iters != 0) {
+ CONF_LOG(LOG_WARNING, "policy[%s].nsec3-iterations defaults to %u, "
+ "since version 3.2 the default becomes 0", args->id, iters);
+ }
+ if (iters > 20) {
+ CONF_LOG(LOG_NOTICE, "policy[%s].nsec3-iterations=%u is too high, "
+ "the recommended value is 0", args->id, iters);
+ }
+ }
+
+ conf_val_t dnskey_mgmt = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY,
+ C_DNSKEY_MGMT, args->id, args->id_len);
+ conf_val_t offline_ksk = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY,
+ C_OFFLINE_KSK, args->id, args->id_len);
+ conf_val_t delete_dely = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY,
+ C_DELETE_DELAY, args->id, args->id_len);
+ if (conf_opt(&dnskey_mgmt) != DNSKEY_MGMT_FULL) {
+ if (conf_bool(&offline_ksk)) {
+ args->err_str = "incremental DNSKEY management can't be used with offline-ksk";
+ return KNOT_EINVAL;
+ }
+ if (conf_int(&delete_dely) <= 0) {
+ args->err_str = "incremental DNSKEY management requires configured delete-delay";
+ return KNOT_EINVAL;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+int check_key(
+ knotd_conf_check_args_t *args)
+{
+ conf_val_t alg = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_KEY,
+ C_ALG, args->id, args->id_len);
+ if (conf_val_count(&alg) == 0) {
+ args->err_str = "no key algorithm defined";
+ return KNOT_EINVAL;
+ }
+
+ conf_val_t secret = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_KEY,
+ C_SECRET, args->id, args->id_len);
+ if (conf_val_count(&secret) == 0) {
+ args->err_str = "no key secret defined";
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
+int check_acl(
+ knotd_conf_check_args_t *args)
+{
+ conf_val_t addr = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_ACL,
+ C_ADDR, args->id, args->id_len);
+ conf_val_t key = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_ACL,
+ C_KEY, args->id, args->id_len);
+ conf_val_t remote = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_ACL,
+ C_RMT, args->id, args->id_len);
+ if (remote.code != KNOT_ENOENT &&
+ (addr.code != KNOT_ENOENT || key.code != KNOT_ENOENT)) {
+ args->err_str = "specified ACL/remote together with address or key";
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
+int check_remote(
+ knotd_conf_check_args_t *args)
+{
+ conf_val_t addr = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_RMT,
+ C_ADDR, args->id, args->id_len);
+ if (conf_val_count(&addr) == 0) {
+ args->err_str = "no remote address defined";
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
+int check_remotes(
+ knotd_conf_check_args_t *args)
+{
+ conf_val_t remote = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_RMTS,
+ C_RMT, args->id, args->id_len);
+ if (remote.code != KNOT_EOK) {
+ args->err_str = "no remote defined";
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
+#define CHECK_DFLT(item, name) { \
+ conf_val_t val = conf_rawid_get_txn(args->extra->conf, args->extra->txn, \
+ C_TPL, item, args->id, args->id_len); \
+ if (val.code == KNOT_EOK) { \
+ args->err_str = name " in non-default template"; \
+ return KNOT_EINVAL; \
+ } \
+}
+
+int check_catalog_group(
+ knotd_conf_check_args_t *args)
+{
+ assert(args->data_len > 0);
+ if (args->data_len - 1 > CATALOG_GROUP_MAXLEN) {
+ args->err_str = "group name longer than 255 characters";
+ return KNOT_EINVAL;
+ }
+
+ return KNOT_EOK;
+}
+
+int check_template(
+ knotd_conf_check_args_t *args)
+{
+ if (!is_default_id(args->id, args->id_len)) {
+ CHECK_DFLT(C_GLOBAL_MODULE, "global module");
+ }
+
+ return KNOT_EOK;
+}
+
+#define CHECK_ZONE_INTERVALS(low_item, high_item) { \
+ conf_val_t high = conf_zone_get_txn(args->extra->conf, args->extra->txn, \
+ high_item, yp_dname(args->id)); \
+ if (high.code == KNOT_EOK) { \
+ conf_val_t low = conf_zone_get_txn(args->extra->conf, args->extra->txn, \
+ low_item, yp_dname(args->id)); \
+ if (low.code == KNOT_EOK && conf_int(&low) > conf_int(&high)) { \
+ if (snprintf(check_str, sizeof(check_str), "'%s' is higher than '%s'", \
+ &low_item[1], &high_item[1]) < 0) { \
+ check_str[0] = '\0'; \
+ } \
+ args->err_str = check_str; \
+ return KNOT_EINVAL; \
+ } \
+ } \
+}
+
+#define CHECK_CATZ_TPL(option, option_string) \
+{ \
+ conf_val_t val = conf_rawid_get_txn(args->extra->conf, args->extra->txn, \
+ C_TPL, option, catalog_tpl.data, \
+ catalog_tpl.len); \
+ if (val.code == KNOT_EOK) { \
+ args->err_str = "'" option_string "' in a catalog template"; \
+ return KNOT_EINVAL; \
+ } \
+}
+
+int check_zone(
+ knotd_conf_check_args_t *args)
+{
+ CHECK_ZONE_INTERVALS(C_REFRESH_MIN_INTERVAL, C_REFRESH_MAX_INTERVAL);
+ CHECK_ZONE_INTERVALS(C_RETRY_MIN_INTERVAL, C_RETRY_MAX_INTERVAL);
+ CHECK_ZONE_INTERVALS(C_EXPIRE_MIN_INTERVAL, C_EXPIRE_MAX_INTERVAL);
+
+ conf_val_t zf_load = conf_zone_get_txn(args->extra->conf, args->extra->txn,
+ C_ZONEFILE_LOAD, yp_dname(args->id));
+ if (conf_opt(&zf_load) == ZONEFILE_LOAD_DIFSE) {
+ conf_val_t journal = conf_zone_get_txn(args->extra->conf, args->extra->txn,
+ C_JOURNAL_CONTENT, yp_dname(args->id));
+ if (conf_opt(&journal) != JOURNAL_CONTENT_ALL) {
+ args->err_str = "'zonefile-load: difference-no-serial' requires 'journal-content: all'";
+ return KNOT_EINVAL;
+ }
+ }
+
+ conf_val_t validation = conf_zone_get_txn(args->extra->conf, args->extra->txn,
+ C_DNSSEC_VALIDATION, yp_dname(args->id));
+ if (conf_bool(&validation)) {
+ conf_val_t signing = conf_zone_get_txn(args->extra->conf, args->extra->txn,
+ C_DNSSEC_SIGNING, yp_dname(args->id));
+ if (conf_bool(&signing)) {
+ args->err_str = "'dnssec-validation' is not compatible with 'dnssec-signing'";
+ return KNOT_EINVAL;
+ }
+ }
+
+ conf_val_t catalog_role = conf_zone_get_txn(args->extra->conf, args->extra->txn,
+ C_CATALOG_ROLE, yp_dname(args->id));
+ conf_val_t catalog_tpl = conf_zone_get_txn(args->extra->conf, args->extra->txn,
+ C_CATALOG_TPL, yp_dname(args->id));
+ conf_val_t catalog_zone = conf_zone_get_txn(args->extra->conf, args->extra->txn,
+ C_CATALOG_ZONE, yp_dname(args->id));
+ conf_val_t catalog_serial = conf_zone_get_txn(args->extra->conf, args->extra->txn,
+ C_SERIAL_POLICY, yp_dname(args->id));
+
+ unsigned role = conf_opt(&catalog_role);
+ if ((bool)(role == CATALOG_ROLE_INTERPRET) != (bool)(catalog_tpl.code == KNOT_EOK)) {
+ args->err_str = "'catalog-role' must correspond to configured 'catalog-template'";
+ return KNOT_EINVAL;
+ }
+ if ((bool)(role == CATALOG_ROLE_MEMBER) != (bool)(catalog_zone.code == KNOT_EOK)) {
+ args->err_str = "'catalog-role' must correspond to configured 'catalog-zone'";
+ return KNOT_EINVAL;
+ }
+ if (role == CATALOG_ROLE_GENERATE &&
+ conf_opt(&catalog_serial) != SERIAL_POLICY_UNIXTIME && // Default doesn't harm.
+ catalog_serial.code == KNOT_EOK) {
+ args->err_str = "'serial-policy' must be 'unixtime' for generated catalog zones";
+ return KNOT_EINVAL;
+ }
+ if (role == CATALOG_ROLE_INTERPRET) {
+ conf_val(&catalog_tpl);
+ while (catalog_tpl.code == KNOT_EOK) {
+ CHECK_CATZ_TPL(C_CATALOG_TPL, "catalog-template");
+ CHECK_CATZ_TPL(C_CATALOG_ROLE, "catalog-role");
+ CHECK_CATZ_TPL(C_CATALOG_ZONE, "catalog-zone");
+ CHECK_CATZ_TPL(C_CATALOG_GROUP, "catalog-group");
+ conf_val_next(&catalog_tpl);
+ }
+ }
+
+ conf_val_t ds_push = conf_zone_get_txn(args->extra->conf, args->extra->txn,
+ C_DS_PUSH, yp_dname(args->id));
+ if (ds_push.code == KNOT_EOK) {
+ conf_val_t policy_id = conf_zone_get_txn(args->extra->conf, args->extra->txn,
+ C_DNSSEC_POLICY, yp_dname(args->id));
+ if (policy_id.code == KNOT_EOK) {
+ conf_val_t cds_cdnskey = conf_id_get_txn(args->extra->conf, args->extra->txn,
+ C_POLICY, C_CDS_CDNSKEY,
+ &policy_id);
+ if (conf_val_count(&ds_push) > 0 && conf_opt(&cds_cdnskey) == CDS_CDNSKEY_NONE) {
+ args->err_str = "DS push requires enabled CDS/CDNSKEY publication";
+ return KNOT_EINVAL;
+ }
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+static int glob_error(
+ const char *epath,
+ int eerrno)
+{
+ CONF_LOG(LOG_WARNING, "failed to access '%s' (%s)", epath,
+ knot_strerror(knot_map_errno_code(eerrno)));
+
+ return 0;
+}
+
+int include_file(
+ knotd_conf_check_args_t *args)
+{
+ if (args->data_len == 0) {
+ return KNOT_YP_ENODATA;
+ }
+
+ // This function should not be called in more threads.
+ static int depth = 0;
+ glob_t glob_buf = { 0 };
+ char *path = NULL;
+ int ret;
+
+ // Check for include loop.
+ if (depth++ > MAX_INCLUDE_DEPTH) {
+ CONF_LOG(LOG_ERR, "include loop detected");
+ ret = KNOT_EPARSEFAIL;
+ goto include_error;
+ }
+
+ // Prepare absolute include path.
+ if (args->data[0] == '/') {
+ path = sprintf_alloc("%.*s", (int)args->data_len, args->data);
+ } else {
+ const char *file_name = args->extra->file_name != NULL ?
+ args->extra->file_name : "./";
+ char *full_current_name = realpath(file_name, NULL);
+ if (full_current_name == NULL) {
+ ret = KNOT_ENOMEM;
+ goto include_error;
+ }
+
+ path = sprintf_alloc("%s/%.*s", dirname(full_current_name),
+ (int)args->data_len, args->data);
+ free(full_current_name);
+ }
+ if (path == NULL) {
+ ret = KNOT_ESPACE;
+ goto include_error;
+ }
+
+ // Evaluate include pattern (empty wildcard match is also valid).
+ ret = glob(path, 0, glob_error, &glob_buf);
+ if (ret != 0 && (ret != GLOB_NOMATCH || strchr(path, '*') == NULL)) {
+ ret = KNOT_EFILE;
+ goto include_error;
+ }
+
+ // Process glob result.
+ for (size_t i = 0; i < glob_buf.gl_pathc; i++) {
+ // Get file status.
+ struct stat file_stat;
+ if (stat(glob_buf.gl_pathv[i], &file_stat) != 0) {
+ CONF_LOG(LOG_WARNING, "failed to get file status for '%s'",
+ glob_buf.gl_pathv[i]);
+ continue;
+ }
+
+ // Ignore directory or non-regular file.
+ if (S_ISDIR(file_stat.st_mode)) {
+ continue;
+ } else if (!S_ISREG(file_stat.st_mode)) {
+ CONF_LOG(LOG_WARNING, "invalid include file '%s'",
+ glob_buf.gl_pathv[i]);
+ continue;
+ }
+
+ // Include regular file.
+ ret = conf_parse(args->extra->conf, args->extra->txn,
+ glob_buf.gl_pathv[i], true);
+ if (ret != KNOT_EOK) {
+ goto include_error;
+ }
+ }
+
+ ret = KNOT_EOK;
+include_error:
+ globfree(&glob_buf);
+ free(path);
+ depth--;
+
+ return ret;
+}
+
+int load_module(
+ knotd_conf_check_args_t *args)
+{
+ conf_val_t val = conf_rawid_get_txn(args->extra->conf, args->extra->txn,
+ C_MODULE, C_FILE, args->id, args->id_len);
+ const char *file_name = conf_str(&val);
+
+ char *mod_name = strndup((const char *)args->id, args->id_len);
+ if (mod_name == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ int ret = conf_mod_load_extra(args->extra->conf, mod_name, file_name,
+ args->extra->check ? MOD_TEMPORARY : MOD_EXPLICIT);
+ free(mod_name);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ // Update currently iterating item.
+ const yp_item_t *section = yp_schema_find(C_MODULE, NULL, args->extra->conf->schema);
+ assert(section);
+ args->item = section->var.g.id;
+
+ return ret;
+}
diff --git a/src/knot/conf/tools.h b/src/knot/conf/tools.h
new file mode 100644
index 0000000..a8875bd
--- /dev/null
+++ b/src/knot/conf/tools.h
@@ -0,0 +1,147 @@
+/* Copyright (C) 2022 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 <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "knot/conf/conf.h"
+#include "libknot/yparser/ypschema.h"
+
+typedef struct knotd_conf_check_extra {
+ conf_t *conf;
+ knot_db_txn_t *txn;
+ const char *file_name;
+ size_t line;
+ bool check; /*!< Indication of the confio check mode. */
+} knotd_conf_check_extra_t;
+
+int legacy_item(
+ knotd_conf_check_args_t *args
+);
+
+int conf_exec_callbacks(
+ knotd_conf_check_args_t *args
+);
+
+int mod_id_to_bin(
+ YP_TXT_BIN_PARAMS
+);
+
+int mod_id_to_txt(
+ YP_BIN_TXT_PARAMS
+);
+
+int rrtype_to_bin(
+ YP_TXT_BIN_PARAMS
+);
+
+int rrtype_to_txt(
+ YP_BIN_TXT_PARAMS
+);
+
+int rdname_to_bin(
+ YP_TXT_BIN_PARAMS
+);
+
+int rdname_to_txt(
+ YP_BIN_TXT_PARAMS
+);
+
+int check_ref(
+ knotd_conf_check_args_t *args
+);
+
+int check_ref_dflt(
+ knotd_conf_check_args_t *args
+);
+
+int check_listen(
+ knotd_conf_check_args_t *args
+);
+
+int check_xdp_listen(
+ knotd_conf_check_args_t *args
+);
+
+int check_database(
+ knotd_conf_check_args_t *args
+);
+
+int check_modref(
+ knotd_conf_check_args_t *args
+);
+
+int check_module_id(
+ knotd_conf_check_args_t *args
+);
+
+int check_file(
+ knotd_conf_check_args_t *args
+);
+
+int check_server(
+ knotd_conf_check_args_t *args
+);
+
+int check_xdp(
+ knotd_conf_check_args_t *args
+);
+
+int check_keystore(
+ knotd_conf_check_args_t *args
+);
+
+int check_policy(
+ knotd_conf_check_args_t *args
+);
+
+int check_key(
+ knotd_conf_check_args_t *args
+);
+
+int check_acl(
+ knotd_conf_check_args_t *args
+);
+
+int check_remote(
+ knotd_conf_check_args_t *args
+);
+
+int check_remotes(
+ knotd_conf_check_args_t *args
+);
+
+int check_catalog_group(
+ knotd_conf_check_args_t *args
+);
+
+int check_template(
+ knotd_conf_check_args_t *args
+);
+
+int check_zone(
+ knotd_conf_check_args_t *args
+);
+
+int include_file(
+ knotd_conf_check_args_t *args
+);
+
+int load_module(
+ knotd_conf_check_args_t *args
+);