Forked from
Knot projects / Knot DNS
7563 commits behind the upstream repository.
-
Daniel Salzman authoredDaniel Salzman authored
commands.c 14.58 KiB
/* Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <urcu.h>
#include "knot/common/log.h"
#include "knot/conf/confio.h"
#include "knot/ctl/commands.h"
#include "knot/ctl/remote.h"
#include "libknot/libknot.h"
#include "contrib/macros.h"
#include "contrib/string.h"
/*! \brief Callback prototype for per-zone operations. */
typedef int (remote_zonef_t)(zone_t *, remote_cmdargs_t *);
/*! \brief Append data to the output buffer. */
static int cmdargs_append(remote_cmdargs_t *args, const char *data, size_t size)
{
assert(args);
assert(size <= CMDARGS_ALLOC_BLOCK);
if (args->response_size + size >= args->response_max) {
size_t new_max = args->response_max + CMDARGS_ALLOC_BLOCK;
char *new_response = realloc(args->response, new_max);
if (!new_response) {
return KNOT_ENOMEM;
}
args->response = new_response;
args->response_max = new_max;
}
memcpy(args->response + args->response_size, data, size);
args->response_size += size;
args->response[args->response_size] = '\0';
return KNOT_EOK;
}
static void zone_apply(server_t *s, remote_cmdargs_t *a, remote_zonef_t *cb,
const knot_dname_t *dname)
{
int ret = KNOT_ENOZONE;
zone_t *zone = knot_zonedb_find(s->zone_db, dname);
if (zone != NULL) {
ret = cb(zone, a);
}
if (ret != KNOT_EOK) {
char name[KNOT_DNAME_TXT_MAXLEN] = "";
knot_dname_to_str(name, dname, sizeof(name));
char *msg = sprintf_alloc("%signoring [%s] %s",
(a->response_size > 0) ? "\n" : "",
name, knot_strerror(ret));
cmdargs_append(a, msg, strlen(msg));
free(msg);
}
}
/*! \brief Apply callback to all zones specified by RDATA. */
static int zones_apply(server_t *s, remote_cmdargs_t *a, remote_zonef_t *cb)
{
assert(s);
assert(a);
assert(cb);
rcu_read_lock();
/* Process all configured zones if none is specified. */
if (a->argc == 0) {
knot_zonedb_foreach(s->zone_db, cb, a);
} else {
/* Process all specified zones. */
for (unsigned i = 0; i < a->argc; i++) {
const knot_rrset_t *rr = &a->arg[i];
if (rr->type != KNOT_RRTYPE_NS) {
continue;
}
for (unsigned j = 0; j < rr->rrs.rr_count; j++) {
zone_apply(s, a, cb, knot_ns_name(&rr->rrs, j));
}
}
}
rcu_read_unlock();
return KNOT_EOK;
}
static char *dnssec_info(const zone_t *zone, char *buf, size_t buf_size)
{
assert(zone);
assert(buf);
time_t refresh_at = zone_events_get_time(zone, ZONE_EVENT_DNSSEC);
struct tm time_gm = { 0 };
gmtime_r(&refresh_at, &time_gm);
size_t written = strftime(buf, buf_size, KNOT_LOG_TIME_FORMAT, &time_gm);
if (written == 0) {
return NULL;
}
return buf;
}
static int zone_status(zone_t *zone, remote_cmdargs_t *a)
{
/* Fetch latest serial. */
uint32_t serial = 0;
if (zone->contents) {
const knot_rdataset_t *soa_rrs = node_rdataset(zone->contents->apex,
KNOT_RRTYPE_SOA);
assert(soa_rrs != NULL);
serial = knot_soa_serial(soa_rrs);
}
/* Fetch next zone event. */
char when[128] = { '\0' };
zone_event_type_t next_type = ZONE_EVENT_INVALID;
const char *next_name = "";
time_t next_time = zone_events_get_next(zone, &next_type);
if (next_type != ZONE_EVENT_INVALID) {
next_name = zone_events_get_name(next_type);
next_time = next_time - time(NULL);
if (next_time < 0) {
memcpy(when, "pending", strlen("pending"));
} else if (snprintf(when, sizeof(when),
"in %lldh%lldm%llds",
(long long)(next_time / 3600),
(long long)(next_time % 3600) / 60,
(long long)(next_time % 60)) < 0) {
return KNOT_ESPACE;
}
} else {
memcpy(when, "idle", strlen("idle"));
}
/* Prepare zone info. */
char buf[512] = { '\0' };
char dnssec_buf[128] = { '\0' };
char *zone_name = knot_dname_to_str_alloc(zone->name);
conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name);
bool dnssec_enable = conf_bool(&val);
bool is_slave = zone_is_slave(conf(), zone);
int n = snprintf(buf, sizeof(buf),
"%s%s\ttype=%s | serial=%u | %s %s | %s %s",
(a->response_size > 0) ? "\n" : "",
zone_name,
is_slave ? "slave" : "master",
serial,
next_name,
when,
dnssec_enable ? "automatic DNSSEC, resigning at:" : "DNSSEC signing disabled",
dnssec_enable ? dnssec_info(zone, dnssec_buf, sizeof(dnssec_buf)) : "");
free(zone_name);
if (n < 0 || n >= sizeof(buf)) {
return KNOT_ESPACE;
}
return cmdargs_append(a, buf, n);
}
static int zone_reload(zone_t *zone, remote_cmdargs_t *a)
{
UNUSED(a);
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, remote_cmdargs_t *a)
{
UNUSED(a);
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, remote_cmdargs_t *a)
{
UNUSED(a);
if (!zone_is_slave(conf(), zone)) {
return KNOT_ENOTSUP;
}
zone->flags |= ZONE_FORCE_AXFR;
zone_events_schedule(zone, ZONE_EVENT_XFER, ZONE_EVENT_NOW);
return KNOT_EOK;
}
static int zone_flush(zone_t *zone, remote_cmdargs_t *a)
{
UNUSED(a);
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, remote_cmdargs_t *a)
{
UNUSED(a);
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 ctl_status(server_t *s, remote_cmdargs_t *a)
{
UNUSED(s);
const char *msg = "Running";
cmdargs_append(a, msg, strlen(msg));
return KNOT_EOK;
}
static int ctl_stop(server_t *s, remote_cmdargs_t *a)
{
UNUSED(s);
UNUSED(a);
return KNOT_CTL_ESTOP;
}
static int ctl_reload(server_t *s, remote_cmdargs_t *a)
{
UNUSED(s);
UNUSED(a);
return server_reload(s, conf()->filename, true);
}
static int ctl_zone_status(server_t *s, remote_cmdargs_t *a)
{
return zones_apply(s, a, zone_status);
}
static int ctl_zone_reload(server_t *s, remote_cmdargs_t *a)
{
return zones_apply(s, a, zone_reload);
}
static int ctl_zone_refresh(server_t *s, remote_cmdargs_t *a)
{
return zones_apply(s, a, zone_refresh);
}
static int ctl_zone_retransfer(server_t *s, remote_cmdargs_t *a)
{
return zones_apply(s, a, zone_retransfer);
}
static int ctl_zone_flush(server_t *s, remote_cmdargs_t *a)
{
return zones_apply(s, a, zone_flush);
}
static int ctl_zone_sign(server_t *s, remote_cmdargs_t *a)
{
return zones_apply(s, a, zone_sign);
}
static int format_item(conf_io_t *io)
{
remote_cmdargs_t *a = (remote_cmdargs_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);
}
// Get the item key and data strings.
char *key = conf_io_txt_key(io);
if (key == NULL) {
return KNOT_ERROR;
}
char *data = conf_io_txt_data(io);
// Format the item.
char *item = sprintf_alloc(
"%s%s%s%s%s%s%s",
(a->response_size > 0 ? "\n" : ""),
(err != NULL ? "Error (" : ""),
(err != NULL ? err : ""),
(err != NULL ? "): " : ""),
key,
(data != NULL ? " = " : ""),
(data != NULL ? data : ""));
free(key);
free(data);
if (item == NULL) {
return KNOT_ENOMEM;
}
// Append the item.
int ret = cmdargs_append(a, item, strlen(item));
free(item);
if (ret != KNOT_EOK) {
return ret;
}
return KNOT_EOK;
}
static int ctl_conf_begin(server_t *s, remote_cmdargs_t *a)
{
UNUSED(s);
UNUSED(a);
return conf_io_begin(false);
}
static int ctl_conf_commit(server_t *s, remote_cmdargs_t *a)
{
UNUSED(a);
conf_io_t io = {
.fcn = format_item,
.misc = a
};
// First check the database.
int ret = conf_io_check(&io);
if (ret != KNOT_EOK) {
conf_io_abort(false);
return ret;
}
ret = conf_io_commit(false);
if (ret != KNOT_EOK) {
conf_io_abort(false);
return ret;
}
return server_reload(s, NULL, false);
}
static int ctl_conf_abort(server_t *s, remote_cmdargs_t *a)
{
UNUSED(s);
UNUSED(a);
conf_io_abort(false);
return KNOT_EOK;
}
static int parse_conf_key(char *key, char **key0, char **id, char **key1)
{
// Check for the empty argument.
if (key == NULL) {
*key0 = NULL;
*key1 = NULL;
*id = NULL;
return KNOT_EOK;
}
// Get key0.
char *_key0 = key;
// Check for id.
char *_id = strchr(key, '[');
if (_id != NULL) {
// Separate key0 and id.
*_id++ = '\0';
// Check for id end.
char *id_end = _id;
while ((id_end = strchr(id_end, ']')) != NULL) {
// Check for escaped character.
if (*(id_end - 1) != '\\') {
break;
}
id_end++;
}
// Check for unclosed id.
if (id_end == NULL) {
return KNOT_EINVAL;
}
// Separate id and key1.
*id_end = '\0';
key = id_end + 1;
// Key1 or nothing must follow.
if (*key != '.' && *key != '\0') {
return KNOT_EINVAL;
}
}
// Check for key1.
char *_key1 = strchr(key, '.');
if (_key1 != NULL) {
// Separate key0/id and key1.
*_key1++ = '\0';
if (*_key1 == '\0') {
return KNOT_EINVAL;
}
}
*key0 = _key0;
*key1 = _key1;
*id = _id;
return KNOT_EOK;
}
static int ctl_conf_list(server_t *s, remote_cmdargs_t *a)
{
UNUSED(s);
if (a->argc > 1) {
return KNOT_EINVAL;
}
char *key = (a->argc == 1) ?
(char *)remote_get_txt(&a->arg[0], 0, NULL) : NULL;
// Split key path.
char *key0, *key1, *id;
int ret = parse_conf_key(key, &key0, &id, &key1);
if (ret != KNOT_EOK) {
free(key);
return ret;
}
if (key1 != NULL || id != NULL) {
free(key);
return KNOT_EINVAL;
}
conf_io_t io = {
.fcn = format_item,
.misc = a
};
// Get items.
ret = conf_io_list(key0, &io);
free(key);
return ret;
}
static int ctl_conf_diff(server_t *s, remote_cmdargs_t *a)
{
UNUSED(s);
if (a->argc > 1) {
return KNOT_EINVAL;
}
char *key = (a->argc == 1) ?
(char *)remote_get_txt(&a->arg[0], 0, NULL) : NULL;
// Split key path.
char *key0, *key1, *id;
int ret = parse_conf_key(key, &key0, &id, &key1);
if (ret != KNOT_EOK) {
free(key);
return ret;
}
conf_io_t io = {
.fcn = format_item,
.misc = a
};
// Get the difference.
ret = conf_io_diff(key0, key1, id, &io);
free(key);
return ret;
}
static int conf_read(server_t *s, remote_cmdargs_t *a, bool get_current)
{
UNUSED(s);
if (a->argc > 1) {
return KNOT_EINVAL;
}
char *key = (a->argc == 1) ?
(char *)remote_get_txt(&a->arg[0], 0, NULL) : NULL;
// Split key path.
char *key0, *key1, *id;
int ret = parse_conf_key(key, &key0, &id, &key1);
if (ret != KNOT_EOK) {
free(key);
return ret;
}
conf_io_t io = {
.fcn = format_item,
.misc = a
};
// Get item(s) value.
ret = conf_io_get(key0, key1, id, get_current, &io);
free(key);
return ret;
}
static int ctl_conf_read(server_t *s, remote_cmdargs_t *a)
{
return conf_read(s, a, true);
}
static int ctl_conf_get(server_t *s, remote_cmdargs_t *a)
{
return conf_read(s, a, false);
}
static int ctl_conf_set(server_t *s, remote_cmdargs_t *a)
{
UNUSED(s);
if (a->argc < 1 || a->argc > 255) {
return KNOT_EINVAL;
}
char *key = (char *)remote_get_txt(&a->arg[0], 0, NULL);
// Split key path.
char *key0, *key1, *id;
int ret = parse_conf_key(key, &key0, &id, &key1);
if (ret != KNOT_EOK) {
free(key);
return ret;
}
conf_io_t io = {
.fcn = format_item,
.misc = a
};
// Start child transaction.
ret = conf_io_begin(true);
if (ret != KNOT_EOK) {
free(key);
return ret;
}
// Add item with no data.
if (a->argc == 1) {
ret = conf_io_set(key0, key1, id, NULL, &io);
// Add item with specified data.
} else {
for (int i = 1; i < a->argc; i++) {
char *data = (char *)remote_get_txt(&a->arg[i], 0, NULL);
ret = conf_io_set(key0, key1, id, data, &io);
free(data);
if (ret != KNOT_EOK) {
break;
}
}
}
free(key);
// Finish child transaction.
if (ret == KNOT_EOK) {
return conf_io_commit(true);
} else {
conf_io_abort(true);
return ret;
}
}
static int ctl_conf_unset(server_t *s, remote_cmdargs_t *a)
{
UNUSED(s);
if (a->argc > 255) {
return KNOT_EINVAL;
}
char *key = (a->argc >= 1) ?
(char *)remote_get_txt(&a->arg[0], 0, NULL) : NULL;
// Split key path.
char *key0, *key1, *id;
int ret = parse_conf_key(key, &key0, &id, &key1);
if (ret != KNOT_EOK) {
free(key);
return ret;
}
// Start child transaction.
ret = conf_io_begin(true);
if (ret != KNOT_EOK) {
free(key);
return ret;
}
// Delete item with no data.
if (a->argc <= 1) {
ret = conf_io_unset(key0, key1, id, NULL);
// Delete specified data.
} else {
for (int i = 1; i < a->argc; i++) {
char *data = (char *)remote_get_txt(&a->arg[i], 0, NULL);
ret = conf_io_unset(key0, key1, id, data);
free(data);
if (ret != KNOT_EOK) {
break;
}
}
}
free(key);
// Finish child transaction.
if (ret == KNOT_EOK) {
return conf_io_commit(true);
} else {
conf_io_abort(true);
return ret;
}
}
/*! \brief Table of remote commands. */
const remote_cmd_t remote_cmd_tbl[] = {
{ KNOT_CTL_STATUS, ctl_status },
{ KNOT_CTL_STOP, ctl_stop },
{ KNOT_CTL_RELOAD, ctl_reload },
{ KNOT_CTL_ZONE_STATUS, ctl_zone_status },
{ KNOT_CTL_ZONE_RELOAD, ctl_zone_reload },
{ KNOT_CTL_ZONE_REFRESH, ctl_zone_refresh },
{ KNOT_CTL_ZONE_RETRANSFER, ctl_zone_retransfer },
{ KNOT_CTL_ZONE_FLUSH, ctl_zone_flush },
{ KNOT_CTL_ZONE_SIGN, ctl_zone_sign },
{ KNOT_CTL_CONF_LIST, ctl_conf_list },
{ KNOT_CTL_CONF_READ, ctl_conf_read },
{ KNOT_CTL_CONF_BEGIN, ctl_conf_begin },
{ KNOT_CTL_CONF_COMMIT, ctl_conf_commit },
{ KNOT_CTL_CONF_ABORT, ctl_conf_abort },
{ KNOT_CTL_CONF_DIFF, ctl_conf_diff },
{ KNOT_CTL_CONF_GET, ctl_conf_get },
{ KNOT_CTL_CONF_SET, ctl_conf_set },
{ KNOT_CTL_CONF_UNSET, ctl_conf_unset },
{ NULL }
};