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