diff options
Diffstat (limited to 'src/knot/conf/tools.c')
-rw-r--r-- | src/knot/conf/tools.c | 1069 |
1 files changed, 1069 insertions, 0 deletions
diff --git a/src/knot/conf/tools.c b/src/knot/conf/tools.c new file mode 100644 index 0000000..6823a05 --- /dev/null +++ b/src/knot/conf/tools.c @@ -0,0 +1,1069 @@ +/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <glob.h> +#include <inttypes.h> +#include <libgen.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> +#ifdef ENABLE_XDP +#include <netinet/in.h> +#include <linux/ip.h> +#include <linux/ipv6.h> +#include <linux/udp.h> +#endif + +#include "libdnssec/key.h" +#include "knot/catalog/catalog_db.h" +#include "knot/conf/tools.h" +#include "knot/conf/conf.h" +#include "knot/conf/module.h" +#include "knot/conf/schema.h" +#include "knot/common/log.h" +#include "libknot/errcode.h" +#include "libknot/yparser/yptrafo.h" +#include "libknot/xdp.h" +#include "contrib/files.h" +#include "contrib/sockaddr.h" +#include "contrib/string.h" +#include "contrib/wire_ctx.h" + +#define MAX_INCLUDE_DEPTH 5 + +char check_str[1024]; + +int legacy_item( + knotd_conf_check_args_t *args) +{ + CONF_LOG(LOG_NOTICE, "line %zu, option '%s.%s' is obsolete and has no effect", + args->extra->line, args->item->parent->name + 1, + args->item->name + 1); + + return KNOT_EOK; +} + +static bool is_default_id( + const uint8_t *id, + size_t id_len) +{ + return id_len == CONF_DEFAULT_ID[0] && + memcmp(id, CONF_DEFAULT_ID + 1, id_len) == 0; +} + +int conf_exec_callbacks( + knotd_conf_check_args_t *args) +{ + if (args == NULL) { + return KNOT_EINVAL; + } + + for (size_t i = 0; i < YP_MAX_MISC_COUNT; i++) { + int (*fcn)(knotd_conf_check_args_t *) = args->item->misc[i]; + if (fcn == NULL) { + break; + } + + int ret = fcn(args); + if (ret != KNOT_EOK) { + return ret; + } + } + + return KNOT_EOK; +} + +int mod_id_to_bin( + YP_TXT_BIN_PARAMS) +{ + YP_CHECK_PARAMS_BIN; + + // Check for "mod_name/mod_id" format. + const uint8_t *pos = (uint8_t *)strchr((char *)in->position, '/'); + if (pos == in->position) { + // Missing module name. + return KNOT_EINVAL; + } else if (pos >= stop - 1) { + // Missing module identifier after slash. + return KNOT_EINVAL; + } + + // Write mod_name in the yp_name_t format. + uint8_t name_len = (pos != NULL) ? (pos - in->position) : + wire_ctx_available(in); + wire_ctx_write_u8(out, name_len); + wire_ctx_write(out, in->position, name_len); + wire_ctx_skip(in, name_len); + + // Check for mod_id. + if (pos != NULL) { + // Skip the separator. + wire_ctx_skip(in, sizeof(uint8_t)); + + // Write mod_id as a zero terminated string. + int ret = yp_str_to_bin(in, out, stop); + if (ret != KNOT_EOK) { + return ret; + } + } + + YP_CHECK_RET; +} + +int mod_id_to_txt( + YP_BIN_TXT_PARAMS) +{ + YP_CHECK_PARAMS_TXT; + + // Write mod_name. + uint8_t name_len = wire_ctx_read_u8(in); + wire_ctx_write(out, in->position, name_len); + wire_ctx_skip(in, name_len); + + // Check for mod_id. + if (wire_ctx_available(in) > 0) { + // Write the separator. + wire_ctx_write_u8(out, '/'); + + // Write mod_id. + int ret = yp_str_to_txt(in, out); + if (ret != KNOT_EOK) { + return ret; + } + } + + YP_CHECK_RET; +} + +int rrtype_to_bin( + YP_TXT_BIN_PARAMS) +{ + YP_CHECK_PARAMS_BIN; + + uint16_t type; + int ret = knot_rrtype_from_string((char *)in->position, &type); + if (ret != 0) { + return KNOT_EINVAL; + } + wire_ctx_write_u64(out, type); + + YP_CHECK_RET; +} + +int rrtype_to_txt( + YP_BIN_TXT_PARAMS) +{ + YP_CHECK_PARAMS_TXT; + + uint16_t type = (uint16_t)wire_ctx_read_u64(in); + int ret = knot_rrtype_to_string(type, (char *)out->position, out->size); + if (ret < 0) { + return KNOT_EINVAL; + } + wire_ctx_skip(out, ret); + + YP_CHECK_RET; +} + +int rdname_to_bin( + YP_TXT_BIN_PARAMS) +{ + YP_CHECK_PARAMS_BIN; + + int ret = yp_dname_to_bin(in, out, stop); + if (ret == KNOT_EOK && in->wire[in->size - 1] != '.') { + // If non-FQDN, trim off the zero label. + wire_ctx_skip(out, -1); + } + + YP_CHECK_RET; +} + +int rdname_to_txt( + YP_BIN_TXT_PARAMS) +{ + YP_CHECK_PARAMS_TXT; + + // Temporarily normalize the input. + if (in->wire[in->size - 1] == '\0') { + return yp_dname_to_txt(in, out); + } + + knot_dname_storage_t full_name; + wire_ctx_t ctx = wire_ctx_init(full_name, sizeof(full_name)); + wire_ctx_write(&ctx, in->wire, in->size); + wire_ctx_write(&ctx, "\0", 1); + wire_ctx_set_offset(&ctx, 0); + + int ret = yp_dname_to_txt(&ctx, out); + if (ret != KNOT_EOK) { + return ret; + } + + // Trim off the trailing dot. + wire_ctx_skip(out, -1); + + YP_CHECK_RET; +} + +int check_ref( + knotd_conf_check_args_t *args) +{ + const yp_item_t *ref = args->item->var.r.ref; + const yp_item_t *ref2 = args->item->var.r.grp_ref; + + bool found1 = false, found2 = false; + + // Try to find the id in the first section. + found1 = conf_rawid_exists_txn(args->extra->conf, args->extra->txn, + ref->name, args->data, args->data_len); + if (ref2 != NULL) { + // Try to find the id in the second section if supported. + found2 = conf_rawid_exists_txn(args->extra->conf, args->extra->txn, + ref2->name, args->data, args->data_len); + } + + if (found1 == found2) { + if (found1) { + args->err_str = "ambiguous reference"; + return KNOT_ENOENT; + } else { + args->err_str = "invalid reference"; + return KNOT_ENOENT; + } + } + + return KNOT_EOK; +} + +int check_ref_dflt( + knotd_conf_check_args_t *args) +{ + if (check_ref(args) != KNOT_EOK && !is_default_id(args->data, args->data_len)) { + args->err_str = "invalid reference"; + return KNOT_ENOENT; + } + + return KNOT_EOK; +} + +int check_listen( + knotd_conf_check_args_t *args) +{ + bool no_port; + struct sockaddr_storage ss = yp_addr(args->data, &no_port); + if (!no_port && sockaddr_port(&ss) == 0) { + args->err_str = "invalid port"; + return KNOT_EINVAL; + } + + return KNOT_EOK; +} + +int check_xdp_listen( + knotd_conf_check_args_t *args) +{ +#ifndef ENABLE_XDP + args->err_str = "XDP is not available"; + return KNOT_ENOTSUP; +#else + bool no_port; + struct sockaddr_storage ss = yp_addr(args->data, &no_port); + conf_xdp_iface_t if_new; + int ret = conf_xdp_iface(&ss, &if_new); + if (ret != KNOT_EOK) { + args->err_str = "invalid XDP interface specification"; + return ret; + } else if (!no_port && if_new.port == 0) { + args->err_str = "invalid port"; + return KNOT_EINVAL; + } + + conf_val_t xdp = conf_get_txn(args->extra->conf, args->extra->txn, C_XDP, + C_LISTEN); + size_t count = conf_val_count(&xdp); + while (xdp.code == KNOT_EOK && count-- > 1) { + struct sockaddr_storage addr = conf_addr(&xdp, NULL); + conf_xdp_iface_t if_prev; + ret = conf_xdp_iface(&addr, &if_prev); + if (ret != KNOT_EOK) { + return ret; + } + if (strcmp(if_new.name, if_prev.name) == 0) { + args->err_str = "duplicate XDP interface specification"; + return KNOT_EINVAL; + } + conf_val_next(&xdp); + } + + return KNOT_EOK; +#endif +} + +static int dir_exists(const char *dir) +{ + struct stat st; + if (stat(dir, &st) != 0) { + return knot_map_errno(); + } else if (!S_ISDIR(st.st_mode)) { + return KNOT_ENOTDIR; + } else if (access(dir, W_OK) != 0) { + return knot_map_errno(); + } else { + return KNOT_EOK; + } +} + +static int dir_can_create(const char *dir) +{ + int ret = dir_exists(dir); + if (ret == KNOT_ENOENT) { + return KNOT_EOK; + } else { + return ret; + } +} + +static void check_db( + knotd_conf_check_args_t *args, + const yp_name_t *db_type, + int (*check_fun)(const char *), + const char *desc) +{ + if (db_type != NULL) { + conf_val_t val = conf_get_txn(args->extra->conf, args->extra->txn, + C_DB, db_type); + if (val.code != KNOT_EOK) { + // Don't check implicit database values. + return; + } + } + + char *db = conf_db_txn(args->extra->conf, args->extra->txn, db_type); + int ret = check_fun(db); + if (ret != KNOT_EOK) { + CONF_LOG(LOG_WARNING, "%s '%s' %s", desc, db, + (ret == KNOT_EACCES ? "not writable" : knot_strerror(ret))); + } + free(db); +} + +int check_database( + knotd_conf_check_args_t *args) +{ + check_db(args, NULL, dir_exists, "database storage"); + check_db(args, C_TIMER_DB, dir_can_create, "timer database"); + check_db(args, C_JOURNAL_DB, dir_can_create, "journal database"); + check_db(args, C_KASP_DB, dir_can_create, "KASP database"); + check_db(args, C_CATALOG_DB, dir_can_create, "catalog database"); + + return KNOT_EOK; +} + +int check_modref( + knotd_conf_check_args_t *args) +{ + const yp_name_t *mod_name = (const yp_name_t *)args->data; + const uint8_t *id = args->data + 1 + args->data[0]; + size_t id_len = args->data_len - 1 - args->data[0]; + + // Check if the module is ever available. + const module_t *mod = conf_mod_find(args->extra->conf, mod_name + 1, + mod_name[0], args->extra->check); + if (mod == NULL) { + args->err_str = "unknown module"; + return KNOT_EINVAL; + } + + // Check if the module requires some configuration. + if (id_len == 0) { + if (mod->api->flags & KNOTD_MOD_FLAG_OPT_CONF) { + return KNOT_EOK; + } else { + args->err_str = "missing module configuration"; + return KNOT_YP_ENOID; + } + } + + // Try to find a module with the id. + if (!conf_rawid_exists_txn(args->extra->conf, args->extra->txn, mod_name, + id, id_len)) { + args->err_str = "invalid module reference"; + return KNOT_ENOENT; + } + + return KNOT_EOK; +} + +int check_module_id( + knotd_conf_check_args_t *args) +{ + const size_t len = strlen(KNOTD_MOD_NAME_PREFIX); + + if (strncmp((const char *)args->id, KNOTD_MOD_NAME_PREFIX, len) != 0) { + args->err_str = "required 'mod-' prefix"; + return KNOT_EINVAL; + } + + return KNOT_EOK; +} + +int check_file( + knotd_conf_check_args_t *args) +{ + char *path = abs_path((const char *)args->data, CONFIG_DIR); + + struct stat st; + int ret = stat(path, &st); + free(path); + + if (ret != 0) { + args->err_str = "invalid file"; + return KNOT_EINVAL; + } else if(!S_ISREG(st.st_mode)) { + args->err_str = "not a file"; + return KNOT_EINVAL; + } else { + return KNOT_EOK; + } +} + +#define CHECK_LEGACY_NAME(section, old_item, new_item) { \ + conf_val_t val = conf_get_txn(args->extra->conf, args->extra->txn, \ + section, old_item); \ + if (val.code == KNOT_EOK) { \ + CONF_LOG(LOG_NOTICE, "option '%s.%s' has no effect, " \ + "use option '%s.%s' instead", \ + §ion[1], &old_item[1], \ + §ion[1], &new_item[1]); \ + } \ +} + +#define CHECK_LEGACY_NAME_ID(section, old_item, new_item) { \ + conf_val_t val = conf_rawid_get_txn(args->extra->conf, args->extra->txn, \ + section, old_item, args->id, args->id_len); \ + if (val.code == KNOT_EOK) { \ + CONF_LOG(LOG_NOTICE, "option '%s.%s' has no effect, " \ + "use option '%s.%s' instead", \ + §ion[1], &old_item[1], \ + §ion[1], &new_item[1]); \ + } \ +} + +static void check_mtu(knotd_conf_check_args_t *args, conf_val_t *xdp_listen) +{ +#ifdef ENABLE_XDP + conf_val_t val = conf_get_txn(args->extra->conf, args->extra->txn, + C_SRV, C_UDP_MAX_PAYLOAD_IPV4); + if (val.code != KNOT_EOK) { + val = conf_get_txn(args->extra->conf, args->extra->txn, + C_SRV, C_UDP_MAX_PAYLOAD); + } + int64_t ipv4_max = conf_int(&val) + sizeof(struct udphdr) + 4 + // Eth. CRC + sizeof(struct iphdr) + sizeof(struct ethhdr); + + val = conf_get_txn(args->extra->conf, args->extra->txn, + C_SRV, C_UDP_MAX_PAYLOAD_IPV6); + if (val.code != KNOT_EOK) { + val = conf_get_txn(args->extra->conf, args->extra->txn, + C_SRV, C_UDP_MAX_PAYLOAD); + } + int64_t ipv6_max = conf_int(&val) + sizeof(struct udphdr) + 4 + // Eth. CRC + sizeof(struct ipv6hdr) + sizeof(struct ethhdr); + + if (ipv6_max > KNOT_XDP_MAX_MTU || ipv4_max > KNOT_XDP_MAX_MTU) { + CONF_LOG(LOG_WARNING, "maximum UDP payload not compatible with XDP MTU (%u)", + KNOT_XDP_MAX_MTU); + } + + while (xdp_listen->code == KNOT_EOK) { + struct sockaddr_storage addr = conf_addr(xdp_listen, NULL); + conf_xdp_iface_t iface; + int ret = conf_xdp_iface(&addr, &iface); + if (ret != KNOT_EOK) { + CONF_LOG(LOG_WARNING, "failed to check XDP interface MTU"); + return; + } + int mtu = knot_eth_mtu(iface.name); + if (mtu < 0) { + CONF_LOG(LOG_WARNING, "failed to read MTU of interface %s", + iface.name); + continue; + } + mtu += sizeof(struct ethhdr) + 4; + if (ipv6_max > mtu || ipv4_max > mtu) { + CONF_LOG(LOG_WARNING, "maximum UDP payload not compatible " + "with MTU of interface %s", iface.name); + } + conf_val_next(xdp_listen); + } +#endif +} + +int check_server( + knotd_conf_check_args_t *args) +{ + conf_val_t key_file = conf_get_txn(args->extra->conf, args->extra->txn, + C_SRV, C_KEY_FILE); + conf_val_t crt_file = conf_get_txn(args->extra->conf, args->extra->txn, + C_SRV, C_CERT_FILE); + if (key_file.code != crt_file.code) { + args->err_str = "both server certificate and key must be set"; + return KNOT_EINVAL; + } + + return KNOT_EOK; +} + +int check_xdp( + knotd_conf_check_args_t *args) +{ + conf_val_t xdp_listen = conf_get_txn(args->extra->conf, args->extra->txn, + C_XDP, C_LISTEN); + conf_val_t srv_listen = conf_get_txn(args->extra->conf, args->extra->txn, + C_SRV, C_LISTEN); + conf_val_t udp = conf_get_txn(args->extra->conf, args->extra->txn, C_XDP, + C_UDP); + conf_val_t tcp = conf_get_txn(args->extra->conf, args->extra->txn, C_XDP, + C_TCP); + conf_val_t quic = conf_get_txn(args->extra->conf, args->extra->txn, C_XDP, + C_QUIC); + if (xdp_listen.code == KNOT_EOK) { + if (!conf_bool(&udp) && !conf_bool(&tcp) && !conf_bool(&quic)) { + args->err_str = "XDP processing requires UDP, TCP, or QUIC enabled"; + return KNOT_EINVAL; + } + + if (srv_listen.code != KNOT_EOK && tcp.code != KNOT_EOK) { + CONF_LOG(LOG_WARNING, "TCP processing not available"); + } + check_mtu(args, &xdp_listen); + } + + if (conf_bool(&quic)) { +#ifdef ENABLE_QUIC + conf_val_t port = conf_get_txn(args->extra->conf, args->extra->txn, C_XDP, + C_QUIC_PORT); + uint16_t quic_port = conf_int(&port); + + while (xdp_listen.code == KNOT_EOK) { + conf_xdp_iface_t iface; + struct sockaddr_storage udp_addr = conf_addr(&xdp_listen, NULL); + if (conf_xdp_iface(&udp_addr, &iface) == KNOT_EOK && iface.port == quic_port) { + args->err_str = "QUIC has to listen on different port than UDP"; + return KNOT_EINVAL; + } + conf_val_next(&xdp_listen); + } +#else + args->err_str = "QUIC processing not available"; + return KNOT_EINVAL; +#endif // ENABLE_QUIC + } + + return KNOT_EOK; +} + +int check_keystore( + knotd_conf_check_args_t *args) +{ + conf_val_t backend = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_KEYSTORE, + C_BACKEND, args->id, args->id_len); + conf_val_t config = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_KEYSTORE, + C_CONFIG, args->id, args->id_len); + conf_val_t key_label = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_KEYSTORE, + C_KEY_LABEL, args->id, args->id_len); + if (conf_opt(&backend) == KEYSTORE_BACKEND_PKCS11 && conf_str(&config) == NULL) { + args->err_str = "no PKCS #11 configuration defined"; + return KNOT_EINVAL; + } + if (conf_opt(&backend) != KEYSTORE_BACKEND_PKCS11 && conf_bool(&key_label)) { + args->err_str = "key labels not supported with the specified keystore"; + return KNOT_EINVAL; + } + + return KNOT_EOK; +} + +int check_policy( + knotd_conf_check_args_t *args) +{ + conf_val_t sts = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_SINGLE_TYPE_SIGNING, args->id, args->id_len); + conf_val_t alg = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_ALG, args->id, args->id_len); + conf_val_t ksk = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_KSK_SIZE, args->id, args->id_len); + conf_val_t zsk = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_ZSK_SIZE, args->id, args->id_len); + conf_val_t lifetime = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_RRSIG_LIFETIME, args->id, args->id_len); + conf_val_t refresh = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_RRSIG_REFRESH, args->id, args->id_len); + conf_val_t prerefresh = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_RRSIG_PREREFRESH, args->id, args->id_len); + conf_val_t prop_del = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_PROPAG_DELAY, args->id, args->id_len); + conf_val_t zsk_life = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_ZSK_LIFETIME, args->id, args->id_len); + conf_val_t ksk_life = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_KSK_LIFETIME, args->id, args->id_len); + conf_val_t dnskey_ttl = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_DNSKEY_TTL, args->id, args->id_len); + conf_val_t zone_max_ttl = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_ZONE_MAX_TTL, args->id, args->id_len); + conf_val_t nsec3 = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_NSEC3, args->id, args->id_len); + conf_val_t nsec3_iters = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_NSEC3_ITER, args->id, args->id_len); + + unsigned algorithm = conf_opt(&alg); + if (algorithm < DNSSEC_KEY_ALGORITHM_RSA_SHA256) { + CONF_LOG(LOG_NOTICE, "algorithm %u is deprecated and shouldn't be used for DNSSEC signing", + algorithm); + } + + int64_t ksk_size = conf_int(&ksk); + if (ksk_size != YP_NIL && !dnssec_algorithm_key_size_check(algorithm, ksk_size)) { + args->err_str = "KSK key size not compatible with the algorithm"; + return KNOT_EINVAL; + } + + int64_t zsk_size = conf_int(&zsk); + if (zsk_size != YP_NIL && !dnssec_algorithm_key_size_check(algorithm, zsk_size)) { + args->err_str = "ZSK key size not compatible with the algorithm"; + return KNOT_EINVAL; + } + + int64_t lifetime_val = conf_int(&lifetime); + int64_t refresh_val = conf_int(&refresh); + int64_t preref_val = conf_int(&prerefresh); + if (lifetime_val <= refresh_val + preref_val) { + args->err_str = "RRSIG refresh + pre-refresh has to be lower than RRSIG lifetime"; + return KNOT_EINVAL; + } + + bool sts_val = conf_bool(&sts); + int64_t prop_del_val = conf_int(&prop_del); + int64_t zsk_life_val = conf_int(&zsk_life); + int64_t ksk_life_val = conf_int(&ksk_life); + int64_t dnskey_ttl_val = conf_int(&dnskey_ttl); + if (dnskey_ttl_val == YP_NIL) { + dnskey_ttl_val = 0; + } + int64_t zone_max_ttl_val = conf_int(&zone_max_ttl); + if (zone_max_ttl_val == YP_NIL) { + zone_max_ttl_val = dnskey_ttl_val; // Better than 0. + } + + if (sts_val) { + if (ksk_life_val != 0 && ksk_life_val < 2 * prop_del_val + dnskey_ttl_val + zone_max_ttl_val) { + args->err_str = "CSK lifetime too low according to propagation delay, DNSKEY TTL, " + "and maximum zone TTL"; + return KNOT_EINVAL; + } + } else { + if (ksk_life_val != 0 && ksk_life_val < 2 * prop_del_val + 2 * dnskey_ttl_val) { + args->err_str = "KSK lifetime too low according to propagation delay and DNSKEY TTL"; + return KNOT_EINVAL; + } + if (zsk_life_val != 0 && zsk_life_val < 2 * prop_del_val + dnskey_ttl_val + zone_max_ttl_val) { + args->err_str = "ZSK lifetime too low according to propagation delay, DNSKEY TTL, " + "and maximum zone TTL"; + return KNOT_EINVAL; + } + } + + conf_val_t cds_cdnskey = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_CDS_CDNSKEY, args->id, args->id_len); + conf_val_t ds_push = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_DS_PUSH, args->id, args->id_len); + + if (conf_val_count(&ds_push) > 0 && conf_opt(&cds_cdnskey) == CDS_CDNSKEY_NONE) { + args->err_str = "DS push requires enabled CDS/CDNSKEY publication"; + return KNOT_EINVAL; + } + +#ifndef HAVE_GNUTLS_REPRODUCIBLE + conf_val_t repro_sign = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_REPRO_SIGNING, args->id, args->id_len); + if (conf_bool(&repro_sign)) { + CONF_LOG(LOG_WARNING, "reproducible signing not available, signing normally"); + } +#endif + + if (conf_bool(&nsec3)) { + uint16_t iters = conf_int(&nsec3_iters); + if (nsec3_iters.code != KNOT_EOK && iters != 0) { + CONF_LOG(LOG_WARNING, "policy[%s].nsec3-iterations defaults to %u, " + "since version 3.2 the default becomes 0", args->id, iters); + } + if (iters > 20) { + CONF_LOG(LOG_NOTICE, "policy[%s].nsec3-iterations=%u is too high, " + "the recommended value is 0", args->id, iters); + } + } + + conf_val_t dnskey_mgmt = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_DNSKEY_MGMT, args->id, args->id_len); + conf_val_t offline_ksk = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_OFFLINE_KSK, args->id, args->id_len); + conf_val_t delete_dely = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_POLICY, + C_DELETE_DELAY, args->id, args->id_len); + if (conf_opt(&dnskey_mgmt) != DNSKEY_MGMT_FULL) { + if (conf_bool(&offline_ksk)) { + args->err_str = "incremental DNSKEY management can't be used with offline-ksk"; + return KNOT_EINVAL; + } + if (conf_int(&delete_dely) <= 0) { + args->err_str = "incremental DNSKEY management requires configured delete-delay"; + return KNOT_EINVAL; + } + } + + return KNOT_EOK; +} + +int check_key( + knotd_conf_check_args_t *args) +{ + conf_val_t alg = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_KEY, + C_ALG, args->id, args->id_len); + if (conf_val_count(&alg) == 0) { + args->err_str = "no key algorithm defined"; + return KNOT_EINVAL; + } + + conf_val_t secret = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_KEY, + C_SECRET, args->id, args->id_len); + if (conf_val_count(&secret) == 0) { + args->err_str = "no key secret defined"; + return KNOT_EINVAL; + } + + return KNOT_EOK; +} + +int check_acl( + knotd_conf_check_args_t *args) +{ + conf_val_t addr = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_ACL, + C_ADDR, args->id, args->id_len); + conf_val_t key = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_ACL, + C_KEY, args->id, args->id_len); + conf_val_t remote = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_ACL, + C_RMT, args->id, args->id_len); + if (remote.code != KNOT_ENOENT && + (addr.code != KNOT_ENOENT || key.code != KNOT_ENOENT)) { + args->err_str = "specified ACL/remote together with address or key"; + return KNOT_EINVAL; + } + + return KNOT_EOK; +} + +int check_remote( + knotd_conf_check_args_t *args) +{ + conf_val_t addr = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_RMT, + C_ADDR, args->id, args->id_len); + if (conf_val_count(&addr) == 0) { + args->err_str = "no remote address defined"; + return KNOT_EINVAL; + } + + return KNOT_EOK; +} + +int check_remotes( + knotd_conf_check_args_t *args) +{ + conf_val_t remote = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_RMTS, + C_RMT, args->id, args->id_len); + if (remote.code != KNOT_EOK) { + args->err_str = "no remote defined"; + return KNOT_EINVAL; + } + + return KNOT_EOK; +} + +#define CHECK_DFLT(item, name) { \ + conf_val_t val = conf_rawid_get_txn(args->extra->conf, args->extra->txn, \ + C_TPL, item, args->id, args->id_len); \ + if (val.code == KNOT_EOK) { \ + args->err_str = name " in non-default template"; \ + return KNOT_EINVAL; \ + } \ +} + +int check_catalog_group( + knotd_conf_check_args_t *args) +{ + assert(args->data_len > 0); + if (args->data_len - 1 > CATALOG_GROUP_MAXLEN) { + args->err_str = "group name longer than 255 characters"; + return KNOT_EINVAL; + } + + return KNOT_EOK; +} + +int check_template( + knotd_conf_check_args_t *args) +{ + if (!is_default_id(args->id, args->id_len)) { + CHECK_DFLT(C_GLOBAL_MODULE, "global module"); + } + + return KNOT_EOK; +} + +#define CHECK_ZONE_INTERVALS(low_item, high_item) { \ + conf_val_t high = conf_zone_get_txn(args->extra->conf, args->extra->txn, \ + high_item, yp_dname(args->id)); \ + if (high.code == KNOT_EOK) { \ + conf_val_t low = conf_zone_get_txn(args->extra->conf, args->extra->txn, \ + low_item, yp_dname(args->id)); \ + if (low.code == KNOT_EOK && conf_int(&low) > conf_int(&high)) { \ + if (snprintf(check_str, sizeof(check_str), "'%s' is higher than '%s'", \ + &low_item[1], &high_item[1]) < 0) { \ + check_str[0] = '\0'; \ + } \ + args->err_str = check_str; \ + return KNOT_EINVAL; \ + } \ + } \ +} + +#define CHECK_CATZ_TPL(option, option_string) \ +{ \ + conf_val_t val = conf_rawid_get_txn(args->extra->conf, args->extra->txn, \ + C_TPL, option, catalog_tpl.data, \ + catalog_tpl.len); \ + if (val.code == KNOT_EOK) { \ + args->err_str = "'" option_string "' in a catalog template"; \ + return KNOT_EINVAL; \ + } \ +} + +int check_zone( + knotd_conf_check_args_t *args) +{ + CHECK_ZONE_INTERVALS(C_REFRESH_MIN_INTERVAL, C_REFRESH_MAX_INTERVAL); + CHECK_ZONE_INTERVALS(C_RETRY_MIN_INTERVAL, C_RETRY_MAX_INTERVAL); + CHECK_ZONE_INTERVALS(C_EXPIRE_MIN_INTERVAL, C_EXPIRE_MAX_INTERVAL); + + conf_val_t zf_load = conf_zone_get_txn(args->extra->conf, args->extra->txn, + C_ZONEFILE_LOAD, yp_dname(args->id)); + if (conf_opt(&zf_load) == ZONEFILE_LOAD_DIFSE) { + conf_val_t journal = conf_zone_get_txn(args->extra->conf, args->extra->txn, + C_JOURNAL_CONTENT, yp_dname(args->id)); + if (conf_opt(&journal) != JOURNAL_CONTENT_ALL) { + args->err_str = "'zonefile-load: difference-no-serial' requires 'journal-content: all'"; + return KNOT_EINVAL; + } + } + + conf_val_t validation = conf_zone_get_txn(args->extra->conf, args->extra->txn, + C_DNSSEC_VALIDATION, yp_dname(args->id)); + if (conf_bool(&validation)) { + conf_val_t signing = conf_zone_get_txn(args->extra->conf, args->extra->txn, + C_DNSSEC_SIGNING, yp_dname(args->id)); + if (conf_bool(&signing)) { + args->err_str = "'dnssec-validation' is not compatible with 'dnssec-signing'"; + return KNOT_EINVAL; + } + } + + conf_val_t catalog_role = conf_zone_get_txn(args->extra->conf, args->extra->txn, + C_CATALOG_ROLE, yp_dname(args->id)); + conf_val_t catalog_tpl = conf_zone_get_txn(args->extra->conf, args->extra->txn, + C_CATALOG_TPL, yp_dname(args->id)); + conf_val_t catalog_zone = conf_zone_get_txn(args->extra->conf, args->extra->txn, + C_CATALOG_ZONE, yp_dname(args->id)); + conf_val_t catalog_serial = conf_zone_get_txn(args->extra->conf, args->extra->txn, + C_SERIAL_POLICY, yp_dname(args->id)); + + unsigned role = conf_opt(&catalog_role); + if ((bool)(role == CATALOG_ROLE_INTERPRET) != (bool)(catalog_tpl.code == KNOT_EOK)) { + args->err_str = "'catalog-role' must correspond to configured 'catalog-template'"; + return KNOT_EINVAL; + } + if ((bool)(role == CATALOG_ROLE_MEMBER) != (bool)(catalog_zone.code == KNOT_EOK)) { + args->err_str = "'catalog-role' must correspond to configured 'catalog-zone'"; + return KNOT_EINVAL; + } + if (role == CATALOG_ROLE_GENERATE && + conf_opt(&catalog_serial) != SERIAL_POLICY_UNIXTIME && // Default doesn't harm. + catalog_serial.code == KNOT_EOK) { + args->err_str = "'serial-policy' must be 'unixtime' for generated catalog zones"; + return KNOT_EINVAL; + } + if (role == CATALOG_ROLE_INTERPRET) { + conf_val(&catalog_tpl); + while (catalog_tpl.code == KNOT_EOK) { + CHECK_CATZ_TPL(C_CATALOG_TPL, "catalog-template"); + CHECK_CATZ_TPL(C_CATALOG_ROLE, "catalog-role"); + CHECK_CATZ_TPL(C_CATALOG_ZONE, "catalog-zone"); + CHECK_CATZ_TPL(C_CATALOG_GROUP, "catalog-group"); + conf_val_next(&catalog_tpl); + } + } + + conf_val_t ds_push = conf_zone_get_txn(args->extra->conf, args->extra->txn, + C_DS_PUSH, yp_dname(args->id)); + if (ds_push.code == KNOT_EOK) { + conf_val_t policy_id = conf_zone_get_txn(args->extra->conf, args->extra->txn, + C_DNSSEC_POLICY, yp_dname(args->id)); + if (policy_id.code == KNOT_EOK) { + conf_val_t cds_cdnskey = conf_id_get_txn(args->extra->conf, args->extra->txn, + C_POLICY, C_CDS_CDNSKEY, + &policy_id); + if (conf_val_count(&ds_push) > 0 && conf_opt(&cds_cdnskey) == CDS_CDNSKEY_NONE) { + args->err_str = "DS push requires enabled CDS/CDNSKEY publication"; + return KNOT_EINVAL; + } + } + } + + return KNOT_EOK; +} + +static int glob_error( + const char *epath, + int eerrno) +{ + CONF_LOG(LOG_WARNING, "failed to access '%s' (%s)", epath, + knot_strerror(knot_map_errno_code(eerrno))); + + return 0; +} + +int include_file( + knotd_conf_check_args_t *args) +{ + if (args->data_len == 0) { + return KNOT_YP_ENODATA; + } + + // This function should not be called in more threads. + static int depth = 0; + glob_t glob_buf = { 0 }; + char *path = NULL; + int ret; + + // Check for include loop. + if (depth++ > MAX_INCLUDE_DEPTH) { + CONF_LOG(LOG_ERR, "include loop detected"); + ret = KNOT_EPARSEFAIL; + goto include_error; + } + + // Prepare absolute include path. + if (args->data[0] == '/') { + path = sprintf_alloc("%.*s", (int)args->data_len, args->data); + } else { + const char *file_name = args->extra->file_name != NULL ? + args->extra->file_name : "./"; + char *full_current_name = realpath(file_name, NULL); + if (full_current_name == NULL) { + ret = KNOT_ENOMEM; + goto include_error; + } + + path = sprintf_alloc("%s/%.*s", dirname(full_current_name), + (int)args->data_len, args->data); + free(full_current_name); + } + if (path == NULL) { + ret = KNOT_ESPACE; + goto include_error; + } + + // Evaluate include pattern (empty wildcard match is also valid). + ret = glob(path, 0, glob_error, &glob_buf); + if (ret != 0 && (ret != GLOB_NOMATCH || strchr(path, '*') == NULL)) { + ret = KNOT_EFILE; + goto include_error; + } + + // Process glob result. + for (size_t i = 0; i < glob_buf.gl_pathc; i++) { + // Get file status. + struct stat file_stat; + if (stat(glob_buf.gl_pathv[i], &file_stat) != 0) { + CONF_LOG(LOG_WARNING, "failed to get file status for '%s'", + glob_buf.gl_pathv[i]); + continue; + } + + // Ignore directory or non-regular file. + if (S_ISDIR(file_stat.st_mode)) { + continue; + } else if (!S_ISREG(file_stat.st_mode)) { + CONF_LOG(LOG_WARNING, "invalid include file '%s'", + glob_buf.gl_pathv[i]); + continue; + } + + // Include regular file. + ret = conf_parse(args->extra->conf, args->extra->txn, + glob_buf.gl_pathv[i], true); + if (ret != KNOT_EOK) { + goto include_error; + } + } + + ret = KNOT_EOK; +include_error: + globfree(&glob_buf); + free(path); + depth--; + + return ret; +} + +int load_module( + knotd_conf_check_args_t *args) +{ + conf_val_t val = conf_rawid_get_txn(args->extra->conf, args->extra->txn, + C_MODULE, C_FILE, args->id, args->id_len); + const char *file_name = conf_str(&val); + + char *mod_name = strndup((const char *)args->id, args->id_len); + if (mod_name == NULL) { + return KNOT_ENOMEM; + } + + int ret = conf_mod_load_extra(args->extra->conf, mod_name, file_name, + args->extra->check ? MOD_TEMPORARY : MOD_EXPLICIT); + free(mod_name); + if (ret != KNOT_EOK) { + return ret; + } + + // Update currently iterating item. + const yp_item_t *section = yp_schema_find(C_MODULE, NULL, args->extra->conf->schema); + assert(section); + args->item = section->var.g.id; + + return ret; +} |