/* Copyright (C) 2022 CZ.NIC, z.s.p.o. 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 . */ #include #include #include #include #include "knot/common/log.h" #include "knot/common/stats.h" #include "knot/conf/confio.h" #include "knot/ctl/commands.h" #include "knot/dnssec/key-events.h" #include "knot/events/events.h" #include "knot/events/handlers.h" #include "knot/journal/journal_metadata.h" #include "knot/nameserver/query_module.h" #include "knot/updates/zone-update.h" #include "knot/zone/backup.h" #include "knot/zone/digest.h" #include "knot/zone/timers.h" #include "knot/zone/zonedb-load.h" #include "knot/zone/zonefile.h" #include "libknot/libknot.h" #include "libknot/yparser/yptrafo.h" #include "contrib/files.h" #include "contrib/string.h" #include "contrib/strtonum.h" #include "contrib/openbsd/strlcat.h" #include "contrib/ucw/lists.h" #include "libzscanner/scanner.h" #define MATCH_OR_FILTER(args, code) ((args)->data[KNOT_CTL_IDX_FILTER] == NULL || \ strchr((args)->data[KNOT_CTL_IDX_FILTER], (code)) != NULL) #define MATCH_AND_FILTER(args, code) ((args)->data[KNOT_CTL_IDX_FILTER] != NULL && \ strchr((args)->data[KNOT_CTL_IDX_FILTER], (code)) != NULL) typedef struct { ctl_args_t *args; int type_filter; // -1: no specific type, [0, 2^16]: specific type. knot_dump_style_t style; knot_ctl_data_t data; knot_dname_txt_storage_t zone; knot_dname_txt_storage_t owner; char ttl[16]; char type[32]; char rdata[2 * 65536]; } send_ctx_t; static struct { send_ctx_t send_ctx; zs_scanner_t scanner; char txt_rr[sizeof(((send_ctx_t *)0)->owner) + sizeof(((send_ctx_t *)0)->ttl) + sizeof(((send_ctx_t *)0)->type) + sizeof(((send_ctx_t *)0)->rdata)]; } ctl_globals; /*! * Evaluates a filter pair and checks for conflicting filters. * * \param[in] args Command arguments. * \param[out] param The filter to be set. * \param[in] dflt Default filter value. * \param[in] filter Name of the filter. * \param[in] neg_filter Name of the negative filter. * * \return false if there is a filter conflict, true otherwise. */ static bool eval_opposite_filters(ctl_args_t *args, bool *param, bool dflt, int filter, int neg_filter) { bool set = MATCH_AND_FILTER(args, filter); bool unset = MATCH_AND_FILTER(args, neg_filter); *param = dflt ? (set || !unset) : (set && !unset); return !(set && unset); } static int schedule_trigger(zone_t *zone, ctl_args_t *args, zone_event_type_t event, bool user) { int ret = KNOT_EOK; if (ctl_has_flag(args->data[KNOT_CTL_IDX_FLAGS], CTL_FLAG_BLOCKING)) { ret = zone_events_schedule_blocking(zone, event, user); } else if (user) { zone_events_schedule_user(zone, event); } else { zone_events_schedule_now(zone, event); } return ret; } static void ctl_log_conf_data(knot_ctl_data_t *data) { if (data == NULL) { return; } const char *section = (*data)[KNOT_CTL_IDX_SECTION]; const char *item = (*data)[KNOT_CTL_IDX_ITEM]; const char *id = (*data)[KNOT_CTL_IDX_ID]; if (section != NULL) { log_ctl_debug("control, config item '%s%s%s%s%s%s'", section, (id != NULL ? "[" : ""), (id != NULL ? id : ""), (id != NULL ? "]" : ""), (item != NULL ? "." : ""), (item != NULL ? item : "")); } } static void send_error(ctl_args_t *args, const char *msg) { knot_ctl_data_t data; memcpy(&data, args->data, sizeof(data)); data[KNOT_CTL_IDX_ERROR] = msg; int ret = knot_ctl_send(args->ctl, KNOT_CTL_TYPE_DATA, &data); if (ret != KNOT_EOK) { log_ctl_debug("control, failed to send error (%s)", knot_strerror(ret)); } } static int get_zone(ctl_args_t *args, zone_t **zone) { const char *name = args->data[KNOT_CTL_IDX_ZONE]; assert(name != NULL); knot_dname_storage_t buff; knot_dname_t *dname = knot_dname_from_str(buff, name, sizeof(buff)); if (dname == NULL) { return KNOT_EINVAL; } knot_dname_to_lower(dname); *zone = knot_zonedb_find(args->server->zone_db, dname); if (*zone == NULL) { return KNOT_ENOZONE; } return KNOT_EOK; } static int zones_apply(ctl_args_t *args, int (*fcn)(zone_t *, ctl_args_t *)) { int ret; // Process all configured zones if none is specified. if (args->data[KNOT_CTL_IDX_ZONE] == NULL) { bool failed = false; knot_zonedb_iter_t *it = knot_zonedb_iter_begin(args->server->zone_db); while (!knot_zonedb_iter_finished(it)) { args->suppress = false; ret = fcn((zone_t *)knot_zonedb_iter_val(it), args); if (ret != KNOT_EOK && !args->suppress) { failed = true; } knot_zonedb_iter_next(it); } knot_zonedb_iter_free(it); if (failed) { ret = KNOT_CTL_EZONE; log_ctl_error("control, error (%s)", knot_strerror(ret)); send_error(args, knot_strerror(ret)); } return KNOT_EOK; } while (true) { zone_t *zone; ret = get_zone(args, &zone); if (ret == KNOT_EOK) { ret = fcn(zone, args); } if (ret != KNOT_EOK) { log_ctl_zone_str_error(args->data[KNOT_CTL_IDX_ZONE], "control, error (%s)", knot_strerror(ret)); send_error(args, knot_strerror(ret)); } // Get next zone name. ret = knot_ctl_receive(args->ctl, &args->type, &args->data); if (ret != KNOT_EOK || args->type != KNOT_CTL_TYPE_DATA) { break; } strtolower((char *)args->data[KNOT_CTL_IDX_ZONE]); // Log the other zones the same way as the first one from process.c. log_ctl_zone_str_info(args->data[KNOT_CTL_IDX_ZONE], "control, received command '%s'", args->data[KNOT_CTL_IDX_CMD]); } return ret; } static int zone_status(zone_t *zone, ctl_args_t *args) { knot_dname_txt_storage_t name; if (knot_dname_to_str(name, zone->name, sizeof(name)) == NULL) { return KNOT_EINVAL; } char flags[16] = ""; knot_ctl_data_t data = { [KNOT_CTL_IDX_ZONE] = name, [KNOT_CTL_IDX_FLAGS] = flags }; const bool slave = zone_is_slave(conf(), zone); if (slave) { strlcat(flags, CTL_FLAG_STATUS_SLAVE, sizeof(flags)); } const bool empty = (zone->contents == NULL); if (empty) { strlcat(flags, CTL_FLAG_STATUS_EMPTY, sizeof(flags)); } const bool member = (zone->flags & ZONE_IS_CAT_MEMBER); if (member) { strlcat(flags, CTL_FLAG_STATUS_MEMBER, sizeof(flags)); } int ret; char buff[128]; knot_ctl_type_t type = KNOT_CTL_TYPE_DATA; if (MATCH_OR_FILTER(args, CTL_FILTER_STATUS_ROLE)) { data[KNOT_CTL_IDX_TYPE] = "role"; if (slave) { data[KNOT_CTL_IDX_DATA] = "slave"; } else { data[KNOT_CTL_IDX_DATA] = "master"; } ret = knot_ctl_send(args->ctl, type, &data); if (ret != KNOT_EOK) { return ret; } else { type = KNOT_CTL_TYPE_EXTRA; } } if (MATCH_OR_FILTER(args, CTL_FILTER_STATUS_SERIAL)) { data[KNOT_CTL_IDX_TYPE] = "serial"; if (empty) { ret = snprintf(buff, sizeof(buff), STATUS_EMPTY); } else { knot_rdataset_t *soa = node_rdataset(zone->contents->apex, KNOT_RRTYPE_SOA); ret = snprintf(buff, sizeof(buff), "%u", knot_soa_serial(soa->rdata)); } if (ret < 0 || ret >= sizeof(buff)) { return KNOT_ESPACE; } data[KNOT_CTL_IDX_DATA] = buff; ret = knot_ctl_send(args->ctl, type, &data); if (ret != KNOT_EOK) { return ret; } else { type = KNOT_CTL_TYPE_EXTRA; } } if (MATCH_OR_FILTER(args, CTL_FILTER_STATUS_TRANSACTION)) { data[KNOT_CTL_IDX_TYPE] = "transaction"; data[KNOT_CTL_IDX_DATA] = (zone->control_update != NULL) ? "open" : STATUS_EMPTY; ret = knot_ctl_send(args->ctl, type, &data); if (ret != KNOT_EOK) { return ret; } else { type = KNOT_CTL_TYPE_EXTRA; } } const bool ufrozen = zone->events.ufrozen; if (MATCH_OR_FILTER(args, CTL_FILTER_STATUS_FREEZE)) { data[KNOT_CTL_IDX_TYPE] = "freeze"; if (ufrozen) { if (zone_events_get_time(zone, ZONE_EVENT_UTHAW) < time(NULL)) { data[KNOT_CTL_IDX_DATA] = "yes"; } else { data[KNOT_CTL_IDX_DATA] = "thawing"; } } else { if (zone_events_get_time(zone, ZONE_EVENT_UFREEZE) < time(NULL)) { data[KNOT_CTL_IDX_DATA] = STATUS_EMPTY; } else { data[KNOT_CTL_IDX_DATA] = "freezing"; } } ret = knot_ctl_send(args->ctl, type, &data); if (ret != KNOT_EOK) { return ret; } else { type = KNOT_CTL_TYPE_EXTRA; } data[KNOT_CTL_IDX_TYPE] = "XFR-freeze"; if (zone_get_flag(zone, ZONE_XFR_FROZEN, false)) { data[KNOT_CTL_IDX_DATA] = "yes"; } else { data[KNOT_CTL_IDX_DATA] = STATUS_EMPTY; } ret = knot_ctl_send(args->ctl, type, &data); if (ret != KNOT_EOK) { return ret; } } if (MATCH_OR_FILTER(args, CTL_FILTER_STATUS_CATALOG)) { char buf[1 + KNOT_DNAME_TXT_MAXLEN + 1 + CATALOG_GROUP_MAXLEN + 1] = ""; data[KNOT_CTL_IDX_TYPE] = "catalog"; data[KNOT_CTL_IDX_DATA] = buf; if (member) { const knot_dname_t *catz; const char *group; void *to_free; ret = catalog_get_catz(zone_catalog(zone), zone->name, &catz, &group, &to_free); if (ret == KNOT_EOK) { if (knot_dname_to_str(buf, catz, sizeof(buf)) == NULL) { buf[0] = '\0'; } if (group[0] != '\0') { size_t idx = strlcat(buf, "#", sizeof(buf)); (void)strlcat(buf + idx, group, sizeof(buf) - idx); } free(to_free); } } else { conf_val_t val = conf_zone_get(conf(), C_CATALOG_ROLE, zone->name); switch (conf_opt(&val)) { case CATALOG_ROLE_INTERPRET: data[KNOT_CTL_IDX_DATA] = "interpret"; break; case CATALOG_ROLE_GENERATE: data[KNOT_CTL_IDX_DATA] = "generate"; break; case CATALOG_ROLE_MEMBER: buf[0] = '@'; val = conf_zone_get(conf(), C_CATALOG_ZONE, zone->name); if (knot_dname_to_str(buf + 1, conf_dname(&val), sizeof(buf) - 1) == NULL) { buf[1] = '\0'; } val = conf_zone_get(conf(), C_CATALOG_GROUP, zone->name); if (val.code == KNOT_EOK) { size_t idx = strlcat(buf, "#", sizeof(buf)); (void)strlcat(buf + idx, conf_str(&val), sizeof(buf) - idx); } break; default: data[KNOT_CTL_IDX_DATA] = STATUS_EMPTY; } } ret = knot_ctl_send(args->ctl, type, &data); if (ret != KNOT_EOK) { return ret; } else { type = KNOT_CTL_TYPE_EXTRA; } } if (MATCH_OR_FILTER(args, CTL_FILTER_STATUS_EVENTS)) { for (zone_event_type_t i = 0; i < ZONE_EVENT_COUNT; i++) { // Events not worth showing or used elsewhere. if (i == ZONE_EVENT_UFREEZE || i == ZONE_EVENT_UTHAW) { continue; } data[KNOT_CTL_IDX_TYPE] = zone_events_get_name(i); time_t ev_time = zone_events_get_time(zone, i); if (zone->events.running && zone->events.type == i) { ret = snprintf(buff, sizeof(buff), "running"); } else if (ev_time <= 0) { ret = snprintf(buff, sizeof(buff), STATUS_EMPTY); } else if (ev_time <= time(NULL)) { bool frozen = ufrozen && ufreeze_applies(i); ret = snprintf(buff, sizeof(buff), frozen ? "frozen" : "pending"); } else { ret = knot_time_print(TIME_PRINT_HUMAN_MIXED, ev_time, buff, sizeof(buff)); } if (ret < 0 || ret >= sizeof(buff)) { return KNOT_ESPACE; } data[KNOT_CTL_IDX_DATA] = buff; ret = knot_ctl_send(args->ctl, type, &data); if (ret != KNOT_EOK) { return ret; } else { type = KNOT_CTL_TYPE_EXTRA; } } } return KNOT_EOK; } static int zone_reload(zone_t *zone, _unused_ ctl_args_t *args) { if (zone_expired(zone)) { args->suppress = true; return KNOT_ENOTSUP; } if (ctl_has_flag(args->data[KNOT_CTL_IDX_FLAGS], CTL_FLAG_FORCE)) { return zone_reload_modules(conf(), args->server, zone->name); } return schedule_trigger(zone, args, ZONE_EVENT_LOAD, true); } static int zone_refresh(zone_t *zone, _unused_ ctl_args_t *args) { if (!zone_is_slave(conf(), zone)) { args->suppress = true; return KNOT_ENOTSUP; } zone->zonefile.bootstrap_cnt = 0; // restart delays return schedule_trigger(zone, args, ZONE_EVENT_REFRESH, true); } static int zone_retransfer(zone_t *zone, _unused_ ctl_args_t *args) { if (!zone_is_slave(conf(), zone)) { args->suppress = true; return KNOT_ENOTSUP; } zone_set_flag(zone, ZONE_FORCE_AXFR); zone->zonefile.bootstrap_cnt = 0; // restart delays return schedule_trigger(zone, args, ZONE_EVENT_REFRESH, true); } static int zone_notify(zone_t *zone, _unused_ ctl_args_t *args) { zone_notifailed_clear(zone); return schedule_trigger(zone, args, ZONE_EVENT_NOTIFY, true); } static int zone_flush(zone_t *zone, ctl_args_t *args) { if (MATCH_AND_FILTER(args, CTL_FILTER_FLUSH_OUTDIR)) { rcu_read_lock(); int ret = zone_dump_to_dir(conf(), zone, args->data[KNOT_CTL_IDX_DATA]); rcu_read_unlock(); if (ret != KNOT_EOK) { log_zone_warning(zone->name, "failed to update zone file (%s)", knot_strerror(ret)); } return ret; } zone_set_flag(zone, ZONE_USER_FLUSH); if (ctl_has_flag(args->data[KNOT_CTL_IDX_FLAGS], CTL_FLAG_FORCE)) { zone_set_flag(zone, ZONE_FORCE_FLUSH); } return schedule_trigger(zone, args, ZONE_EVENT_FLUSH, true); } static int init_backup(ctl_args_t *args, bool restore_mode) { if (!MATCH_AND_FILTER(args, CTL_FILTER_BACKUP_OUTDIR)) { return KNOT_ENOPARAM; } // Make sure that the backup outdir is not the same as the server DB storage. conf_val_t db_storage_val = conf_db_param(conf(), C_STORAGE); const char *db_storage = conf_str(&db_storage_val); const char *backup_dir = args->data[KNOT_CTL_IDX_DATA]; if (same_path(backup_dir, db_storage)) { char *msg = sprintf_alloc("%s the database storage directory not allowed", restore_mode ? "restore from" : "backup to"); if (args->data[KNOT_CTL_IDX_ZONE] == NULL) { log_ctl_error("%s", msg); } else { log_ctl_zone_str_error(args->data[KNOT_CTL_IDX_ZONE], "%s", msg); } free(msg); return KNOT_EINVAL; } // Evaluate filters (and possibly fail) before writing to the filesystem. bool filter_zonefile, filter_journal, filter_timers, filter_kaspdb, filter_catalog; // The default filter values are set just in this paragraph. if (!(eval_opposite_filters(args, &filter_zonefile, true, CTL_FILTER_BACKUP_ZONEFILE, CTL_FILTER_BACKUP_NOZONEFILE) && eval_opposite_filters(args, &filter_journal, false, CTL_FILTER_BACKUP_JOURNAL, CTL_FILTER_BACKUP_NOJOURNAL) && eval_opposite_filters(args, &filter_timers, true, CTL_FILTER_BACKUP_TIMERS, CTL_FILTER_BACKUP_NOTIMERS) && eval_opposite_filters(args, &filter_kaspdb, true, CTL_FILTER_BACKUP_KASPDB, CTL_FILTER_BACKUP_NOKASPDB) && eval_opposite_filters(args, &filter_catalog, true, CTL_FILTER_BACKUP_CATALOG, CTL_FILTER_BACKUP_NOCATALOG))) { return KNOT_EXPARAM; } bool forced = ctl_has_flag(args->data[KNOT_CTL_IDX_FLAGS], CTL_FLAG_FORCE); zone_backup_ctx_t *ctx; // The present timer db size is not up-to-date, use the maximum one. conf_val_t timer_db_size = conf_db_param(conf(), C_TIMER_DB_MAX_SIZE); int ret = zone_backup_init(restore_mode, forced, args->data[KNOT_CTL_IDX_DATA], knot_lmdb_copy_size(&args->server->kaspdb), conf_int(&timer_db_size), knot_lmdb_copy_size(&args->server->journaldb), knot_lmdb_copy_size(&args->server->catalog.db), &ctx); if (ret != KNOT_EOK) { return ret; } assert(ctx != NULL); ctx->backup_zonefile = filter_zonefile; ctx->backup_journal = filter_journal; ctx->backup_timers = filter_timers; ctx->backup_kaspdb = filter_kaspdb; ctx->backup_catalog = filter_catalog; zone_backups_add(&args->server->backup_ctxs, ctx); return ret; } static zone_backup_ctx_t *latest_backup_ctx(ctl_args_t *args) { // no need to mutex in this case return (zone_backup_ctx_t *)TAIL(args->server->backup_ctxs.ctxs); } static int deinit_backup(ctl_args_t *args) { return zone_backup_deinit(latest_backup_ctx(args)); } static int zone_backup_cmd(zone_t *zone, ctl_args_t *args) { zone_backup_ctx_t *ctx = latest_backup_ctx(args); if (!ctx->restore_mode && ctx->failed) { // No need to proceed with already faulty backup. return KNOT_EOK; } if (zone->backup_ctx != NULL) { log_zone_warning(zone->name, "backup or restore already in progress, skipping zone"); ctx->failed = true; return KNOT_EPROGRESS; } zone->backup_ctx = ctx; pthread_mutex_lock(&ctx->readers_mutex); ctx->readers++; pthread_mutex_unlock(&ctx->readers_mutex); ctx->zone_count++; int ret = schedule_trigger(zone, args, ZONE_EVENT_BACKUP, true); if (ret == KNOT_EOK && !ctx->backup_global && (ctx->restore_mode || !ctx->failed)) { ret = global_backup(ctx, zone_catalog(zone), zone->name); } return ret; } static int zones_apply_backup(ctl_args_t *args, bool restore_mode) { int ret_deinit; int ret = init_backup(args, restore_mode); if (ret != KNOT_EOK) { char *msg = sprintf_alloc("%s init failed (%s)", restore_mode ? "restore" : "backup", knot_strerror(ret)); if (args->data[KNOT_CTL_IDX_ZONE] == NULL) { log_ctl_error("%s", msg); } else { log_ctl_zone_str_error(args->data[KNOT_CTL_IDX_ZONE], "%s", msg); } free (msg); /* Warning: zone name in the control command params discarded here. */ args->data[KNOT_CTL_IDX_ZONE] = NULL; send_error(args, knot_strerror(ret)); return KNOT_CTL_EZONE; } /* Global catalog zones backup. */ if (args->data[KNOT_CTL_IDX_ZONE] == NULL) { zone_backup_ctx_t *ctx = latest_backup_ctx(args); ctx->backup_global = true; ret = global_backup(ctx, &args->server->catalog, NULL); if (ret != KNOT_EOK) { log_ctl_error("control, error (%s)", knot_strerror(ret)); send_error(args, knot_strerror(ret)); ret = KNOT_EOK; goto done; } } ret = zones_apply(args, zone_backup_cmd); done: ret_deinit = deinit_backup(args); return ret != KNOT_EOK ? ret : ret_deinit; } static int zone_sign(zone_t *zone, _unused_ ctl_args_t *args) { conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name); if (!conf_bool(&val)) { args->suppress = true; return KNOT_ENOTSUP; } zone_set_flag(zone, ZONE_FORCE_RESIGN); return schedule_trigger(zone, args, ZONE_EVENT_DNSSEC, true); } static int zone_keys_load(zone_t *zone, _unused_ ctl_args_t *args) { conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name); if (!conf_bool(&val)) { args->suppress = true; return KNOT_ENOTSUP; } return schedule_trigger(zone, args, ZONE_EVENT_DNSSEC, true); } static int zone_key_roll(zone_t *zone, ctl_args_t *args) { conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name); if (!conf_bool(&val)) { args->suppress = true; return KNOT_ENOTSUP; } const char *key_type = args->data[KNOT_CTL_IDX_TYPE]; if (strncasecmp(key_type, "ksk", 3) == 0) { zone_set_flag(zone, ZONE_FORCE_KSK_ROLL); } else if (strncasecmp(key_type, "zsk", 3) == 0) { zone_set_flag(zone, ZONE_FORCE_ZSK_ROLL); } else { return KNOT_EINVAL; } return schedule_trigger(zone, args, ZONE_EVENT_DNSSEC, true); } static int zone_ksk_sbm_confirm(zone_t *zone, _unused_ ctl_args_t *args) { kdnssec_ctx_t ctx = { 0 }; int ret = kdnssec_ctx_init(conf(), &ctx, zone->name, zone_kaspdb(zone), NULL); if (ret != KNOT_EOK) { return ret; } ret = knot_dnssec_ksk_sbm_confirm(&ctx, 0); kdnssec_ctx_deinit(&ctx); conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name); if (ret == KNOT_EOK && conf_bool(&val)) { // NOT zone_events_schedule_user(), intentionally! ret = schedule_trigger(zone, args, ZONE_EVENT_DNSSEC, false); } return ret; } static int zone_freeze(zone_t *zone, _unused_ ctl_args_t *args) { return schedule_trigger(zone, args, ZONE_EVENT_UFREEZE, false); } static int zone_thaw(zone_t *zone, _unused_ ctl_args_t *args) { return schedule_trigger(zone, args, ZONE_EVENT_UTHAW, false); } static int zone_xfr_freeze(zone_t *zone, _unused_ ctl_args_t *args) { zone_set_flag(zone, ZONE_XFR_FROZEN); log_zone_info(zone->name, "outgoing XFR frozen"); return KNOT_EOK; } static int zone_xfr_thaw(zone_t *zone, _unused_ ctl_args_t *args) { zone_unset_flag(zone, ZONE_XFR_FROZEN); log_zone_info(zone->name, "outgoing XFR unfrozen"); return KNOT_EOK; } static int zone_txn_begin(zone_t *zone, _unused_ ctl_args_t *args) { if (zone->control_update != NULL) { return KNOT_TXN_EEXISTS; } zone->control_update = malloc(sizeof(zone_update_t)); if (zone->control_update == NULL) { return KNOT_ENOMEM; } zone_update_flags_t type = (zone->contents == NULL) ? UPDATE_FULL : UPDATE_INCREMENTAL; int ret = zone_update_init(zone->control_update, zone, type | UPDATE_STRICT); if (ret != KNOT_EOK) { free(zone->control_update); zone->control_update = NULL; } return ret; } static int zone_txn_commit(zone_t *zone, _unused_ ctl_args_t *args) { if (zone->control_update == NULL) { args->suppress = true; return KNOT_TXN_ENOTEXISTS; } int ret = zone_update_semcheck(conf(), zone->control_update); if (ret != KNOT_EOK) { return ret; // Recoverable error. } // NOOP if empty changeset/contents. if (((zone->control_update->flags & UPDATE_INCREMENTAL) && changeset_empty(&zone->control_update->change)) || ((zone->control_update->flags & UPDATE_FULL) && zone_contents_is_empty(zone->control_update->new_cont))) { zone_control_clear(zone); return KNOT_EOK; } // Sign update. conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name); bool dnssec_enable = conf_bool(&val); val = conf_zone_get(conf(), C_ZONEMD_GENERATE, zone->name); unsigned digest_alg = conf_opt(&val); if (dnssec_enable) { if (zone->control_update->flags & UPDATE_FULL) { zone_sign_reschedule_t resch = { 0 }; zone_sign_roll_flags_t rflags = KEY_ROLL_ALLOW_ALL; ret = knot_dnssec_zone_sign(zone->control_update, conf(), 0, rflags, 0, &resch); event_dnssec_reschedule(conf(), zone, &resch, false); } else { ret = knot_dnssec_sign_update(zone->control_update, conf()); } } else if (digest_alg != ZONE_DIGEST_NONE) { if (zone_update_to(zone->control_update) == NULL) { ret = zone_update_increment_soa(zone->control_update, conf()); } if (ret == KNOT_EOK) { ret = zone_update_add_digest(zone->control_update, digest_alg, false); } } if (ret != KNOT_EOK) { zone_control_clear(zone); return ret; } ret = zone_update_commit(conf(), zone->control_update); if (ret != KNOT_EOK) { zone_control_clear(zone); return ret; } free(zone->control_update); zone->control_update = NULL; zone_schedule_notify(zone, 0); return KNOT_EOK; } static int zone_txn_abort(zone_t *zone, _unused_ ctl_args_t *args) { if (zone->control_update == NULL) { args->suppress = true; return KNOT_TXN_ENOTEXISTS; } zone_control_clear(zone); return KNOT_EOK; } static int init_send_ctx(send_ctx_t *ctx, const knot_dname_t *zone_name, ctl_args_t *args) { memset(ctx, 0, sizeof(*ctx)); ctx->args = args; // Set the dump style. ctx->style.show_ttl = true; ctx->style.original_ttl = true; ctx->style.human_timestamp = true; // Set the output data buffers. ctx->data[KNOT_CTL_IDX_ZONE] = ctx->zone; ctx->data[KNOT_CTL_IDX_OWNER] = ctx->owner; ctx->data[KNOT_CTL_IDX_TTL] = ctx->ttl; ctx->data[KNOT_CTL_IDX_TYPE] = ctx->type; ctx->data[KNOT_CTL_IDX_DATA] = ctx->rdata; // Set the ZONE. if (knot_dname_to_str(ctx->zone, zone_name, sizeof(ctx->zone)) == NULL) { return KNOT_EINVAL; } // Set the TYPE filter. if (args->data[KNOT_CTL_IDX_TYPE] != NULL) { uint16_t type; if (knot_rrtype_from_string(args->data[KNOT_CTL_IDX_TYPE], &type) != 0) { return KNOT_EINVAL; } ctx->type_filter = type; } else { ctx->type_filter = -1; } return KNOT_EOK; } static int send_rrset(knot_rrset_t *rrset, send_ctx_t *ctx) { if (rrset->type != KNOT_RRTYPE_RRSIG) { int ret = snprintf(ctx->ttl, sizeof(ctx->ttl), "%u", rrset->ttl); if (ret <= 0 || ret >= sizeof(ctx->ttl)) { return KNOT_ESPACE; } } if (knot_rrtype_to_string(rrset->type, ctx->type, sizeof(ctx->type)) < 0) { return KNOT_ESPACE; } for (size_t i = 0; i < rrset->rrs.count; ++i) { if (rrset->type == KNOT_RRTYPE_RRSIG) { int ret = snprintf(ctx->ttl, sizeof(ctx->ttl), "%u", knot_rrsig_original_ttl(knot_rdataset_at(&rrset->rrs, i))); if (ret <= 0 || ret >= sizeof(ctx->ttl)) { return KNOT_ESPACE; } } int ret = knot_rrset_txt_dump_data(rrset, i, ctx->rdata, sizeof(ctx->rdata), &ctx->style); if (ret < 0) { return ret; } ret = knot_ctl_send(ctx->args->ctl, KNOT_CTL_TYPE_DATA, &ctx->data); if (ret != KNOT_EOK) { return ret; } } return KNOT_EOK; } static int send_node(zone_node_t *node, void *ctx_void) { send_ctx_t *ctx = ctx_void; if (knot_dname_to_str(ctx->owner, node->owner, sizeof(ctx->owner)) == NULL) { return KNOT_EINVAL; } for (size_t i = 0; i < node->rrset_count; ++i) { knot_rrset_t rrset = node_rrset_at(node, i); // Check for requested TYPE. if (ctx->type_filter != -1 && rrset.type != ctx->type_filter) { continue; } int ret = send_rrset(&rrset, ctx); if (ret != KNOT_EOK) { return ret; } } return KNOT_EOK; } static int get_owner(uint8_t *out, size_t out_len, knot_dname_t *origin, ctl_args_t *args) { const char *owner = args->data[KNOT_CTL_IDX_OWNER]; assert(owner != NULL); bool fqdn = false; size_t prefix_len = 0; size_t owner_len = strlen(owner); if (owner_len > 0 && (owner_len != 1 || owner[0] != '@')) { // Check if the owner is FQDN. if (owner[owner_len - 1] == '.') { fqdn = true; } if (knot_dname_from_str(out, owner, out_len) == NULL) { return KNOT_EINVAL; } knot_dname_to_lower(out); prefix_len = knot_dname_size(out); if (prefix_len == 0) { return KNOT_EINVAL; } // Ignore trailing dot. prefix_len--; } // Append the origin. if (!fqdn) { size_t origin_len = knot_dname_size(origin); if (origin_len == 0 || origin_len > out_len - prefix_len) { return KNOT_EINVAL; } memcpy(out + prefix_len, origin, origin_len); } return KNOT_EOK; } static int zone_read(zone_t *zone, ctl_args_t *args) { send_ctx_t *ctx = &ctl_globals.send_ctx; int ret = init_send_ctx(ctx, zone->name, args); if (ret != KNOT_EOK) { return ret; } if (args->data[KNOT_CTL_IDX_OWNER] != NULL) { knot_dname_storage_t owner; ret = get_owner(owner, sizeof(owner), zone->name, args); if (ret != KNOT_EOK) { return ret; } const zone_node_t *node = zone_contents_node_or_nsec3(zone->contents, owner); if (node == NULL) { return KNOT_ENONODE; } ret = send_node((zone_node_t *)node, ctx); } else if (zone->contents != NULL) { ret = zone_contents_apply(zone->contents, send_node, ctx); if (ret == KNOT_EOK) { ret = zone_contents_nsec3_apply(zone->contents, send_node, ctx); } } return ret; } static int zone_flag_txn_get(zone_t *zone, ctl_args_t *args, const char *flag) { if (zone->control_update == NULL) { args->suppress = true; return KNOT_TXN_ENOTEXISTS; } send_ctx_t *ctx = &ctl_globals.send_ctx; int ret = init_send_ctx(ctx, zone->name, args); if (ret != KNOT_EOK) { return ret; } ctx->data[KNOT_CTL_IDX_FLAGS] = flag; if (args->data[KNOT_CTL_IDX_OWNER] != NULL) { knot_dname_storage_t owner; ret = get_owner(owner, sizeof(owner), zone->name, args); if (ret != KNOT_EOK) { return ret; } const zone_node_t *node = zone_contents_node_or_nsec3(zone->control_update->new_cont, owner); if (node == NULL) { return KNOT_ENONODE; } ret = send_node((zone_node_t *)node, ctx); } else { zone_tree_it_t it = { 0 }; ret = zone_tree_it_double_begin(zone->control_update->new_cont->nodes, zone->control_update->new_cont->nsec3_nodes, &it); while (ret == KNOT_EOK && !zone_tree_it_finished(&it)) { ret = send_node(zone_tree_it_val(&it), ctx); zone_tree_it_next(&it); } zone_tree_it_free(&it); } return ret; } static int zone_txn_get(zone_t *zone, ctl_args_t *args) { return zone_flag_txn_get(zone, args, NULL); } static int send_changeset_part(changeset_t *ch, send_ctx_t *ctx, bool from) { ctx->data[KNOT_CTL_IDX_FLAGS] = from ? CTL_FLAG_DIFF_REM : CTL_FLAG_DIFF_ADD; // Send SOA only if explicitly changed. if (ch->soa_to != NULL) { knot_rrset_t *soa = from ? ch->soa_from : ch->soa_to; assert(soa); char *owner = knot_dname_to_str(ctx->owner, soa->owner, sizeof(ctx->owner)); if (owner == NULL) { return KNOT_EINVAL; } int ret = send_rrset(soa, ctx); if (ret != KNOT_EOK) { return ret; } } // Send other records. changeset_iter_t it; int ret = from ? changeset_iter_rem(&it, ch) : changeset_iter_add(&it, ch); if (ret != KNOT_EOK) { return ret; } knot_rrset_t rrset = changeset_iter_next(&it); while (!knot_rrset_empty(&rrset)) { char *owner = knot_dname_to_str(ctx->owner, rrset.owner, sizeof(ctx->owner)); if (owner == NULL) { changeset_iter_clear(&it); return KNOT_EINVAL; } ret = send_rrset(&rrset, ctx); if (ret != KNOT_EOK) { changeset_iter_clear(&it); return ret; } rrset = changeset_iter_next(&it); } changeset_iter_clear(&it); return KNOT_EOK; } static int send_changeset(changeset_t *ch, send_ctx_t *ctx) { // First send 'from' changeset part. int ret = send_changeset_part(ch, ctx, true); if (ret != KNOT_EOK) { return ret; } // Second send 'to' changeset part. return send_changeset_part(ch, ctx, false); } static int zone_txn_diff(zone_t *zone, ctl_args_t *args) { if (zone->control_update == NULL) { args->suppress = true; return KNOT_TXN_ENOTEXISTS; } // FULL update has no changeset to print, do a 'get' instead. if (zone->control_update->flags & UPDATE_FULL) { return zone_flag_txn_get(zone, args, CTL_FLAG_DIFF_ADD); } send_ctx_t *ctx = &ctl_globals.send_ctx; int ret = init_send_ctx(ctx, zone->name, args); if (ret != KNOT_EOK) { return ret; } return send_changeset(&zone->control_update->change, ctx); } static int get_ttl(zone_t *zone, ctl_args_t *args, uint32_t *ttl) { knot_dname_storage_t owner; int ret = get_owner(owner, sizeof(owner), zone->name, args); if (ret != KNOT_EOK) { return ret; } const zone_node_t *node = zone_contents_node_or_nsec3(zone->control_update->new_cont, owner); if (node == NULL) { return KNOT_ENOTTL; } uint16_t type; if (knot_rrtype_from_string(args->data[KNOT_CTL_IDX_TYPE], &type) != 0) { return KNOT_EINVAL; } knot_rrset_t rrset = node_rrset(node, type); if (knot_rrset_empty(&rrset)) { return KNOT_ENOTTL; } *ttl = rrset.ttl; return KNOT_EOK; } static int create_rrset(knot_rrset_t **rrset, zone_t *zone, ctl_args_t *args, bool need_ttl) { knot_dname_txt_storage_t origin_buff; char *origin = knot_dname_to_str(origin_buff, zone->name, sizeof(origin_buff)); if (origin == NULL) { return KNOT_EINVAL; } const char *owner = args->data[KNOT_CTL_IDX_OWNER]; const char *type = args->data[KNOT_CTL_IDX_TYPE]; const char *data = args->data[KNOT_CTL_IDX_DATA]; const char *ttl = need_ttl ? args->data[KNOT_CTL_IDX_TTL] : NULL; // Prepare a buffer for a reconstructed record. const size_t buff_len = sizeof(ctl_globals.txt_rr); char *buff = ctl_globals.txt_rr; uint32_t default_ttl = 0; if (ttl == NULL) { int ret = get_ttl(zone, args, &default_ttl); if (need_ttl && ret != KNOT_EOK) { return ret; } } // Reconstruct the record. int ret = snprintf(buff, buff_len, "%s %s %s %s\n", (owner != NULL ? owner : ""), (ttl != NULL ? ttl : ""), (type != NULL ? type : ""), (data != NULL ? data : "")); if (ret <= 0 || ret >= buff_len) { return KNOT_ESPACE; } size_t rdata_len = ret; // Parse the record. zs_scanner_t *scanner = &ctl_globals.scanner; if (zs_init(scanner, origin, KNOT_CLASS_IN, default_ttl) != 0 || zs_set_input_string(scanner, buff, rdata_len) != 0 || zs_parse_record(scanner) != 0 || scanner->state != ZS_STATE_DATA) { ret = KNOT_EPARSEFAIL; goto parser_failed; } knot_dname_to_lower(scanner->r_owner); // Create output rrset. *rrset = knot_rrset_new(scanner->r_owner, scanner->r_type, scanner->r_class, scanner->r_ttl, NULL); if (*rrset == NULL) { ret = KNOT_ENOMEM; goto parser_failed; } ret = knot_rrset_add_rdata(*rrset, scanner->r_data, scanner->r_data_length, NULL); parser_failed: zs_deinit(scanner); return ret; } static int zone_txn_set(zone_t *zone, ctl_args_t *args) { if (zone->control_update == NULL) { args->suppress = true; return KNOT_TXN_ENOTEXISTS; } if (args->data[KNOT_CTL_IDX_OWNER] == NULL || args->data[KNOT_CTL_IDX_TYPE] == NULL) { return KNOT_EINVAL; } knot_rrset_t *rrset; int ret = create_rrset(&rrset, zone, args, true); if (ret != KNOT_EOK) { return ret; } ret = zone_update_add(zone->control_update, rrset); knot_rrset_free(rrset, NULL); return ret; } static int zone_txn_unset(zone_t *zone, ctl_args_t *args) { if (zone->control_update == NULL) { args->suppress = true; return KNOT_TXN_ENOTEXISTS; } if (args->data[KNOT_CTL_IDX_OWNER] == NULL) { return KNOT_EINVAL; } // Remove specific record. if (args->data[KNOT_CTL_IDX_DATA] != NULL) { if (args->data[KNOT_CTL_IDX_TYPE] == NULL) { return KNOT_EINVAL; } knot_rrset_t *rrset; int ret = create_rrset(&rrset, zone, args, false); if (ret != KNOT_EOK) { return ret; } ret = zone_update_remove(zone->control_update, rrset); knot_rrset_free(rrset, NULL); return ret; } else { knot_dname_storage_t owner; int ret = get_owner(owner, sizeof(owner), zone->name, args); if (ret != KNOT_EOK) { return ret; } // Remove whole rrset. if (args->data[KNOT_CTL_IDX_TYPE] != NULL) { uint16_t type; if (knot_rrtype_from_string(args->data[KNOT_CTL_IDX_TYPE], &type) != 0) { return KNOT_EINVAL; } return zone_update_remove_rrset(zone->control_update, owner, type); // Remove whole node. } else { return zone_update_remove_node(zone->control_update, owner); } } } static bool zone_exists(const knot_dname_t *zone, void *data) { assert(zone); assert(data); knot_zonedb_t *db = data; return knot_zonedb_find(db, zone) != NULL; } static bool zone_names_distinct(const knot_dname_t *zone, void *data) { assert(zone); assert(data); knot_dname_t *zone_to_purge = data; return !knot_dname_is_equal(zone, zone_to_purge); } static int drop_journal_if_orphan(const knot_dname_t *for_zone, void *ctx) { server_t *server = ctx; zone_journal_t j = { &server->journaldb, for_zone }; if (!zone_exists(for_zone, server->zone_db)) { return journal_scrape_with_md(j, false); } return KNOT_EOK; } static int purge_orphan_member_cb(const knot_dname_t *member, const knot_dname_t *owner, const knot_dname_t *catz, const char *group, void *ctx) { server_t *server = ctx; if (zone_exists(member, server->zone_db)) { return KNOT_EOK; } const char *err_str = NULL; rcu_read_lock(); zone_t *cat_z = knot_zonedb_find(server->zone_db, catz); if (cat_z == NULL) { err_str = "existing"; } else if (!cat_z->is_catalog_flag) { err_str = "catalog"; } rcu_read_unlock(); if (err_str == NULL) { return KNOT_EOK; } knot_dname_txt_storage_t catz_str; (void)knot_dname_to_str(catz_str, catz, sizeof(catz_str)); log_zone_info(member, "member of a non-%s zone %s", err_str, catz_str); // Single-purpose fake zone_t containing only minimal data. // malloc() should suffice here, but clean zone_t is more mishandling-proof. zone_t *orphan = calloc(1, sizeof(zone_t)); if (orphan == NULL) { return KNOT_ENOMEM; } orphan->name = (knot_dname_t *)member; orphan->server = server; purge_flag_t params = PURGE_ZONE_TIMERS | PURGE_ZONE_JOURNAL | PURGE_ZONE_KASPDB | PURGE_ZONE_BEST | PURGE_ZONE_LOG; int ret = selective_zone_purge(conf(), orphan, params); free(orphan); if (ret != KNOT_EOK) { log_zone_error(member, "purge of an orphaned zone failed (%s)", knot_strerror(ret)); } // this deleting inside catalog DB iteration is OK, since // the deletion happens in RW txn, while the iteration in persistent RO txn ret = catalog_del(&server->catalog, member); if (ret != KNOT_EOK) { log_zone_error(member, "remove of an orphan from catalog failed (%s)", knot_strerror(ret)); } return KNOT_EOK; } static int catalog_orphans_sweep(server_t *server) { catalog_t *cat = &server->catalog; int ret2 = KNOT_EOK; int ret = catalog_begin(cat); if (ret == KNOT_EOK) { ret = catalog_apply(cat, NULL, purge_orphan_member_cb, server, false); if (ret != KNOT_EOK) { log_error("failed to purge orphan members data (%s)", knot_strerror(ret)); } ret2 = catalog_commit(cat); synchronize_rcu(); catalog_commit_cleanup(cat); if (ret2 != KNOT_EOK) { log_error("failed to update catalog (%s)", knot_strerror(ret)); } } else { log_error("can't open catalog for purging (%s)", knot_strerror(ret)); } return (ret == KNOT_EOK) ? ret2 : ret; } static void log_if_orphans_error(knot_dname_t *zone_name, int err, char *db_type, bool *failed) { if (err == KNOT_EOK || err == KNOT_ENOENT || err == KNOT_EFILE) { return; } *failed = true; const char *error = knot_strerror(err); char *msg = sprintf_alloc("control, failed to purge orphan from %s database (%s)", db_type, error); if (msg == NULL) { return; } if (zone_name == NULL) { log_error("%s", msg); } else { log_zone_error(zone_name, "%s", msg); } free(msg); } static int orphans_purge(ctl_args_t *args) { assert(args->data[KNOT_CTL_IDX_FILTER] != NULL); bool only_orphan = (strlen(args->data[KNOT_CTL_IDX_FILTER]) == 1); int ret; bool failed = false; if (args->data[KNOT_CTL_IDX_ZONE] == NULL) { // Purge KASP DB. if (only_orphan || MATCH_AND_FILTER(args, CTL_FILTER_PURGE_KASPDB)) { ret = kasp_db_sweep(&args->server->kaspdb, zone_exists, args->server->zone_db); log_if_orphans_error(NULL, ret, "KASP", &failed); } // Purge zone journals of unconfigured zones. if (only_orphan || MATCH_AND_FILTER(args, CTL_FILTER_PURGE_JOURNAL)) { ret = journals_walk(&args->server->journaldb, drop_journal_if_orphan, args->server); log_if_orphans_error(NULL, ret, "journal", &failed); } // Purge timers of unconfigured zones. if (only_orphan || MATCH_AND_FILTER(args, CTL_FILTER_PURGE_TIMERS)) { ret = zone_timers_sweep(&args->server->timerdb, zone_exists, args->server->zone_db); log_if_orphans_error(NULL, ret, "timer", &failed); } // Purge and remove orphan members of non-existing/non-catalog zones. if (only_orphan || MATCH_AND_FILTER(args, CTL_FILTER_PURGE_CATALOG)) { ret = catalog_orphans_sweep(args->server); log_if_orphans_error(NULL, ret, "catalog", &failed); } if (failed) { send_error(args, knot_strerror(KNOT_CTL_EZONE)); } } else { knot_dname_storage_t buff; while (true) { knot_dname_t *zone_name = knot_dname_from_str(buff, args->data[KNOT_CTL_IDX_ZONE], sizeof(buff)); if (zone_name == NULL) { log_ctl_zone_str_error(args->data[KNOT_CTL_IDX_ZONE], "control, error (%s)", knot_strerror(KNOT_EINVAL)); send_error(args, knot_strerror(KNOT_EINVAL)); return KNOT_EINVAL; } knot_dname_to_lower(zone_name); if (!zone_exists(zone_name, args->server->zone_db)) { // Purge KASP DB. if (only_orphan || MATCH_AND_FILTER(args, CTL_FILTER_PURGE_KASPDB)) { if (knot_lmdb_open(&args->server->kaspdb) == KNOT_EOK) { ret = kasp_db_delete_all(&args->server->kaspdb, zone_name); log_if_orphans_error(zone_name, ret, "KASP", &failed); } } // Purge zone journal. if (only_orphan || MATCH_AND_FILTER(args, CTL_FILTER_PURGE_JOURNAL)) { zone_journal_t j = { &args->server->journaldb, zone_name }; ret = journal_scrape_with_md(j, true); log_if_orphans_error(zone_name, ret, "journal", &failed); } // Purge zone timers. if (only_orphan || MATCH_AND_FILTER(args, CTL_FILTER_PURGE_TIMERS)) { ret = zone_timers_sweep(&args->server->timerdb, zone_names_distinct, zone_name); log_if_orphans_error(zone_name, ret, "timer", &failed); } // Purge Catalog. if (only_orphan || MATCH_AND_FILTER(args, CTL_FILTER_PURGE_CATALOG)) { ret = catalog_zone_purge(args->server, NULL, zone_name); log_if_orphans_error(zone_name, ret, "catalog", &failed); } if (failed) { send_error(args, knot_strerror(KNOT_ERROR)); failed = false; } } // Get next zone name. ret = knot_ctl_receive(args->ctl, &args->type, &args->data); if (ret != KNOT_EOK || args->type != KNOT_CTL_TYPE_DATA) { break; } strtolower((char *)args->data[KNOT_CTL_IDX_ZONE]); // Log the other zones the same way as the first one from process.c. log_ctl_zone_str_info(args->data[KNOT_CTL_IDX_ZONE], "control, received command '%s'", args->data[KNOT_CTL_IDX_CMD]); } } return KNOT_EOK; } static int zone_purge(zone_t *zone, ctl_args_t *args) { if (MATCH_OR_FILTER(args, CTL_FILTER_PURGE_EXPIRE)) { // Abort possible editing transaction. int ret = zone_txn_abort(zone, args); if (ret != KNOT_EOK && ret != KNOT_TXN_ENOTEXISTS) { log_zone_error(zone->name, "failed to abort pending transaction (%s)", knot_strerror(ret)); return ret; } // Expire the zone. // KNOT_EOK is the only return value from event_expire(). (void)schedule_trigger(zone, args, ZONE_EVENT_EXPIRE, true); } purge_flag_t params = MATCH_OR_FILTER(args, CTL_FILTER_PURGE_TIMERS) * PURGE_ZONE_TIMERS | MATCH_OR_FILTER(args, CTL_FILTER_PURGE_ZONEFILE) * PURGE_ZONE_ZONEFILE | MATCH_OR_FILTER(args, CTL_FILTER_PURGE_JOURNAL) * PURGE_ZONE_JOURNAL | MATCH_OR_FILTER(args, CTL_FILTER_PURGE_KASPDB) * PURGE_ZONE_KASPDB | MATCH_OR_FILTER(args, CTL_FILTER_PURGE_CATALOG) * PURGE_ZONE_CATALOG | PURGE_ZONE_NOSYNC; // Purge even zonefiles with disabled syncing. // Purge the requested zone data. return selective_zone_purge(conf(), zone, params); } static int send_stats_ctr(mod_ctr_t *ctr, uint64_t **stats_vals, unsigned threads, ctl_args_t *args, knot_ctl_data_t *data) { char index[128]; char value[32]; if (ctr->count == 1) { uint64_t counter = stats_get_counter(stats_vals, ctr->offset, threads); int ret = snprintf(value, sizeof(value), "%"PRIu64, counter); if (ret <= 0 || ret >= sizeof(value)) { return KNOT_ESPACE; } (*data)[KNOT_CTL_IDX_ID] = NULL; (*data)[KNOT_CTL_IDX_DATA] = value; ret = knot_ctl_send(args->ctl, KNOT_CTL_TYPE_DATA, data); if (ret != KNOT_EOK) { return ret; } } else { bool force = ctl_has_flag(args->data[KNOT_CTL_IDX_FLAGS], CTL_FLAG_FORCE); for (uint32_t i = 0; i < ctr->count; i++) { uint64_t counter = stats_get_counter(stats_vals, ctr->offset + i, threads); // Skip empty counters. if (counter == 0 && !force) { continue; } int ret; if (ctr->idx_to_str) { char *str = ctr->idx_to_str(i, ctr->count); if (str == NULL) { continue; } ret = snprintf(index, sizeof(index), "%s", str); free(str); } else { ret = snprintf(index, sizeof(index), "%u", i); } if (ret <= 0 || ret >= sizeof(index)) { return KNOT_ESPACE; } ret = snprintf(value, sizeof(value), "%"PRIu64, counter); if (ret <= 0 || ret >= sizeof(value)) { return KNOT_ESPACE; } (*data)[KNOT_CTL_IDX_ID] = index; (*data)[KNOT_CTL_IDX_DATA] = value; knot_ctl_type_t type = (i == 0) ? KNOT_CTL_TYPE_DATA : KNOT_CTL_TYPE_EXTRA; ret = knot_ctl_send(args->ctl, type, data); if (ret != KNOT_EOK) { return ret; } } } return KNOT_EOK; } static int modules_stats(list_t *query_modules, ctl_args_t *args, knot_dname_t *zone) { if (query_modules == NULL) { return KNOT_EOK; } const char *section = args->data[KNOT_CTL_IDX_SECTION]; const char *item = args->data[KNOT_CTL_IDX_ITEM]; knot_dname_txt_storage_t name = ""; knot_ctl_data_t data = { 0 }; bool section_found = (section == NULL) ? true : false; bool item_found = (item == NULL) ? true : false; knotd_mod_t *mod; WALK_LIST(mod, *query_modules) { // Skip modules without statistics. if (mod->stats_count == 0) { continue; } // Check for specific module. if (section != NULL) { if (section_found) { break; } else if (strcasecmp(mod->id->name + 1, section) == 0) { section_found = true; } else { continue; } } data[KNOT_CTL_IDX_SECTION] = mod->id->name + 1; unsigned threads = knotd_mod_threads(mod); for (int i = 0; i < mod->stats_count; i++) { mod_ctr_t *ctr = mod->stats_info + i; // Skip empty counter. if (ctr->name == NULL) { continue; } // Check for specific counter. if (item != NULL) { if (item_found) { break; } else if (strcasecmp(ctr->name, item) == 0) { item_found = true; } else { continue; } } // Prepare zone name if not already prepared. if (zone != NULL && name[0] == '\0') { if (knot_dname_to_str(name, zone, sizeof(name)) == NULL) { return KNOT_EINVAL; } data[KNOT_CTL_IDX_ZONE] = name; } data[KNOT_CTL_IDX_ITEM] = ctr->name; // Send the counters. int ret = send_stats_ctr(ctr, mod->stats_vals, threads, args, &data); if (ret != KNOT_EOK) { return ret; } } } return (section_found && item_found) ? KNOT_EOK : KNOT_ENOENT; } static int zone_stats(zone_t *zone, ctl_args_t *args) { return modules_stats(&zone->query_modules, args, zone->name); } static int ctl_zone(ctl_args_t *args, ctl_cmd_t cmd) { switch (cmd) { case CTL_ZONE_STATUS: return zones_apply(args, zone_status); case CTL_ZONE_RELOAD: return zones_apply(args, zone_reload); case CTL_ZONE_REFRESH: return zones_apply(args, zone_refresh); case CTL_ZONE_RETRANSFER: return zones_apply(args, zone_retransfer); case CTL_ZONE_NOTIFY: return zones_apply(args, zone_notify); case CTL_ZONE_FLUSH: return zones_apply(args, zone_flush); case CTL_ZONE_BACKUP: return zones_apply_backup(args, false); case CTL_ZONE_RESTORE: return zones_apply_backup(args, true); case CTL_ZONE_SIGN: return zones_apply(args, zone_sign); case CTL_ZONE_KEYS_LOAD: return zones_apply(args, zone_keys_load); case CTL_ZONE_KEY_ROLL: return zones_apply(args, zone_key_roll); case CTL_ZONE_KSK_SBM: return zones_apply(args, zone_ksk_sbm_confirm); case CTL_ZONE_FREEZE: return zones_apply(args, zone_freeze); case CTL_ZONE_THAW: return zones_apply(args, zone_thaw); case CTL_ZONE_XFR_FREEZE: return zones_apply(args, zone_xfr_freeze); case CTL_ZONE_XFR_THAW: return zones_apply(args, zone_xfr_thaw); case CTL_ZONE_READ: return zones_apply(args, zone_read); case CTL_ZONE_BEGIN: return zones_apply(args, zone_txn_begin); case CTL_ZONE_COMMIT: return zones_apply(args, zone_txn_commit); case CTL_ZONE_ABORT: return zones_apply(args, zone_txn_abort); case CTL_ZONE_DIFF: return zones_apply(args, zone_txn_diff); case CTL_ZONE_GET: return zones_apply(args, zone_txn_get); case CTL_ZONE_SET: return zones_apply(args, zone_txn_set); case CTL_ZONE_UNSET: return zones_apply(args, zone_txn_unset); case CTL_ZONE_PURGE: if (MATCH_AND_FILTER(args, CTL_FILTER_PURGE_ORPHAN)) { return orphans_purge(args); } else { return zones_apply(args, zone_purge); } case CTL_ZONE_STATS: return zones_apply(args, zone_stats); default: assert(0); return KNOT_EINVAL; } } static int server_status(ctl_args_t *args) { const char *type = args->data[KNOT_CTL_IDX_TYPE]; if (type == NULL || strlen(type) == 0) { return KNOT_EOK; } char buff[4096] = ""; int ret; if (strcasecmp(type, "version") == 0) { ret = snprintf(buff, sizeof(buff), "Version: %s", PACKAGE_VERSION); } else if (strcasecmp(type, "workers") == 0) { int running_bkg_wrk, wrk_queue; worker_pool_status(args->server->workers, false, &running_bkg_wrk, &wrk_queue); ret = snprintf(buff, sizeof(buff), "UDP workers: %zu, TCP workers: %zu, " "XDP workers: %zu, background workers: %zu (running: %d, pending: %d)", conf()->cache.srv_udp_threads, conf()->cache.srv_tcp_threads, conf()->cache.srv_xdp_threads, conf()->cache.srv_bg_threads, running_bkg_wrk, wrk_queue); } else if (strcasecmp(type, "configure") == 0) { ret = snprintf(buff, sizeof(buff), "%s", CONFIGURE_SUMMARY); } else { return KNOT_EINVAL; } if (ret <= 0 || ret >= sizeof(buff)) { return KNOT_ESPACE; } args->data[KNOT_CTL_IDX_DATA] = buff; return knot_ctl_send(args->ctl, KNOT_CTL_TYPE_DATA, &args->data); } static int ctl_server(ctl_args_t *args, ctl_cmd_t cmd) { int ret = KNOT_EOK; switch (cmd) { case CTL_STATUS: ret = server_status(args); if (ret != KNOT_EOK) { send_error(args, knot_strerror(ret)); } break; case CTL_STOP: ret = KNOT_CTL_ESTOP; break; case CTL_RELOAD: ret = server_reload(args->server, RELOAD_FULL); if (ret != KNOT_EOK) { send_error(args, knot_strerror(ret)); } break; default: assert(0); ret = KNOT_EINVAL; } return ret; } static int ctl_stats(ctl_args_t *args, ctl_cmd_t cmd) { const char *section = args->data[KNOT_CTL_IDX_SECTION]; const char *item = args->data[KNOT_CTL_IDX_ITEM]; bool found = (section == NULL) ? true : false; // Process server metrics. if (section == NULL || strcasecmp(section, "server") == 0) { char value[32]; knot_ctl_data_t data = { [KNOT_CTL_IDX_SECTION] = "server", [KNOT_CTL_IDX_DATA] = value }; for (const stats_item_t *i = server_stats; i->name != NULL; i++) { if (item != NULL) { if (found) { break; } else if (strcmp(i->name, item) == 0) { found = true; } else { continue; } } else { found = true; } data[KNOT_CTL_IDX_ITEM] = i->name; int ret = snprintf(value, sizeof(value), "%"PRIu64, i->val(args->server)); if (ret <= 0 || ret >= sizeof(value)) { ret = KNOT_ESPACE; send_error(args, knot_strerror(ret)); return ret; } ret = knot_ctl_send(args->ctl, KNOT_CTL_TYPE_DATA, &data); if (ret != KNOT_EOK) { send_error(args, knot_strerror(ret)); return ret; } } } // Process modules metrics. if (section == NULL || strncasecmp(section, "mod-", strlen("mod-")) == 0) { int ret = modules_stats(conf()->query_modules, args, NULL); if (ret != KNOT_EOK) { send_error(args, knot_strerror(ret)); return ret; } found = true; } if (!found) { send_error(args, knot_strerror(KNOT_EINVAL)); return KNOT_EINVAL; } return KNOT_EOK; } static int send_block_data(conf_io_t *io, knot_ctl_data_t *data) { knot_ctl_t *ctl = (knot_ctl_t *)io->misc; const yp_item_t *item = (io->key1 != NULL) ? io->key1 : io->key0; assert(item != NULL); char buff[YP_MAX_TXT_DATA_LEN + 1] = "\0"; (*data)[KNOT_CTL_IDX_DATA] = buff; // Format explicit binary data value. if (io->data.bin != NULL) { size_t buff_len = sizeof(buff); int ret = yp_item_to_txt(item, io->data.bin, io->data.bin_len, buff, &buff_len, YP_SNOQUOTE); if (ret != KNOT_EOK) { return ret; } return knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, data); // Format all multivalued item data if no specified index. } else if ((item->flags & YP_FMULTI) && io->data.index == 0) { size_t values = conf_val_count(io->data.val); for (size_t i = 0; i < values; i++) { conf_val(io->data.val); size_t buff_len = sizeof(buff); int ret = yp_item_to_txt(item, io->data.val->data, io->data.val->len, buff,&buff_len, YP_SNOQUOTE); if (ret != KNOT_EOK) { return ret; } knot_ctl_type_t type = (i == 0) ? KNOT_CTL_TYPE_DATA : KNOT_CTL_TYPE_EXTRA; ret = knot_ctl_send(ctl, type, data); if (ret != KNOT_EOK) { return ret; } conf_val_next(io->data.val); } return KNOT_EOK; // Format singlevalued item data or a specified one from multivalued. } else { conf_val(io->data.val); size_t buff_len = sizeof(buff); int ret = yp_item_to_txt(item, io->data.val->data, io->data.val->len, buff, &buff_len, YP_SNOQUOTE); if (ret != KNOT_EOK) { return ret; } return knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, data); } } static int send_block(conf_io_t *io) { knot_ctl_t *ctl = (knot_ctl_t *)io->misc; // Get possible error message. const char *err = io->error.str; if (err == NULL && io->error.code != KNOT_EOK) { err = knot_strerror(io->error.code); } knot_ctl_data_t data = { [KNOT_CTL_IDX_ERROR] = err, }; if (io->key0 != NULL) { data[KNOT_CTL_IDX_SECTION] = io->key0->name + 1; } if (io->key1 != NULL) { data[KNOT_CTL_IDX_ITEM] = io->key1->name + 1; } // Get the item prefix. switch (io->type) { case NEW: data[KNOT_CTL_IDX_FLAGS] = CTL_FLAG_DIFF_ADD; break; case OLD: data[KNOT_CTL_IDX_FLAGS] = CTL_FLAG_DIFF_REM; break; default: break; } knot_dname_txt_storage_t id; // Get the textual item id. if (io->id_len > 0 && io->key0 != NULL) { size_t id_len = sizeof(id); int ret = yp_item_to_txt(io->key0->var.g.id, io->id, io->id_len, id, &id_len, YP_SNOQUOTE); if (ret != KNOT_EOK) { return ret; } if (io->id_as_data) { data[KNOT_CTL_IDX_DATA] = id; } else { data[KNOT_CTL_IDX_ID] = id; } } if (io->data.val == NULL && io->data.bin == NULL) { return knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &data); } else { return send_block_data(io, &data); } } static int ctl_conf_txn(ctl_args_t *args, ctl_cmd_t cmd) { conf_io_t io = { .fcn = send_block, .misc = args->ctl }; int ret = KNOT_EOK; switch (cmd) { case CTL_CONF_BEGIN: ret = conf_io_begin(false); break; case CTL_CONF_ABORT: conf_io_abort(false); ret = KNOT_EOK; break; case CTL_CONF_COMMIT: // First check the database. ret = conf_io_check(&io); if (ret != KNOT_EOK) { // A semantic error is already sent by the check function. if (io.error.code != KNOT_EOK) { return KNOT_EOK; } // No transaction abort! break; } ret = conf_io_commit(false); if (ret != KNOT_EOK) { conf_io_abort(false); break; } ret = server_reload(args->server, RELOAD_COMMIT); break; default: assert(0); ret = KNOT_EINVAL; } if (ret != KNOT_EOK) { send_error(args, knot_strerror(ret)); } return ret; } static void list_zone(zone_t *zone, knot_ctl_t *ctl) { knot_dname_txt_storage_t buff; knot_dname_to_str(buff, zone->name, sizeof(buff)); knot_ctl_data_t data = { [KNOT_CTL_IDX_SECTION] = "zone", [KNOT_CTL_IDX_ID] = buff }; (void)knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &data); } static int list_zones(knot_zonedb_t *zonedb, knot_ctl_t *ctl) { assert(zonedb != NULL && ctl != NULL); knot_zonedb_foreach(zonedb, list_zone, ctl); return KNOT_EOK; } static int ctl_conf_list(ctl_args_t *args, ctl_cmd_t cmd) { conf_io_t io = { .fcn = send_block, .misc = args->ctl }; int ret = KNOT_EOK; while (true) { const char *key0 = args->data[KNOT_CTL_IDX_SECTION]; const char *key1 = args->data[KNOT_CTL_IDX_ITEM]; const char *id = args->data[KNOT_CTL_IDX_ID]; const char *flags = args->data[KNOT_CTL_IDX_FLAGS]; bool schema = ctl_has_flag(flags, CTL_FLAG_LIST_SCHEMA); bool current = !ctl_has_flag(flags, CTL_FLAG_LIST_TXN); bool zones = ctl_has_flag(flags, CTL_FLAG_LIST_ZONES); if (zones) { ret = list_zones(args->server->zone_db, args->ctl); } else { ret = conf_io_list(key0, key1, id, schema, current, &io); } if (ret != KNOT_EOK) { send_error(args, knot_strerror(ret)); break; } // Get next data unit. ret = knot_ctl_receive(args->ctl, &args->type, &args->data); if (ret != KNOT_EOK || args->type != KNOT_CTL_TYPE_DATA) { break; } } return ret; } static int ctl_conf_read(ctl_args_t *args, ctl_cmd_t cmd) { conf_io_t io = { .fcn = send_block, .misc = args->ctl }; int ret = KNOT_EOK; while (true) { const char *key0 = args->data[KNOT_CTL_IDX_SECTION]; const char *key1 = args->data[KNOT_CTL_IDX_ITEM]; const char *id = args->data[KNOT_CTL_IDX_ID]; ctl_log_conf_data(&args->data); switch (cmd) { case CTL_CONF_READ: ret = conf_io_get(key0, key1, id, true, &io); break; case CTL_CONF_DIFF: ret = conf_io_diff(key0, key1, id, &io); break; case CTL_CONF_GET: ret = conf_io_get(key0, key1, id, false, &io); break; default: assert(0); ret = KNOT_EINVAL; } if (ret != KNOT_EOK) { send_error(args, knot_strerror(ret)); break; } // Get next data unit. ret = knot_ctl_receive(args->ctl, &args->type, &args->data); if (ret != KNOT_EOK || args->type != KNOT_CTL_TYPE_DATA) { break; } } return ret; } static int ctl_conf_modify(ctl_args_t *args, ctl_cmd_t cmd) { // Start child transaction. int ret = conf_io_begin(true); if (ret != KNOT_EOK) { send_error(args, knot_strerror(ret)); return ret; } while (true) { const char *key0 = args->data[KNOT_CTL_IDX_SECTION]; const char *key1 = args->data[KNOT_CTL_IDX_ITEM]; const char *id = args->data[KNOT_CTL_IDX_ID]; const char *data = args->data[KNOT_CTL_IDX_DATA]; ctl_log_conf_data(&args->data); switch (cmd) { case CTL_CONF_SET: ret = conf_io_set(key0, key1, id, data); break; case CTL_CONF_UNSET: ret = conf_io_unset(key0, key1, id, data); break; default: assert(0); ret = KNOT_EINVAL; } if (ret != KNOT_EOK) { send_error(args, knot_strerror(ret)); break; } // Get next data unit. ret = knot_ctl_receive(args->ctl, &args->type, &args->data); if (ret != KNOT_EOK || args->type != KNOT_CTL_TYPE_DATA) { break; } } // Finish child transaction. if (ret == KNOT_EOK) { ret = conf_io_commit(true); if (ret != KNOT_EOK) { send_error(args, knot_strerror(ret)); } } else { conf_io_abort(true); } return ret; } typedef struct { const char *name; int (*fcn)(ctl_args_t *, ctl_cmd_t); } desc_t; static const desc_t cmd_table[] = { [CTL_NONE] = { "" }, [CTL_STATUS] = { "status", ctl_server }, [CTL_STOP] = { "stop", ctl_server }, [CTL_RELOAD] = { "reload", ctl_server }, [CTL_STATS] = { "stats", ctl_stats }, [CTL_ZONE_STATUS] = { "zone-status", ctl_zone }, [CTL_ZONE_RELOAD] = { "zone-reload", ctl_zone }, [CTL_ZONE_REFRESH] = { "zone-refresh", ctl_zone }, [CTL_ZONE_RETRANSFER] = { "zone-retransfer", ctl_zone }, [CTL_ZONE_NOTIFY] = { "zone-notify", ctl_zone }, [CTL_ZONE_FLUSH] = { "zone-flush", ctl_zone }, [CTL_ZONE_BACKUP] = { "zone-backup", ctl_zone }, [CTL_ZONE_RESTORE] = { "zone-restore", ctl_zone }, [CTL_ZONE_SIGN] = { "zone-sign", ctl_zone }, [CTL_ZONE_KEYS_LOAD] = { "zone-keys-load", ctl_zone }, [CTL_ZONE_KEY_ROLL] = { "zone-key-rollover", ctl_zone }, [CTL_ZONE_KSK_SBM] = { "zone-ksk-submitted", ctl_zone }, [CTL_ZONE_FREEZE] = { "zone-freeze", ctl_zone }, [CTL_ZONE_THAW] = { "zone-thaw", ctl_zone }, [CTL_ZONE_XFR_FREEZE] = { "zone-xfr-freeze", ctl_zone }, [CTL_ZONE_XFR_THAW] = { "zone-xfr-thaw", ctl_zone }, [CTL_ZONE_READ] = { "zone-read", ctl_zone }, [CTL_ZONE_BEGIN] = { "zone-begin", ctl_zone }, [CTL_ZONE_COMMIT] = { "zone-commit", ctl_zone }, [CTL_ZONE_ABORT] = { "zone-abort", ctl_zone }, [CTL_ZONE_DIFF] = { "zone-diff", ctl_zone }, [CTL_ZONE_GET] = { "zone-get", ctl_zone }, [CTL_ZONE_SET] = { "zone-set", ctl_zone }, [CTL_ZONE_UNSET] = { "zone-unset", ctl_zone }, [CTL_ZONE_PURGE] = { "zone-purge", ctl_zone }, [CTL_ZONE_STATS] = { "zone-stats", ctl_zone }, [CTL_CONF_LIST] = { "conf-list", ctl_conf_list }, [CTL_CONF_READ] = { "conf-read", ctl_conf_read }, [CTL_CONF_BEGIN] = { "conf-begin", ctl_conf_txn }, [CTL_CONF_COMMIT] = { "conf-commit", ctl_conf_txn }, [CTL_CONF_ABORT] = { "conf-abort", ctl_conf_txn }, [CTL_CONF_DIFF] = { "conf-diff", ctl_conf_read }, [CTL_CONF_GET] = { "conf-get", ctl_conf_read }, [CTL_CONF_SET] = { "conf-set", ctl_conf_modify }, [CTL_CONF_UNSET] = { "conf-unset", ctl_conf_modify }, }; #define MAX_CTL_CODE (sizeof(cmd_table) / sizeof(desc_t) - 1) const char *ctl_cmd_to_str(ctl_cmd_t cmd) { if (cmd <= CTL_NONE || cmd > MAX_CTL_CODE) { return NULL; } return cmd_table[cmd].name; } ctl_cmd_t ctl_str_to_cmd(const char *cmd_str) { if (cmd_str == NULL) { return CTL_NONE; } for (ctl_cmd_t cmd = CTL_NONE + 1; cmd <= MAX_CTL_CODE; cmd++) { if (strcmp(cmd_str, cmd_table[cmd].name) == 0) { return cmd; } } return CTL_NONE; } int ctl_exec(ctl_cmd_t cmd, ctl_args_t *args) { if (args == NULL) { return KNOT_EINVAL; } return cmd_table[cmd].fcn(args, cmd); } bool ctl_has_flag(const char *flags, const char *flag) { if (flags == NULL || flag == NULL) { return false; } return strstr(flags, flag) != NULL; }