diff options
Diffstat (limited to 'src/knot/conf/tools.c')
-rw-r--r-- | src/knot/conf/tools.c | 785 |
1 files changed, 785 insertions, 0 deletions
diff --git a/src/knot/conf/tools.c b/src/knot/conf/tools.c new file mode 100644 index 0000000..5add58f --- /dev/null +++ b/src/knot/conf/tools.c @@ -0,0 +1,785 @@ +/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <glob.h> +#include <inttypes.h> +#include <libgen.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#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/sockaddr.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 rrtype_to_bin( + YP_TXT_BIN_PARAMS) +{ + YP_CHECK_PARAMS_BIN; + + uint16_t type; + int ret = knot_rrtype_from_string((char *)in->position, &type); + if (ret != 0) { + return KNOT_EINVAL; + } + wire_ctx_write_u64(out, type); + + YP_CHECK_RET; +} + +int rrtype_to_txt( + YP_BIN_TXT_PARAMS) +{ + YP_CHECK_PARAMS_TXT; + + uint16_t type = (uint16_t)wire_ctx_read_u64(in); + int ret = knot_rrtype_to_string(type, (char *)out->position, out->size); + if (ret < 0) { + return KNOT_EINVAL; + } + wire_ctx_skip(out, ret); + + YP_CHECK_RET; +} + +int rdname_to_bin( + YP_TXT_BIN_PARAMS) +{ + YP_CHECK_PARAMS_BIN; + + int ret = yp_dname_to_bin(in, out, stop); + if (ret == KNOT_EOK && in->wire[in->size - 1] != '.') { + // If non-FQDN, trim off the zero label. + wire_ctx_skip(out, -1); + } + + YP_CHECK_RET; +} + +int rdname_to_txt( + YP_BIN_TXT_PARAMS) +{ + YP_CHECK_PARAMS_TXT; + + // Temporarily normalize the input. + if (in->wire[in->size - 1] == '\0') { + return yp_dname_to_txt(in, out); + } + + knot_dname_storage_t full_name; + wire_ctx_t ctx = wire_ctx_init(full_name, sizeof(full_name)); + wire_ctx_write(&ctx, in->wire, in->size); + wire_ctx_write(&ctx, "\0", 1); + wire_ctx_set_offset(&ctx, 0); + + int ret = yp_dname_to_txt(&ctx, out); + if (ret != KNOT_EOK) { + return ret; + } + + // Trim off the trailing dot. + wire_ctx_skip(out, -1); + + YP_CHECK_RET; +} + +int check_ref( + knotd_conf_check_args_t *args) +{ + const yp_item_t *ref = args->item->var.r.ref; + + // 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_listen( + knotd_conf_check_args_t *args) +{ + bool no_port; + struct sockaddr_storage ss = yp_addr(args->data, &no_port); + if (!no_port && sockaddr_port(&ss) == 0) { + args->err_str = "invalid port"; + return KNOT_EINVAL; + } + + return KNOT_EOK; +} + +int check_xdp( + knotd_conf_check_args_t *args) +{ +#ifndef ENABLE_XDP + args->err_str = "XDP is not available"; + return KNOT_ENOTSUP; +#else + bool no_port; + struct sockaddr_storage ss = yp_addr(args->data, &no_port); + conf_xdp_iface_t if_new; + int ret = conf_xdp_iface(&ss, &if_new); + if (ret != KNOT_EOK) { + args->err_str = "invalid XDP interface specification"; + return ret; + } + + conf_val_t xdp = conf_get_txn(args->extra->conf, args->extra->txn, C_SRV, + C_LISTEN_XDP); + size_t count = conf_val_count(&xdp); + while (xdp.code == KNOT_EOK && count-- > 1) { + struct sockaddr_storage addr = conf_addr(&xdp, NULL); + conf_xdp_iface_t if_prev; + ret = conf_xdp_iface(&addr, &if_prev); + if (ret != KNOT_EOK) { + return ret; + } + if (strcmp(if_new.name, if_prev.name) == 0) { + args->err_str = "duplicate XDP interface specification"; + return KNOT_EINVAL; + } + conf_val_next(&xdp); + } + + return KNOT_EOK; +#endif +} + +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; +} + +#define CHECK_LEGACY_NAME(section, old_item, new_item) { \ + conf_val_t val = conf_get_txn(args->extra->conf, args->extra->txn, \ + section, old_item); \ + if (val.code == KNOT_EOK) { \ + CONF_LOG(LOG_NOTICE, "option '%s.%s' is obsolete, " \ + "use option '%s.%s' instead", \ + §ion[1], &old_item[1], \ + §ion[1], &new_item[1]); \ + } \ +} + +#define CHECK_LEGACY_NAME_ID(section, old_item, new_item) { \ + conf_val_t val = conf_rawid_get_txn(args->extra->conf, args->extra->txn, \ + section, old_item, args->id, args->id_len); \ + if (val.code == KNOT_EOK) { \ + CONF_LOG(LOG_NOTICE, "option '%s.%s' is obsolete, " \ + "use option '%s.%s' instead", \ + §ion[1], &old_item[1], \ + §ion[1], &new_item[1]); \ + } \ +} + +int check_server( + knotd_conf_check_args_t *args) +{ + conf_val_t listen = conf_get_txn(args->extra->conf, args->extra->txn, C_SRV, + C_LISTEN); + conf_val_t xdp = conf_get_txn(args->extra->conf, args->extra->txn, C_SRV, + C_LISTEN_XDP); + if (xdp.code == KNOT_EOK && listen.code != KNOT_EOK) { + CONF_LOG(LOG_WARNING, "unable to process TCP queries due to XDP-only interfaces"); + } + + conf_val_t hshake = conf_get_txn(args->extra->conf, args->extra->txn, C_SRV, + C_TCP_HSHAKE_TIMEOUT); + if (hshake.code == KNOT_EOK) { + CONF_LOG(LOG_NOTICE, "option 'server.tcp-handshake-timeout' is no longer supported"); + } + + CHECK_LEGACY_NAME(C_SRV, C_TCP_REPLY_TIMEOUT, C_TCP_RMT_IO_TIMEOUT); + CHECK_LEGACY_NAME(C_SRV, C_MAX_TCP_CLIENTS, C_TCP_MAX_CLIENTS); + CHECK_LEGACY_NAME(C_SRV, C_MAX_UDP_PAYLOAD, C_UDP_MAX_PAYLOAD); + CHECK_LEGACY_NAME(C_SRV, C_MAX_IPV4_UDP_PAYLOAD, C_UDP_MAX_PAYLOAD_IPV4); + CHECK_LEGACY_NAME(C_SRV, C_MAX_IPV6_UDP_PAYLOAD, C_UDP_MAX_PAYLOAD_IPV6); + + 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 sts = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_SINGLE_TYPE_SIGNING, args->id, args->id_len); + conf_val_t alg = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_ALG, args->id, args->id_len); + conf_val_t ksk = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_KSK_SIZE, args->id, args->id_len); + conf_val_t zsk = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_ZSK_SIZE, args->id, args->id_len); + conf_val_t lifetime = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_RRSIG_LIFETIME, args->id, args->id_len); + conf_val_t refresh = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_RRSIG_REFRESH, args->id, args->id_len); + conf_val_t prerefresh = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_RRSIG_PREREFRESH, args->id, args->id_len); + conf_val_t prop_del = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_PROPAG_DELAY, args->id, args->id_len); + conf_val_t zsk_life = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_ZSK_LIFETIME, args->id, args->id_len); + conf_val_t ksk_life = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_KSK_LIFETIME, args->id, args->id_len); + conf_val_t dnskey_ttl = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_DNSKEY_TTL, args->id, args->id_len); + conf_val_t zone_max_ttl = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_ZONE_MAX_TLL, 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); + int64_t preref_val = conf_int(&prerefresh); + if (lifetime_val <= refresh_val + preref_val) { + args->err_str = "RRSIG refresh + pre-refresh has to be lower than RRSIG lifetime"; + return KNOT_EINVAL; + } + + bool sts_val = conf_bool(&sts); + int64_t prop_del_val = conf_int(&prop_del); + int64_t zsk_life_val = conf_int(&zsk_life); + int64_t ksk_life_val = conf_int(&ksk_life); + int64_t dnskey_ttl_val = conf_int(&dnskey_ttl); + if (dnskey_ttl_val == YP_NIL) { + dnskey_ttl_val = 0; + } + int64_t zone_max_ttl_val = conf_int(&zone_max_ttl); + if (zone_max_ttl_val == YP_NIL) { + zone_max_ttl_val = dnskey_ttl_val; // Better than 0. + } + + if (sts_val) { + if (ksk_life_val != 0 && ksk_life_val < 2 * prop_del_val + dnskey_ttl_val + zone_max_ttl_val) { + args->err_str = "CSK lifetime too low according to propagation delay, DNSKEY TTL, " + "and maximum zone TTL"; + return KNOT_EINVAL; + } + } else { + if (ksk_life_val != 0 && ksk_life_val < 2 * prop_del_val + 2 * dnskey_ttl_val) { + args->err_str = "KSK lifetime too low according to propagation delay and DNSKEY TTL"; + return KNOT_EINVAL; + } + if (zsk_life_val != 0 && zsk_life_val < 2 * prop_del_val + dnskey_ttl_val + zone_max_ttl_val) { + args->err_str = "ZSK lifetime too low according to propagation delay, DNSKEY TTL, " + "and maximum zone TTL"; + return KNOT_EINVAL; + } + } + + conf_val_t cds_cdnskey = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_CDS_CDNSKEY, args->id, args->id_len); + conf_val_t ds_push = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_DS_PUSH, args->id, args->id_len); + + if (conf_val_count(&ds_push) > 0 && conf_opt(&cds_cdnskey) == CDS_CDNSKEY_NONE) { + args->err_str = "DS push requires enabled CDS/CDNSKEY publication"; + return KNOT_EINVAL; + } + +#ifndef HAVE_GLNUTLS_REPRODUCIBLE + conf_val_t repro_sign = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_REPRO_SIGNING, args->id, args->id_len); + if (conf_bool(&repro_sign)) { + CONF_LOG(LOG_WARNING, "reproducible signing not available, signing normally"); + } +#endif + + 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; + } + + conf_val_t addr = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_ACL, + C_ADDR, args->id, args->id_len); + conf_val_t key = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_ACL, + C_KEY, args->id, args->id_len); + conf_val_t remote = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_ACL, + C_RMT, args->id, args->id_len); + if (remote.code != KNOT_ENOENT && + (addr.code != KNOT_ENOENT || key.code != KNOT_ENOENT)) { + args->err_str = "specified ACL/remote together with address or key"; + return KNOT_EINVAL; + } + + return KNOT_EOK; +} + +int check_remote( + knotd_conf_check_args_t *args) +{ + conf_val_t addr = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_RMT, + C_ADDR, args->id, args->id_len); + if (conf_val_count(&addr) == 0) { + args->err_str = "no remote address defined"; + return KNOT_EINVAL; + } + + return KNOT_EOK; +} + +#define CHECK_LEGACY_MOVED(old_item, new_item) { \ + conf_val_t val = conf_rawid_get_txn(args->extra->conf, args->extra->txn, \ + C_TPL, old_item, args->id, args->id_len); \ + if (val.code == KNOT_EOK) { \ + CONF_LOG(LOG_NOTICE, "option 'template.%s' is obsolete, " \ + "use option 'database.%s' instead", \ + &old_item[1], &new_item[1]); \ + } \ +} + +#define CHECK_DFLT(item, name) { \ + conf_val_t val = conf_rawid_get_txn(args->extra->conf, args->extra->txn, \ + C_TPL, item, args->id, args->id_len); \ + if (val.code == KNOT_EOK) { \ + args->err_str = name " in non-default template"; \ + return KNOT_EINVAL; \ + } \ +} + +int check_template( + knotd_conf_check_args_t *args) +{ + CHECK_LEGACY_MOVED(C_TIMER_DB, C_TIMER_DB); + CHECK_LEGACY_MOVED(C_MAX_TIMER_DB_SIZE, C_TIMER_DB_MAX_SIZE); + CHECK_LEGACY_MOVED(C_JOURNAL_DB, C_JOURNAL_DB); + CHECK_LEGACY_MOVED(C_JOURNAL_DB_MODE, C_JOURNAL_DB_MODE); + CHECK_LEGACY_MOVED(C_MAX_JOURNAL_DB_SIZE, C_JOURNAL_DB_MAX_SIZE); + CHECK_LEGACY_MOVED(C_KASP_DB, C_KASP_DB); + CHECK_LEGACY_MOVED(C_MAX_KASP_DB_SIZE, C_KASP_DB_MAX_SIZE); + + CHECK_LEGACY_NAME_ID(C_TPL, C_MAX_ZONE_SIZE, C_ZONE_MAX_SIZE); + CHECK_LEGACY_NAME_ID(C_TPL, C_MAX_REFRESH_INTERVAL, C_REFRESH_MAX_INTERVAL); + CHECK_LEGACY_NAME_ID(C_TPL, C_MIN_REFRESH_INTERVAL, C_REFRESH_MIN_INTERVAL); + CHECK_LEGACY_NAME_ID(C_TPL, C_MAX_JOURNAL_DEPTH, C_JOURNAL_MAX_DEPTH); + CHECK_LEGACY_NAME_ID(C_TPL, C_MAX_JOURNAL_USAGE, C_JOURNAL_MAX_USAGE); + + conf_val_t any = conf_rawid_get_txn(args->extra->conf, args->extra->txn, + C_TPL, C_DISABLE_ANY, args->id, args->id_len); + if (any.code == KNOT_EOK) { + CONF_LOG(LOG_NOTICE, "option 'disable-any' is deprecated and has no effect"); + } + + if (is_default_id(args->id, args->id_len)) { + conf_val_t db_storage = conf_get_txn(args->extra->conf, args->extra->txn, + C_DB, C_STORAGE); + conf_val_t tpl_storage = conf_rawid_get_txn(args->extra->conf, args->extra->txn, + C_TPL, C_STORAGE, args->id, args->id_len); + if (db_storage.code != KNOT_EOK && tpl_storage.code == KNOT_EOK && + strcmp(conf_str(&tpl_storage), STORAGE_DIR) != 0) { + CONF_LOG(LOG_NOTICE, "non-default 'template[default].storage' detected, " + "please configure also 'db.storage' to avoid compatibility " + "issues with future versions"); + } + } else { + CHECK_DFLT(C_GLOBAL_MODULE, "global module"); + } + + return KNOT_EOK; +} + +int check_zone( + knotd_conf_check_args_t *args) +{ + CHECK_LEGACY_NAME_ID(C_ZONE, C_MAX_ZONE_SIZE, C_ZONE_MAX_SIZE); + CHECK_LEGACY_NAME_ID(C_ZONE, C_MAX_REFRESH_INTERVAL, C_REFRESH_MAX_INTERVAL); + CHECK_LEGACY_NAME_ID(C_ZONE, C_MIN_REFRESH_INTERVAL, C_REFRESH_MIN_INTERVAL); + CHECK_LEGACY_NAME_ID(C_ZONE, C_MAX_JOURNAL_DEPTH, C_JOURNAL_MAX_DEPTH); + CHECK_LEGACY_NAME_ID(C_ZONE, C_MAX_JOURNAL_USAGE, C_JOURNAL_MAX_USAGE); + + conf_val_t any = conf_rawid_get_txn(args->extra->conf, args->extra->txn, + C_TPL, C_DISABLE_ANY, args->id, args->id_len); + if (any.code == KNOT_EOK) { + CONF_LOG(LOG_NOTICE, "option 'disable-any' is deprecated and has no effect"); + } + + conf_val_t zf_load = conf_zone_get_txn(args->extra->conf, args->extra->txn, + C_ZONEFILE_LOAD, yp_dname(args->id)); + conf_val_t journal = conf_zone_get_txn(args->extra->conf, args->extra->txn, + C_JOURNAL_CONTENT, yp_dname(args->id)); + if (conf_opt(&zf_load) == ZONEFILE_LOAD_DIFSE && + conf_opt(&journal) != JOURNAL_CONTENT_ALL) { + CONF_LOG(LOG_NOTICE, "'zonefile-load: difference-no-serial' will require 'journal-content: all' " + "in versions >= 3.1"); + } + + conf_val_t signing = conf_zone_get_txn(args->extra->conf, args->extra->txn, + C_DNSSEC_SIGNING, yp_dname(args->id)); + conf_val_t validation = conf_zone_get_txn(args->extra->conf, args->extra->txn, + C_DNSSEC_VALIDATION, yp_dname(args->id)); + if (conf_bool(&signing) == true && conf_bool(&validation) == true) { + args->err_str = "'dnssec-validation' is not compatible with 'dnssec-signing'"; + return KNOT_EINVAL; + } + + conf_val_t catalog_role = conf_zone_get_txn(args->extra->conf, args->extra->txn, + C_CATALOG_ROLE, yp_dname(args->id)); + conf_val_t catalog_tpl = conf_zone_get_txn(args->extra->conf, args->extra->txn, + C_CATALOG_TPL, yp_dname(args->id)); + if ((bool)(conf_opt(&catalog_role) == CATALOG_ROLE_INTERPRET) != + (bool)(catalog_tpl.code == KNOT_EOK)) { + args->err_str = "'catalog-role' must correspond to configured 'catalog-template'"; + return KNOT_EINVAL; + } + + return KNOT_EOK; +} + +static int glob_error( + const char *epath, + int eerrno) +{ + CONF_LOG(LOG_WARNING, "failed to access '%s' (%s)", epath, + knot_strerror(knot_map_errno_code(eerrno))); + + return 0; +} + +int include_file( + knotd_conf_check_args_t *args) +{ + if (args->data_len == 0) { + return KNOT_YP_ENODATA; + } + + // This function should not be called in more threads. + static int depth = 0; + glob_t glob_buf = { 0 }; + char *path = NULL; + int ret; + + // Check for include loop. + if (depth++ > MAX_INCLUDE_DEPTH) { + CONF_LOG(LOG_ERR, "include loop detected"); + ret = KNOT_EPARSEFAIL; + goto include_error; + } + + // Prepare absolute include path. + if (args->data[0] == '/') { + path = sprintf_alloc("%.*s", (int)args->data_len, args->data); + } else { + const char *file_name = args->extra->file_name != NULL ? + args->extra->file_name : "./"; + char *full_current_name = realpath(file_name, NULL); + if (full_current_name == NULL) { + ret = KNOT_ENOMEM; + goto include_error; + } + + path = sprintf_alloc("%s/%.*s", dirname(full_current_name), + (int)args->data_len, args->data); + free(full_current_name); + } + if (path == NULL) { + ret = KNOT_ESPACE; + goto include_error; + } + + // Evaluate include pattern (empty wildcard match is also valid). + ret = glob(path, 0, glob_error, &glob_buf); + if (ret != 0 && (ret != GLOB_NOMATCH || strchr(path, '*') == NULL)) { + ret = KNOT_EFILE; + goto include_error; + } + + // Process glob result. + for (size_t i = 0; i < glob_buf.gl_pathc; i++) { + // Get file status. + struct stat file_stat; + if (stat(glob_buf.gl_pathv[i], &file_stat) != 0) { + CONF_LOG(LOG_WARNING, "failed to get file status for '%s'", + glob_buf.gl_pathv[i]); + continue; + } + + // Ignore directory or non-regular file. + if (S_ISDIR(file_stat.st_mode)) { + continue; + } else if (!S_ISREG(file_stat.st_mode)) { + CONF_LOG(LOG_WARNING, "invalid include file '%s'", + glob_buf.gl_pathv[i]); + continue; + } + + // Include regular file. + ret = conf_parse(args->extra->conf, args->extra->txn, + glob_buf.gl_pathv[i], true); + if (ret != KNOT_EOK) { + goto include_error; + } + } + + ret = KNOT_EOK; +include_error: + globfree(&glob_buf); + free(path); + depth--; + + return ret; +} + +int load_module( + knotd_conf_check_args_t *args) +{ + conf_val_t val = conf_rawid_get_txn(args->extra->conf, args->extra->txn, + C_MODULE, C_FILE, args->id, args->id_len); + const char *file_name = conf_str(&val); + + char *mod_name = strndup((const char *)args->id, args->id_len); + if (mod_name == NULL) { + return KNOT_ENOMEM; + } + + int ret = conf_mod_load_extra(args->extra->conf, mod_name, file_name, + args->extra->check); + 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; +} |