diff options
Diffstat (limited to '')
-rw-r--r-- | src/knot/conf/base.c | 924 | ||||
-rw-r--r-- | src/knot/conf/base.h | 279 | ||||
-rw-r--r-- | src/knot/conf/conf.c | 1216 | ||||
-rw-r--r-- | src/knot/conf/conf.h | 715 | ||||
-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 | 1514 | ||||
-rw-r--r-- | src/knot/conf/confio.h | 218 | ||||
-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 | 460 | ||||
-rw-r--r-- | src/knot/conf/module.h | 110 | ||||
-rw-r--r-- | src/knot/conf/schema.c | 344 | ||||
-rw-r--r-- | src/knot/conf/schema.h | 154 | ||||
-rw-r--r-- | src/knot/conf/tools.c | 551 | ||||
-rw-r--r-- | src/knot/conf/tools.h | 107 |
16 files changed, 7884 insertions, 0 deletions
diff --git a/src/knot/conf/base.c b/src/knot/conf/base.c new file mode 100644 index 0000000..7fd8ba9 --- /dev/null +++ b/src/knot/conf/base.c @@ -0,0 +1,924 @@ +/* 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 <http://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); +} + +void conf_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 void init_cache( + conf_t *conf) +{ + conf_val_t val = conf_get(conf, C_SRV, C_MAX_IPV4_UDP_PAYLOAD); + if (val.code != KNOT_EOK) { + val = conf_get(conf, C_SRV, C_MAX_UDP_PAYLOAD); + } + conf->cache.srv_max_ipv4_udp_payload = conf_int(&val); + + val = conf_get(conf, C_SRV, C_MAX_IPV6_UDP_PAYLOAD); + if (val.code != KNOT_EOK) { + val = conf_get(conf, C_SRV, C_MAX_UDP_PAYLOAD); + } + conf->cache.srv_max_ipv6_udp_payload = conf_int(&val); + + val = conf_get(conf, C_SRV, C_TCP_HSHAKE_TIMEOUT); + conf->cache.srv_tcp_hshake_timeout = 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_REPLY_TIMEOUT); + conf->cache.srv_tcp_reply_timeout = conf_int(&val); + + val = conf_get(conf, C_SRV, C_MAX_TCP_CLIENTS); + conf->cache.srv_max_tcp_clients = conf_int(&val); + + val = conf_get(conf, C_CTL, C_TIMEOUT); + conf->cache.ctl_timeout = conf_int(&val) * 1000; + + conf->cache.srv_nsid = conf_get(conf, C_SRV, C_NSID); + + val = conf_get(conf, C_SRV, C_ECS); + conf->cache.use_ecs = conf_bool(&val); + + val = conf_get(conf, C_SRV, C_ANS_ROTATION); + conf->cache.srv_ans_rotate = conf_bool(&val); +} + +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->api = knot_db_lmdb_api(); + struct knot_db_lmdb_opts lmdb_opts = KNOT_DB_LMDB_OPTS_INITIALIZER; + lmdb_opts.mapsize = max_conf_size; + 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)) { + conf_refresh_hostname(out); + } + + // Initialize cached values. + init_cache(out); + + // 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), false); + 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); + } + + // Initialize cached values. + init_cache(out); + + 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) +{ + CONF_LOG_LINE(parser->file.name, parser->line_count, + "item '%s'%s%s%s (%s)", + parser->key, + (parser->data_len > 0) ? ", value '" : "", + (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, + "%s '%s' (%s)", args->item->name + 1, buff, + 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) +{ + 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); + + // 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..b7ef61c --- /dev/null +++ b/src/knot/conf/base.h @@ -0,0 +1,279 @@ +/* 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 <http://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 "contrib/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 concurrent DB readers. */ +#define CONF_MAX_DB_READERS 630 + +/*! 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; + +/*! Query module context. */ +typedef struct { + /*! Module interface. */ + const knotd_mod_api_t *api; + /*! Shared library dlopen handler. */ + void *lib_handle; + /*! Indication of a temporary module created during confio check. */ + bool temporary; +} module_t; + +dynarray_declare(mod, module_t *, DYNARRAY_VISIBILITY_PUBLIC, 16) +dynarray_declare(old_schema, yp_item_t *, DYNARRAY_VISIBILITY_PUBLIC, 16) + +/*! 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; + + /*! 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 { + int16_t srv_max_ipv4_udp_payload; + int16_t srv_max_ipv6_udp_payload; + int32_t srv_tcp_hshake_timeout; + int32_t srv_tcp_idle_timeout; + int32_t srv_tcp_reply_timeout; + int32_t srv_max_tcp_clients; + int32_t ctl_timeout; + conf_val_t srv_nsid; + bool use_ecs; + bool srv_ans_rotate; + } 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; +} 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 +); + +/*! + * Refreshes cached hostname. + * + * \param[in] conf Configuration. + */ +void conf_refresh_hostname( + 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. + * + * \return Error code, KNOT_EOK if success. + */ +int conf_import( + conf_t *conf, + const char *input, + bool is_file +); + +/*! + * 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..8bdac68 --- /dev/null +++ b/src/knot/conf/conf.c @@ -0,0 +1,1216 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> +#include <grp.h> +#include <pwd.h> +#include <stdio.h> + +#include "knot/conf/base.h" +#include "knot/conf/confdb.h" +#include "knot/common/log.h" +#include "knot/server/dthreads.h" +#include "libknot/libknot.h" +#include "libknot/yparser/yptrafo.h" +#include "contrib/macros.h" +#include "contrib/sockaddr.h" +#include "contrib/string.h" +#include "contrib/wire_ctx.h" +#include "contrib/openbsd/strlcat.h" + +#define DBG_LOG(err) CONF_LOG(LOG_DEBUG, "%s (%s)", __func__, knot_strerror((err))); + +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_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); + break; + 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: + // Use the default template. + conf_db_get(conf, txn, C_TPL, key1_name, CONF_DEFAULT_ID + 1, + CONF_DEFAULT_ID[0], &val); + } + + 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; + } + + 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; + } 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; + } +} + +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; +} + +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); + 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((struct sockaddr *)&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; +} + +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); + 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((struct sockaddr *)addr, + (struct sockaddr *)&min, mask)) { + return true; + } + } else { + if (sockaddr_range_match((struct sockaddr *)addr, + (struct sockaddr *)&min, + (struct sockaddr *)&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); + if (path == NULL) { + return NULL; + } else if (path[0] == '/') { + return strdup(path); + } else { + char *abs_path; + if (base_dir == NULL) { + char *cwd = realpath("./", NULL); + abs_path = sprintf_alloc("%s/%s", cwd, path); + free(cwd); + } else { + abs_path = sprintf_alloc("%s/%s", base_dir, path); + } + return abs_path; + } +} + +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); + + 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) +{ + 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) +{ + 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) +{ + 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_journalfile_txn( + conf_t *conf, + knot_db_txn_t *txn) +{ + conf_val_t val = conf_default_get_txn(conf, txn, C_STORAGE); + char *storage = conf_abs_path(&val, NULL); + val = conf_default_get_txn(conf, txn, C_JOURNAL_DB); + char *journaldir = conf_abs_path(&val, storage); + free(storage); + + return journaldir; +} + +char* conf_kaspdir_txn( + conf_t *conf, + knot_db_txn_t *txn) +{ + conf_val_t val = conf_default_get_txn(conf, txn, C_STORAGE); + char *storage = conf_abs_path(&val, NULL); + val = conf_default_get_txn(conf, txn, C_KASP_DB); + char *kaspdir = conf_abs_path(&val, storage); + free(storage); + + return kaspdir; +} + +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); + if (workers == YP_NIL) { + return dt_optimal_size(); + } + + 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); + if (workers == YP_NIL) { + return MAX(conf_udp_threads_txn(conf, txn) * 2, CONF_XFERS); + } + + 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); + if (workers == YP_NIL) { + return MIN(dt_optimal_size(), CONF_XFERS); + } + + return workers; +} + +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); + + return out; +} diff --git a/src/knot/conf/conf.h b/src/knot/conf/conf.h new file mode 100644 index 0000000..bc3183e --- /dev/null +++ b/src/knot/conf/conf.h @@ -0,0 +1,715 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <sys/socket.h> + +#include "knot/conf/base.h" +#include "knot/conf/schema.h" + +#define CONF_XFERS 10 + +/*! 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; +} 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 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; + +/*! + * 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 automaticaly 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 item value. + * + * \param[in] val Item value. + */ +void conf_val_next( + 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 +); + +/*! + * 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 +); + +/*! + * 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 zonef ile 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 journal file path. + * + * \note The result must be explicitly deallocated. + * + * \param[in] conf Configuration. + * \param[in] txn Configuration DB transaction. + * + * \return Absolute journal file path string pointer. + */ +char* conf_journalfile_txn( + conf_t *conf, + knot_db_txn_t *txn); +static inline char* conf_journalfile( + conf_t *conf) +{ + return conf_journalfile_txn(conf, &conf->read_txn); +} + +char* conf_kaspdir_txn( + conf_t *conf, + knot_db_txn_t *txn); +static inline char* conf_kaspdir( + conf_t *conf) +{ + return conf_kaspdir_txn(conf, &conf->read_txn); +} + +/*! + * 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 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 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); + +} diff --git a/src/knot/conf/confdb.c b/src/knot/conf/confdb.c new file mode 100644 index 0000000..364142a --- /dev/null +++ b/src/knot/conf/confdb.c @@ -0,0 +1,951 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <time.h> +#include <unistd.h> + +#include "knot/conf/confdb.h" +#include "libknot/errcode.h" +#include "libknot/yparser/yptrafo.h" +#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 identificators. + * [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 code = ((uint8_t *)iter_val.data)[0]; + codes[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 && + 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..04e222d --- /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 <http://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..bd24800 --- /dev/null +++ b/src/knot/conf/confio.c @@ -0,0 +1,1514 @@ +/* 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 <http://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; + } + + // 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, + conf_io_t *io) +{ + if (io == NULL) { + return KNOT_EINVAL; + } + + assert(conf() != NULL); + + // 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, NULL, NULL, NULL); + if (ret != KNOT_EOK) { + goto list_error; + } + + yp_node_t *node = &ctx->nodes[ctx->current]; + + // Check for non-group item. + if (node->item->type != YP_TGRP) { + ret = KNOT_ENOTSUP; + 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_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, (const char *)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, (const char *)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; + + 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_val_t bin; + 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_type_t)(*trie_it_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..460bfbd --- /dev/null +++ b/src/knot/conf/confio.h @@ -0,0 +1,218 @@ +/* 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 <http://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; +}; + +/*! + * 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[out] io Operation output. + * + * \return Error code, KNOT_EOK if success. + */ +int conf_io_list( + const char *key0, + 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..a15edae --- /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 <http://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..c885ff4 --- /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 <http://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..6bb013f --- /dev/null +++ b/src/knot/conf/module.c @@ -0,0 +1,460 @@ +/* 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 <http://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" + +dynarray_define(mod, module_t *, DYNARRAY_VISIBILITY_PUBLIC) +dynarray_define(old_schema, yp_item_t *, DYNARRAY_VISIBILITY_PUBLIC) + +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; + } + } + + // Second, search in dynamic modules. + dynarray_foreach(mod, module_t *, module, conf->modules) { + if ((*module) != NULL && (*module)->temporary == temporary && + 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) { + 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], + false); + } + + 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, + bool temporary) +{ + 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), 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->temporary = temporary; + + 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; + } + + dynarray_foreach(old_schema, yp_item_t *, schema, conf->old_schemas) { + yp_schema_free(*schema); + } + old_schema_dynarray_free(&conf->old_schemas); + + dynarray_foreach(mod, module_t *, module, conf->modules) { + if ((*module) != NULL && (*module)->temporary) { + unload_shared((*module)); + *module = NULL; // Cannot remove from dynarray. + } + } +} + +void conf_mod_unload_shared( + conf_t *conf) +{ + if (conf == NULL) { + return; + } + + 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, + 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, 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 = NULL, *next = NULL; + WALK_LIST_DELSAFE(mod, next, *query_modules) { + if (mod->api->unload != NULL) { + mod->api->unload(mod); + } + query_module_close(mod); + } + init_list(query_modules); +} diff --git a/src/knot/conf/module.h b/src/knot/conf/module.h new file mode 100644 index 0000000..2dc353a --- /dev/null +++ b/src/knot/conf/module.h @@ -0,0 +1,110 @@ +/* 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 <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include "knot/conf/base.h" + +/*! + * 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] temporary Mark module as temporary. + * + * \return Error code, KNOT_EOK if success. + */ +int conf_mod_load_extra( + conf_t *conf, + const char *mod_name, + const char *file_name, + bool temporary +); + +/*! + * 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, + 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 +); diff --git a/src/knot/conf/schema.c b/src/knot/conf/schema.c new file mode 100644 index 0000000..560f005 --- /dev/null +++ b/src/knot/conf/schema.c @@ -0,0 +1,344 @@ +/* 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 <http://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/journal/journal.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 GIGA(1) +#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" }, + { DNSSEC_KEY_ALGORITHM_ED25519, "ed25519" }, + /* Obsolete items. */ + { 3, "dsa" }, + { 6, "dsa-nsec3-sha1" }, + { 0, NULL } +}; + +const knot_lookup_t child_record[] = { + { CHILD_RECORDS_NONE, "none" }, + { CHILD_RECORDS_EMPTY, "delete-dnssec" }, + { CHILD_RECORDS_ROLLOVER, "rollover" }, + { CHILD_RECORDS_ALWAYS, "always" }, + { 0, NULL } +}; + +const knot_lookup_t acl_actions[] = { + { ACL_ACTION_NOTIFY, "notify" }, + { ACL_ACTION_TRANSFER, "transfer" }, + { ACL_ACTION_UPDATE, "update" }, + { 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 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 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, 255, YP_NIL } }, + { C_TCP_WORKERS, YP_TINT, YP_VINT = { 1, 255, YP_NIL } }, + { C_BG_WORKERS, YP_TINT, YP_VINT = { 1, 255, YP_NIL } }, + { C_ASYNC_START, YP_TBOOL, YP_VNONE }, + { C_TCP_HSHAKE_TIMEOUT, YP_TINT, YP_VINT = { 0, INT32_MAX, 5, YP_STIME } }, + { C_TCP_IDLE_TIMEOUT, YP_TINT, YP_VINT = { 0, INT32_MAX, 20, YP_STIME } }, + { C_TCP_REPLY_TIMEOUT, YP_TINT, YP_VINT = { 0, INT32_MAX, 10, YP_STIME } }, + { C_MAX_TCP_CLIENTS, YP_TINT, YP_VINT = { 0, INT32_MAX, 100 } }, + { C_MAX_UDP_PAYLOAD, YP_TINT, YP_VINT = { KNOT_EDNS_MIN_DNSSEC_PAYLOAD, + KNOT_EDNS_MAX_UDP_PAYLOAD, + KNOT_EDNS_MAX_UDP_PAYLOAD, YP_SSIZE } }, + { C_MAX_IPV4_UDP_PAYLOAD, YP_TINT, YP_VINT = { KNOT_EDNS_MIN_DNSSEC_PAYLOAD, + KNOT_EDNS_MAX_UDP_PAYLOAD, + KNOT_EDNS_MAX_UDP_PAYLOAD, YP_SSIZE } }, + { C_MAX_IPV6_UDP_PAYLOAD, YP_TINT, YP_VINT = { KNOT_EDNS_MIN_DNSSEC_PAYLOAD, + KNOT_EDNS_MAX_UDP_PAYLOAD, + KNOT_EDNS_MAX_UDP_PAYLOAD, YP_SSIZE } }, + { C_LISTEN, YP_TADDR, YP_VADDR = { 53 }, YP_FMULTI }, + { C_COMMENT, YP_TSTR, YP_VNONE }, + { C_ECS, YP_TBOOL, YP_VNONE }, + { C_ANS_ROTATION, YP_TBOOL, 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 }, + { 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_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_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_ACTION, YP_TOPT, YP_VOPT = { acl_actions, ACL_ACTION_NONE }, YP_FMULTI }, + { C_DENY, YP_TBOOL, YP_VNONE }, + { C_COMMENT, YP_TSTR, YP_VNONE }, + { NULL } +}; + +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_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 }, YP_FMULTI | CONF_IO_FRLD_ZONES, + { check_ref } }, + { C_CHK_INTERVAL, YP_TINT, YP_VINT = { 1, UINT32_MAX, HOURS(1), YP_STIME }, + CONF_IO_FRLD_ZONES }, + { C_TIMEOUT, YP_TINT, YP_VINT = { 1, UINT32_MAX, 0, YP_STIME }, + CONF_IO_FRLD_ZONES }, + { 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_KSK_SHARED, 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_DNSKEY_TTL, YP_TINT, YP_VINT = { 0, UINT32_MAX, YP_NIL, 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_KSK_LIFETIME, YP_TINT, YP_VINT = { 0, UINT32_MAX, 0, YP_STIME }, + CONF_IO_FRLD_ZONES }, + { C_PROPAG_DELAY, YP_TINT, YP_VINT = { 0, UINT32_MAX, HOURS(1), YP_STIME }, + CONF_IO_FRLD_ZONES }, + { C_RRSIG_LIFETIME, YP_TINT, YP_VINT = { 1, UINT32_MAX, DAYS(14), YP_STIME }, + CONF_IO_FRLD_ZONES }, + { C_RRSIG_REFRESH, YP_TINT, YP_VINT = { 1, UINT32_MAX, DAYS(7), YP_STIME }, + CONF_IO_FRLD_ZONES }, + { C_NSEC3, YP_TBOOL, YP_VNONE, CONF_IO_FRLD_ZONES }, + { C_NSEC3_ITER, YP_TINT, YP_VINT = { 0, UINT16_MAX, 10 }, 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_KSK_SBM, YP_TREF, YP_VREF = { C_SBM }, CONF_IO_FRLD_ZONES, + { check_ref } }, + { C_CHILD_RECORDS, YP_TOPT, YP_VOPT = { child_record, CHILD_RECORDS_ALWAYS } }, + { 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 }, 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 }, YP_FMULTI, { check_ref } }, \ + { C_ACL, YP_TREF, YP_VREF = { C_ACL }, YP_FMULTI, { check_ref } }, \ + { C_SEM_CHECKS, YP_TBOOL, YP_VNONE, FLAGS }, \ + { C_DISABLE_ANY, YP_TBOOL, YP_VNONE }, \ + { C_ZONEFILE_SYNC, YP_TINT, YP_VINT = { -1, INT32_MAX, 0, YP_STIME } }, \ + { C_JOURNAL_CONTENT, YP_TOPT, YP_VOPT = { journal_content, JOURNAL_CONTENT_CHANGES } }, \ + { C_ZONEFILE_LOAD, YP_TOPT, YP_VOPT = { zonefile_load, ZONEFILE_LOAD_WHOLE } }, \ + { C_MAX_ZONE_SIZE, YP_TINT, YP_VINT = { 0, SSIZE_MAX, SSIZE_MAX, YP_SSIZE }, FLAGS }, \ + { C_MAX_JOURNAL_USAGE, YP_TINT, YP_VINT = { KILO(40), SSIZE_MAX, MEGA(100), YP_SSIZE } }, \ + { C_MAX_JOURNAL_DEPTH, YP_TINT, YP_VINT = { 2, SSIZE_MAX, SSIZE_MAX } }, \ + { C_DNSSEC_SIGNING, YP_TBOOL, YP_VNONE, FLAGS }, \ + { C_DNSSEC_POLICY, YP_TREF, YP_VREF = { C_POLICY }, FLAGS, { check_ref_dflt } }, \ + { C_SERIAL_POLICY, YP_TOPT, YP_VOPT = { serial_policies, SERIAL_POLICY_INCREMENT } }, \ + { C_REQUEST_EDNS_OPTION, YP_TDATA, YP_VDATA = { 0, NULL, edns_opt_to_bin, edns_opt_to_txt } }, \ + { C_MAX_REFRESH_INTERVAL,YP_TINT, YP_VINT = { 2, UINT32_MAX, UINT32_MAX, YP_STIME } }, \ + { C_MIN_REFRESH_INTERVAL,YP_TINT, YP_VINT = { 2, UINT32_MAX, 2, YP_STIME } }, \ + { 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 }, \ + +static const yp_item_t desc_template[] = { + { C_ID, YP_TSTR, YP_VNONE, CONF_IO_FREF }, + ZONE_ITEMS(CONF_IO_FRLD_ZONES) + { 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 } }, + { C_TIMER_DB, YP_TSTR, YP_VSTR = { "timers" }, CONF_IO_FRLD_ZONES }, + { C_MAX_TIMER_DB_SIZE, YP_TINT, YP_VINT = { MEGA(1), VIRT_MEM_LIMIT(GIGA(100)), + MEGA(100), YP_SSIZE }, CONF_IO_FRLD_ZONES }, + { C_JOURNAL_DB, YP_TSTR, YP_VSTR = { "journal" }, CONF_IO_FRLD_SRV }, + { C_JOURNAL_DB_MODE, YP_TOPT, YP_VOPT = { journal_modes, JOURNAL_MODE_ROBUST }, + CONF_IO_FRLD_SRV }, + { C_MAX_JOURNAL_DB_SIZE, YP_TINT, YP_VINT = { JOURNAL_MIN_FSLIMIT, VIRT_MEM_LIMIT(TERA(100)), + VIRT_MEM_LIMIT(GIGA(20)), YP_SSIZE }, + CONF_IO_FRLD_SRV }, + { C_KASP_DB, YP_TSTR, YP_VSTR = { "keys" }, CONF_IO_FRLD_SRV }, + { C_MAX_KASP_DB_SIZE, YP_TINT, YP_VINT = { MEGA(5), VIRT_MEM_LIMIT(GIGA(100)), + MEGA(500), YP_SSIZE }, CONF_IO_FRLD_SRV }, + { 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_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_KEYSTORE, YP_TGRP, YP_VGRP = { desc_keystore }, YP_FMULTI, { check_keystore } }, + { C_KEY, YP_TGRP, YP_VGRP = { desc_key }, YP_FMULTI, { check_key } }, + { C_ACL, YP_TGRP, YP_VGRP = { desc_acl }, YP_FMULTI, { check_acl } }, + { C_RMT, YP_TGRP, YP_VGRP = { desc_remote }, YP_FMULTI, { check_remote } }, + { 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..8ba6941 --- /dev/null +++ b/src/knot/conf/schema.h @@ -0,0 +1,154 @@ +/* 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 <http://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_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_BACKEND "\x07""backend" +#define C_BG_WORKERS "\x12""background-workers" +#define C_CHILD_RECORDS "\x13""cds-cdnskey-publish" +#define C_CHK_INTERVAL "\x0E""check-interval" +#define C_COMMENT "\x07""comment" +#define C_CONFIG "\x06""config" +#define C_CTL "\x07""control" +#define C_DDNS_MASTER "\x0B""ddns-master" +#define C_DENY "\x04""deny" +#define C_DISABLE_ANY "\x0B""disable-any" +#define C_DNSKEY_TTL "\x0A""dnskey-ttl" +#define C_DNSSEC_POLICY "\x0D""dnssec-policy" +#define C_DNSSEC_SIGNING "\x0E""dnssec-signing" +#define C_DOMAIN "\x06""domain" +#define C_ECS "\x12""edns-client-subnet" +#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_MODE "\x0F""journal-db-mode" +#define C_KASP_DB "\x07""kasp-db" +#define C_KEY "\x03""key" +#define C_KEYSTORE "\x08""keystore" +#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_MAX_IPV4_UDP_PAYLOAD "\x14""max-ipv4-udp-payload" +#define C_MAX_IPV6_UDP_PAYLOAD "\x14""max-ipv6-udp-payload" +#define C_MAX_JOURNAL_DB_SIZE "\x13""max-journal-db-size" +#define C_MAX_JOURNAL_DEPTH "\x11""max-journal-depth" +#define C_MAX_JOURNAL_USAGE "\x11""max-journal-usage" +#define C_MAX_KASP_DB_SIZE "\x10""max-kasp-db-size" +#define C_MAX_REFRESH_INTERVAL "\x14""max-refresh-interval" +#define C_MAX_TCP_CLIENTS "\x0F""max-tcp-clients" +#define C_MAX_TIMER_DB_SIZE "\x11""max-timer-db-size" +#define C_MAX_UDP_PAYLOAD "\x0F""max-udp-payload" +#define C_MAX_ZONE_SIZE "\x0D""max-zone-size" +#define C_MIN_REFRESH_INTERVAL "\x14""min-refresh-interval" +#define C_MODULE "\x06""module" +#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_PARENT "\x06""parent" +#define C_PIDFILE "\x07""pidfile" +#define C_POLICY "\x06""policy" +#define C_PROPAG_DELAY "\x11""propagation-delay" +#define C_REQUEST_EDNS_OPTION "\x13""request-edns-option" +#define C_RMT "\x06""remote" +#define C_RRSIG_LIFETIME "\x0E""rrsig-lifetime" +#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_SINGLE_TYPE_SIGNING "\x13""single-type-signing" +#define C_SRV "\x06""server" +#define C_STATS "\x0A""statistics" +#define C_STORAGE "\x07""storage" +#define C_TARGET "\x06""target" +#define C_TCP_HSHAKE_TIMEOUT "\x15""tcp-handshake-timeout" +#define C_TCP_IDLE_TIMEOUT "\x10""tcp-idle-timeout" +#define C_TCP_REPLY_TIMEOUT "\x11""tcp-reply-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_TPL "\x08""template" +#define C_UDP_WORKERS "\x0B""udp-workers" +#define C_USER "\x04""user" +#define C_VERSION "\x07""version" +#define C_VIA "\x03""via" +#define C_ZONE "\x04""zone" +#define C_ZONEFILE_LOAD "\x0D""zonefile-load" +#define C_ZONEFILE_SYNC "\x0D""zonefile-sync" +#define C_ZSK_LIFETIME "\x0C""zsk-lifetime" +#define C_ZSK_SIZE "\x08""zsk-size" + +enum { + KEYSTORE_BACKEND_PEM = 1, + KEYSTORE_BACKEND_PKCS11 = 2, +}; + +enum { + CHILD_RECORDS_NONE = 0, + CHILD_RECORDS_EMPTY = 1, + CHILD_RECORDS_ROLLOVER = 2, + CHILD_RECORDS_ALWAYS = 3, +}; + +enum { + SERIAL_POLICY_INCREMENT = 1, + SERIAL_POLICY_UNIXTIME = 2, + SERIAL_POLICY_DATESERIAL = 3, +}; + +enum { + JOURNAL_CONTENT_NONE = 0, + JOURNAL_CONTENT_CHANGES = 1, + JOURNAL_CONTENT_ALL = 2, +}; + +enum { + ZONEFILE_LOAD_NONE = 0, + ZONEFILE_LOAD_DIFF = 1, + ZONEFILE_LOAD_WHOLE = 2, + ZONEFILE_LOAD_DIFSE = 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..3ab365e --- /dev/null +++ b/src/knot/conf/tools.c @@ -0,0 +1,551 @@ +/* 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 <http://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> + +#include "libdnssec/key.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 "contrib/string.h" +#include "contrib/wire_ctx.h" + +#define MAX_INCLUDE_DEPTH 5 + +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 edns_opt_to_bin( + YP_TXT_BIN_PARAMS) +{ + YP_CHECK_PARAMS_BIN; + + // Check for "code:[value]" format. + const uint8_t *pos = (uint8_t *)strchr((char *)in->position, ':'); + if (pos == NULL || pos >= stop) { + return KNOT_EINVAL; + } + + // Write option code. + int ret = yp_int_to_bin(in, out, pos, 0, UINT16_MAX, YP_SNONE); + if (ret != KNOT_EOK) { + return ret; + } + + // Skip the separator. + wire_ctx_skip(in, sizeof(uint8_t)); + + // Write option data. + ret = yp_hex_to_bin(in, out, stop); + if (ret != KNOT_EOK) { + return ret; + } + + YP_CHECK_RET; +} + +int edns_opt_to_txt( + YP_BIN_TXT_PARAMS) +{ + YP_CHECK_PARAMS_TXT; + + // Write option code. + int ret = yp_int_to_txt(in, out, YP_SNONE); + if (ret != KNOT_EOK) { + return ret; + } + + // Write the separator. + wire_ctx_write_u8(out, ':'); + + // Write option data. + ret = yp_hex_to_txt(in, out); + if (ret != KNOT_EOK) { + return ret; + } + + YP_CHECK_RET; +} + +int check_ref( + knotd_conf_check_args_t *args) +{ + const yp_item_t *ref = args->item->var.r.ref; + + // Try to find a referenced block with the id. + if (!conf_rawid_exists_txn(args->extra->conf, args->extra->txn, ref->name, + args->data, args->data_len)) { + 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_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_server( + knotd_conf_check_args_t *args) +{ + 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); + + if (conf_opt(&backend) == KEYSTORE_BACKEND_PKCS11 && conf_str(&config) == NULL) { + args->err_str = "no PKCS #11 configuration defined"; + return KNOT_EINVAL; + } + + return KNOT_EOK; +} + +int check_policy( + knotd_conf_check_args_t *args) +{ + 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 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); + + unsigned algorithm = conf_opt(&alg); + if (algorithm == 3 || algorithm == 6) { + args->err_str = "DSA algorithm no longer supported"; + return KNOT_EINVAL; + } + + 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); + if (lifetime_val <= refresh_val) { + args->err_str = "RRSIG refresh has to be lower than RRSIG lifetime"; + return KNOT_EINVAL; + } + + 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; + } + + if (zsk_life_val != 0 && zsk_life_val < 2 * prop_del_val + dnskey_ttl_val) { + args->err_str = "ZSK lifetime too low according to propagation delay and DNSKEY TTL"; + return KNOT_EINVAL; + } + 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; + } + + 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 action = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_ACL, + C_ACTION, args->id, args->id_len); + conf_val_t deny = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_ACL, + C_DENY, args->id, args->id_len); + if (conf_val_count(&action) == 0 && conf_val_count(&deny) == 0) { + args->err_str = "no ACL action defined"; + 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_template( + knotd_conf_check_args_t *args) +{ + // Stop if the default template. + if (is_default_id(args->id, args->id_len)) { + return KNOT_EOK; + } + + conf_val_t val; + #define CHECK_DFLT(item, name) \ + 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; \ + } + + CHECK_DFLT(C_GLOBAL_MODULE, "global module"); + CHECK_DFLT(C_TIMER_DB, "timer database path"); + CHECK_DFLT(C_MAX_TIMER_DB_SIZE, "timer database maximum size"); + CHECK_DFLT(C_JOURNAL_DB, "journal database path"); + CHECK_DFLT(C_JOURNAL_DB_MODE, "journal database mode"); + CHECK_DFLT(C_MAX_JOURNAL_DB_SIZE, "journal database maximum size"); + CHECK_DFLT(C_KASP_DB, "KASP database path"); + CHECK_DFLT(C_MAX_KASP_DB_SIZE, "KASP database maximum size"); + + return KNOT_EOK; +} + +int check_zone( + knotd_conf_check_args_t *args) +{ + 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); + 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..5283c0f --- /dev/null +++ b/src/knot/conf/tools.h @@ -0,0 +1,107 @@ +/* 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 <http://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 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 edns_opt_to_bin( + YP_TXT_BIN_PARAMS +); + +int edns_opt_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_modref( + knotd_conf_check_args_t *args +); + +int check_module_id( + knotd_conf_check_args_t *args +); + +int check_server( + 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_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 +); |