diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:24:08 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:24:08 +0000 |
commit | f449f278dd3c70e479a035f50a9bb817a9b433ba (patch) | |
tree | 8ca2bfb785dda9bb4d573acdf9b42aea9cd51383 /src/knot/conf | |
parent | Initial commit. (diff) | |
download | knot-upstream.tar.xz knot-upstream.zip |
Adding upstream version 3.2.6.upstream/3.2.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/knot/conf')
-rw-r--r-- | src/knot/conf/base.c | 1056 | ||||
-rw-r--r-- | src/knot/conf/base.h | 322 | ||||
-rw-r--r-- | src/knot/conf/conf.c | 1469 | ||||
-rw-r--r-- | src/knot/conf/conf.h | 939 | ||||
-rw-r--r-- | src/knot/conf/confdb.c | 951 | ||||
-rw-r--r-- | src/knot/conf/confdb.h | 230 | ||||
-rw-r--r-- | src/knot/conf/confio.c | 1612 | ||||
-rw-r--r-- | src/knot/conf/confio.h | 231 | ||||
-rw-r--r-- | src/knot/conf/migration.c | 81 | ||||
-rw-r--r-- | src/knot/conf/migration.h | 30 | ||||
-rw-r--r-- | src/knot/conf/module.c | 509 | ||||
-rw-r--r-- | src/knot/conf/module.h | 126 | ||||
-rw-r--r-- | src/knot/conf/schema.c | 530 | ||||
-rw-r--r-- | src/knot/conf/schema.h | 279 | ||||
-rw-r--r-- | src/knot/conf/tools.c | 1069 | ||||
-rw-r--r-- | src/knot/conf/tools.h | 147 |
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", \ + §ion[1], &old_item[1], \ + §ion[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", \ + §ion[1], &old_item[1], \ + §ion[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 +); |