Forked from
Knot projects / Knot DNS
6872 commits behind the upstream repository.
-
Jan Včelák authoredJan Včelák authored
commands.c 31.12 KiB
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <unistd.h>
#include "knot/common/log.h"
#include "knot/conf/confio.h"
#include "knot/ctl/commands.h"
#include "knot/events/handlers.h"
#include "knot/updates/zone-update.h"
#include "libknot/libknot.h"
#include "libknot/yparser/yptrafo.h"
#include "contrib/macros.h"
#include "contrib/mempattern.h"
#include "contrib/string.h"
#include "zscanner/scanner.h"
void ctl_log_data(knot_ctl_data_t *data)
{
if (data == NULL) {
return;
}
const char *zone = (*data)[KNOT_CTL_IDX_ZONE];
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 (zone != NULL) {
log_debug("control, zone '%s'", zone);
} else if (section != NULL) {
log_debug("control, 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_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);
uint8_t buff[KNOT_DNAME_MAXLEN];
knot_dname_t *dname = knot_dname_from_str(buff, name, sizeof(buff));
if (dname == NULL) {
return KNOT_EINVAL;
}
int ret = knot_dname_to_lower(dname);
if (ret != KNOT_EOK) {
return ret;
}
*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 *))
{
// Process all configured zones if none is specified.
if (args->data[KNOT_CTL_IDX_ZONE] == NULL) {
knot_zonedb_foreach(args->server->zone_db, fcn, args);
return KNOT_EOK;
}
int ret = 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_zone_str_debug(args->data[KNOT_CTL_IDX_ZONE],
"control, (%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;
}
ctl_log_data(&args->data);
}
return ret;
}
static int zone_status(zone_t *zone, ctl_args_t *args)
{
// Zone name.
char name[KNOT_DNAME_TXT_MAXLEN + 1];
if (knot_dname_to_str(name, zone->name, sizeof(name)) == NULL) {
return KNOT_EINVAL;
}
knot_ctl_data_t data = {
[KNOT_CTL_IDX_ZONE] = name
};
// Zone type.
data[KNOT_CTL_IDX_TYPE] = "type";
if (zone_is_slave(conf(), zone)) {
data[KNOT_CTL_IDX_DATA] = "slave";
} else {
data[KNOT_CTL_IDX_DATA] = "master";
}
int ret = knot_ctl_send(args->ctl, KNOT_CTL_TYPE_DATA, &data);
if (ret != KNOT_EOK) {
return ret;
}
// Zone serial.
data[KNOT_CTL_IDX_TYPE] = "serial";
char buff[128];
if (zone->contents != NULL) {
knot_rdataset_t *soa = node_rdataset(zone->contents->apex,
KNOT_RRTYPE_SOA);
ret = snprintf(buff, sizeof(buff), "%u", knot_soa_serial(soa));
} else {
ret = snprintf(buff, sizeof(buff), "none");
}
if (ret < 0 || ret >= sizeof(buff)) {
return KNOT_ESPACE;
}
data[KNOT_CTL_IDX_DATA] = buff;
ret = knot_ctl_send(args->ctl, KNOT_CTL_TYPE_EXTRA, &data);
if (ret != KNOT_EOK) {
return ret;
}
// Next event.
data[KNOT_CTL_IDX_TYPE] = "next-event";
zone_event_type_t next_type = ZONE_EVENT_INVALID;
time_t next_time = zone_events_get_next(zone, &next_type);
if (next_type != ZONE_EVENT_INVALID) {
const char *next_name = zone_events_get_name(next_type);
next_time = next_time - time(NULL);
if (next_time < 0) {
ret = snprintf(buff, sizeof(buff), "%s pending",
next_name);
} else {
ret = snprintf(buff, sizeof(buff), "%s in %lldh%lldm%llds",
next_name,
(long long)(next_time / 3600),
(long long)(next_time % 3600) / 60,
(long long)(next_time % 60));
}
if (ret < 0 || ret >= sizeof(buff)) {
return KNOT_ESPACE;
}
data[KNOT_CTL_IDX_DATA] = buff;
} else {
data[KNOT_CTL_IDX_DATA] = "idle";
}
ret = knot_ctl_send(args->ctl, KNOT_CTL_TYPE_EXTRA, &data);
if (ret != KNOT_EOK) {
return ret;
}
// DNSSEC re-signing time.
data[KNOT_CTL_IDX_TYPE] = "auto-dnssec";
conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name);
if (conf_bool(&val)) {
struct tm time_gm = { 0 };
time_t refresh_at = zone_events_get_time(zone, ZONE_EVENT_DNSSEC);
gmtime_r(&refresh_at, &time_gm);
size_t written = strftime(buff, sizeof(buff), KNOT_LOG_TIME_FORMAT,
&time_gm);
if (written == 0) {
return KNOT_ESPACE;
}
data[KNOT_CTL_IDX_DATA] = buff;
} else {
data[KNOT_CTL_IDX_DATA] = "disabled";
}
ret = knot_ctl_send(args->ctl, KNOT_CTL_TYPE_EXTRA, &data);
if (ret != KNOT_EOK) {
return ret;
}
// Zone transaction.
data[KNOT_CTL_IDX_TYPE] = "transaction";
data[KNOT_CTL_IDX_DATA] = (zone->control_update != NULL) ? "open" : "none";
return knot_ctl_send(args->ctl, KNOT_CTL_TYPE_EXTRA, &data);
}
static int zone_reload(zone_t *zone, ctl_args_t *args)
{
UNUSED(args);
if (zone->flags & ZONE_EXPIRED) {
return KNOT_ENOTSUP;
}
zone_events_schedule(zone, ZONE_EVENT_LOAD, ZONE_EVENT_NOW);
return KNOT_EOK;
}
static int zone_refresh(zone_t *zone, ctl_args_t *args)
{
UNUSED(args);
if (!zone_is_slave(conf(), zone)) {
return KNOT_ENOTSUP;
}
zone_events_schedule(zone, ZONE_EVENT_REFRESH, ZONE_EVENT_NOW);
return KNOT_EOK;
}
static int zone_retransfer(zone_t *zone, ctl_args_t *args)
{
UNUSED(args);
if (!zone_is_slave(conf(), zone)) {
return KNOT_ENOTSUP;
}
zone->flags |= ZONE_FORCE_AXFR;
zone_events_schedule(zone, ZONE_EVENT_REFRESH, ZONE_EVENT_NOW);
return KNOT_EOK;
}
static int zone_flush(zone_t *zone, ctl_args_t *args)
{
UNUSED(args);
if (ctl_has_flag(args->data[KNOT_CTL_IDX_FLAGS], CTL_FLAG_FORCE)) {
zone->flags |= ZONE_FORCE_FLUSH;
}
zone_events_schedule(zone, ZONE_EVENT_FLUSH, ZONE_EVENT_NOW);
return KNOT_EOK;
}
static int zone_sign(zone_t *zone, ctl_args_t *args)
{
UNUSED(args);
conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name);
if (!conf_bool(&val)) {
return KNOT_ENOTSUP;
}
zone->flags |= ZONE_FORCE_RESIGN;
zone_events_schedule(zone, ZONE_EVENT_DNSSEC, ZONE_EVENT_NOW);
return KNOT_EOK;
}
static int zone_txn_begin(zone_t *zone, ctl_args_t *args)
{
UNUSED(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_SIGN | UPDATE_STRICT);
if (ret != KNOT_EOK) {
free(zone->control_update);
zone->control_update = NULL;
return ret;
}
return KNOT_EOK;
}
static int zone_txn_commit(zone_t *zone, ctl_args_t *args)
{
UNUSED(args);
if (zone->control_update == NULL) {
return KNOT_TXN_ENOTEXISTS;
}
int ret = zone_update_commit(conf(), zone->control_update);
if (ret != KNOT_EOK) {
return ret;
}
zone_update_clear(zone->control_update);
free(zone->control_update);
zone->control_update = NULL;
/* Sync zonefile immediately if configured. */
conf_val_t val = conf_zone_get(conf(), C_ZONEFILE_SYNC, zone->name);
if (conf_int(&val) == 0) {
zone_events_schedule(zone, ZONE_EVENT_FLUSH, ZONE_EVENT_NOW);
}
zone_events_schedule(zone, ZONE_EVENT_NOTIFY, ZONE_EVENT_NOW);
return KNOT_EOK;
}
static int zone_txn_abort(zone_t *zone, ctl_args_t *args)
{
UNUSED(args);
if (zone->control_update == NULL) {
return KNOT_TXN_ENOTEXISTS;
}
zone_update_clear(zone->control_update);
free(zone->control_update);
zone->control_update = NULL;
return KNOT_EOK;
}
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;
char zone[KNOT_DNAME_TXT_MAXLEN + 1];
char owner[KNOT_DNAME_TXT_MAXLEN + 1];
char ttl[16];
char type[32];
char rdata[2 * 65536];
} send_ctx_t;
static send_ctx_t *create_send_ctx(const knot_dname_t *zone_name, ctl_args_t *args)
{
send_ctx_t *ctx = mm_alloc(&args->mm, sizeof(*ctx));
if (ctx == NULL) {
return NULL;
}
memset(ctx, 0, sizeof(*ctx));
ctx->args = args;
// Set the dump style.
ctx->style.show_ttl = true;
ctx->style.human_tmstamp = 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) {
mm_free(&args->mm, ctx);
return NULL;
}
// 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) {
mm_free(&args->mm, ctx);
return NULL;
}
ctx->type_filter = type;
} else {
ctx->type_filter = -1;
}
return ctx;
}
static int send_rrset(knot_rrset_t *rrset, send_ctx_t *ctx)
{
int ret = snprintf(ctx->ttl, sizeof(ctx->ttl), "%u", knot_rrset_ttl(rrset));
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.rr_count; ++i) {
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;
int 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;
}
knot_dname_t *dname = knot_dname_from_str(out, owner, out_len);
if (dname == NULL) {
return KNOT_EINVAL;
}
int ret = knot_dname_to_lower(dname);
if (ret != KNOT_EOK) {
return ret;
}
prefix_len = knot_dname_size(out);
if (prefix_len <= 0) {
return KNOT_EINVAL;
}
// Ignore trailing dot.
prefix_len--;
}
// Append the origin.
if (!fqdn) {
int 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 = create_send_ctx(zone->name, args);
if (ctx == NULL) {
return KNOT_ENOMEM;
}
int ret = KNOT_EOK;
if (args->data[KNOT_CTL_IDX_OWNER] != NULL) {
uint8_t owner[KNOT_DNAME_MAXLEN];
ret = get_owner(owner, sizeof(owner), zone->name, args);
if (ret != KNOT_EOK) {
goto zone_read_failed;
}
const zone_node_t *node = zone_contents_find_node(zone->contents, owner);
if (node == NULL) {
ret = KNOT_ENONODE;
goto zone_read_failed;
}
ret = send_node((zone_node_t *)node, ctx);
} else if (zone->contents != NULL) {
ret = zone_contents_tree_apply_inorder(zone->contents, send_node, ctx);
}
zone_read_failed:
mm_free(&args->mm, ctx);
return ret;
}
static int zone_flag_txn_get(zone_t *zone, ctl_args_t *args, const char *flag)
{
if (zone->control_update == NULL) {
return KNOT_TXN_ENOTEXISTS;
}
send_ctx_t *ctx = create_send_ctx(zone->name, args);
if (ctx == NULL) {
return KNOT_ENOMEM;
}
ctx->data[KNOT_CTL_IDX_FLAGS] = flag;
int ret = KNOT_EOK;
if (args->data[KNOT_CTL_IDX_OWNER] != NULL) {
uint8_t owner[KNOT_DNAME_MAXLEN];
ret = get_owner(owner, sizeof(owner), zone->name, args);
if (ret != KNOT_EOK) {
goto zone_txn_get_failed;
}
const zone_node_t *node = zone_update_get_node(zone->control_update, owner);
if (node == NULL) {
ret = KNOT_ENONODE;
goto zone_txn_get_failed;
}
ret = send_node((zone_node_t *)node, ctx);
} else {
zone_update_iter_t it;
ret = zone_update_iter(&it, zone->control_update);
if (ret != KNOT_EOK) {
goto zone_txn_get_failed;
}
const zone_node_t *iter_node = zone_update_iter_val(&it);
while (iter_node != NULL) {
ret = send_node((zone_node_t *)iter_node, ctx);
if (ret != KNOT_EOK) {
zone_update_iter_finish(&it);
goto zone_txn_get_failed;
}
ret = zone_update_iter_next(&it);
if (ret != KNOT_EOK) {
zone_update_iter_finish(&it);
goto zone_txn_get_failed;
}
iter_node = zone_update_iter_val(&it);
}
zone_update_iter_finish(&it);
}
zone_txn_get_failed:
mm_free(&args->mm, ctx);
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_REM : CTL_FLAG_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, true) :
changeset_iter_add(&it, ch, true);
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.
ret = send_changeset_part(ch, ctx, false);
if (ret != KNOT_EOK) {
return ret;
}
return KNOT_EOK;
}
static int zone_txn_diff(zone_t *zone, ctl_args_t *args)
{
if (zone->control_update == NULL) {
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_ADD);
}
send_ctx_t *ctx = create_send_ctx(zone->name, args);
if (ctx == NULL) {
return KNOT_ENOMEM;
}
int ret = send_changeset(&zone->control_update->change, ctx);
mm_free(&args->mm, ctx);
return ret;
}
static int get_ttl(zone_t *zone, ctl_args_t *args, uint32_t *ttl)
{
uint8_t owner[KNOT_DNAME_MAXLEN];
int ret = get_owner(owner, sizeof(owner), zone->name, args);
if (ret != KNOT_EOK) {
return ret;
}
const zone_node_t *node = zone_update_get_node(zone->control_update, owner);
if (node == NULL) {
return KNOT_ETTL;
}
uint16_t type;
if (knot_rrtype_from_string(args->data[KNOT_CTL_IDX_TYPE], &type) != 0) {
return KNOT_EINVAL;
}
knot_rdataset_t *rdataset = node_rdataset(node, type);
if (rdataset == NULL) {
return KNOT_ETTL;
}
*ttl = knot_rdataset_ttl(rdataset);
return KNOT_EOK;
}
static int create_rrset(knot_rrset_t **rrset, zone_t *zone, ctl_args_t *args,
bool need_ttl)
{
char origin_buff[KNOT_DNAME_TXT_MAXLEN + 1];
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(((send_ctx_t *)0)->owner) +
sizeof(((send_ctx_t *)0)->ttl) +
sizeof(((send_ctx_t *)0)->type) +
sizeof(((send_ctx_t *)0)->rdata);
char *buff = mm_alloc(&args->mm, buff_len);
if (buff == NULL) {
return KNOT_ENOMEM;
}
uint32_t default_ttl = 0;
if (ttl == NULL) {
int ret = get_ttl(zone, args, &default_ttl);
if (need_ttl && ret != KNOT_EOK) {
mm_free(&args->mm, buff);
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) {
mm_free(&args->mm, buff);
return KNOT_ESPACE;
}
size_t rdata_len = ret;
// Initialize RR parser.
zs_scanner_t *scanner = mm_alloc(&args->mm, sizeof(*scanner));
if (scanner == NULL) {
ret = KNOT_ENOMEM;
goto parser_failed;
}
// Parse the record.
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;
}
// Create output rrset.
*rrset = knot_rrset_new(scanner->r_owner, scanner->r_type,
scanner->r_class, NULL);
if (*rrset == NULL) {
ret = KNOT_ENOMEM;
goto parser_failed;
}
ret = knot_rrset_add_rdata(*rrset, scanner->r_data, scanner->r_data_length,
scanner->r_ttl, NULL);
parser_failed:
zs_deinit(scanner);
mm_free(&args->mm, scanner);
mm_free(&args->mm, buff);
return ret;
}
static int zone_txn_set(zone_t *zone, ctl_args_t *args)
{
if (zone->control_update == NULL) {
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);
// Silently update TTL.
if (ret == KNOT_ETTL) {
ret = KNOT_EOK;
}
return ret;
}
static int zone_txn_unset(zone_t *zone, ctl_args_t *args)
{
if (zone->control_update == NULL) {
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 {
uint8_t owner[KNOT_DNAME_MAXLEN];
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 int zone_purge(zone_t *zone, ctl_args_t *args)
{
UNUSED(args);
// Abort possible editing transaction.
(void)zone_txn_abort(zone, args);
// Expire the zone.
event_expire(conf(), zone);
// Purge the zone file.
char *zonefile = conf_zonefile(conf(), zone->name);
(void)unlink(zonefile);
free(zonefile);
// Purge the zone journal.
char *journalfile = conf_journalfile(conf(), zone->name);
(void)unlink(journalfile);
free(journalfile);
// TODO: Purge the zone timers (after zone events refactoring).
return KNOT_EOK;
}
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_FLUSH:
return zones_apply(args, zone_flush);
case CTL_ZONE_SIGN:
return zones_apply(args, zone_sign);
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:
return zones_apply(args, zone_purge);
default:
assert(0);
return KNOT_EINVAL;
}
}
static int ctl_server(ctl_args_t *args, ctl_cmd_t cmd)
{
int ret = KNOT_EOK;
switch (cmd) {
case CTL_STATUS:
ret = KNOT_EOK;
break;
case CTL_STOP:
ret = KNOT_CTL_ESTOP;
break;
case CTL_RELOAD:
ret = server_reload(args->server, conf()->filename, true);
if (ret != KNOT_EOK) {
send_error(args, knot_strerror(ret));
}
break;
default:
assert(0);
ret = KNOT_EINVAL;
}
return ret;
}
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_ADD; break;
case OLD: data[KNOT_CTL_IDX_FLAGS] = CTL_FLAG_REM; break;
default: break;
}
char id[KNOT_DNAME_TXT_MAXLEN + 1] = "\0";
// 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) {
// Error response is already sent by the check function.
// No transaction abort!
return KNOT_EOK;
}
ret = conf_io_commit(false);
if (ret != KNOT_EOK) {
conf_io_abort(false);
break;
}
ret = server_reload(args->server, NULL, false);
break;
default:
assert(0);
ret = KNOT_EINVAL;
}
if (ret != KNOT_EOK) {
send_error(args, knot_strerror(ret));
}
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];
switch (cmd) {
case CTL_CONF_LIST:
ret = conf_io_list(key0, &io);
break;
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;
}
ctl_log_data(&args->data);
}
return ret;
}
static int ctl_conf_modify(ctl_args_t *args, ctl_cmd_t cmd)
{
conf_io_t io = {
.fcn = send_block,
.misc = args->ctl
};
// 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];
switch (cmd) {
case CTL_CONF_SET:
ret = conf_io_set(key0, key1, id, data, &io);
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;
}
ctl_log_data(&args->data);
}
// 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_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_FLUSH] = { "zone-flush", ctl_zone },
[CTL_ZONE_SIGN] = { "zone-sign", 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_CONF_LIST] = { "conf-list", ctl_conf_read },
[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;
}