is transferable to secondary servers using common AXFR/IXFR techniques.
*Catalog-member zone* (or just *member zone*) is a zone based on
information from the catalog zone and not from configuration file/database.
+*Member properties* are some additional information related to each member zone,
+also distributed by the catalog zone.
A catalog zone is handled almost in the same way as a regular zone:
It can be configured using all the standard options (but for example
``unique-id.zones.catalog. 0 IN PTR member.com.`` (but not ``too.deep.zones.catalog.``!)
are processed and member zones created, with zone names taken from the
PTR records' RData, and zone settings taken from the configuration
-template specified by :ref:`zone_catalog-template`.
+templates specified by :ref:`zone_catalog-template`.
The owner names of the PTR records shall follow this scheme:
<unique-id>.zones.<catalog-zone>.
-where the mentioned group of labels shall match:
+where the mentioned labels shall match:
- *<unique-id>* — Single label that is recommended to be unique among member zones.
- ``zones`` — Required label.
- *<catalog-zone>* — Name of the catalog zone.
-All records other than PTR are ignored. They remain in the catalog
+Additionally, records in the format
+``group.unique-id.zones.catalog. 0 IN TXT "conf-template"``
+are processed as a definition of the member's *group* property. The
+``unique-id`` must match the one of the PTR record defining the member.
+
+All other records and other member properties are ignored. They remain in the catalog
zone, however, and might be for example transferred to a secondary server,
which may interpret catalog zones differently. SOA still needs to be present in
the catalog zone and its serial handled appropriately. An apex NS record should be
zone file reload). In the case of incremental change, only affected
member zones are reloaded.
+The catalog zone must have at least one :ref:`zone_catalog-template`
+configured. The configuration for any defined member zone is taken from its
+*group* property value, which should match some catalog-template name.
+If the *group* property is not defined for a member, is empty, or doesn't match
+any of defined catalog-template names, the first catalog-template
+(in the order from configuration) is used.
+
Any de-cataloged member zone is purged immediately, including its
zone file, journal, timers, and DNSSEC keys. The zone file is not
deleted if :ref:`zone_zonefile-sync` is set to *-1* for member zones.
refresh\-min\-interval: TIME
refresh\-max\-interval: TIME
catalog\-role: none | interpret
- catalog\-template: template_id
+ catalog\-template: template_id ...
catalog\-zone: DNAME
module: STR/STR ...
.ft P
\fIDefault:\fP none
.SS catalog\-template
.sp
-For the catalog\-member zones, the specified configuration template will be applied.
+For the catalog member zones, the specified configuration template will be applied.
+.sp
+Multiple catalog templates may be defined. The first one is used unless the member zone
+has the \fIgroup\fP property defined, matching another catalog template.
.sp
\fBNOTE:\fP
.INDENT 0.0
refresh-min-interval: TIME
refresh-max-interval: TIME
catalog-role: none | interpret
- catalog-template: template_id
+ catalog-template: template_id ...
catalog-zone: DNAME
module: STR/STR ...
catalog-template
----------------
-For the catalog-member zones, the specified configuration template will be applied.
+For the catalog member zones, the specified configuration template will be applied.
+
+Multiple catalog templates may be defined. The first one is used unless the member zone
+has the *group* property defined, matching another catalog template.
.. NOTE::
This option must be set if and only if :ref:`zone_catalog-role` is *interpret*.
-/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2021 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
return memcpy(result, data, data_size);
}
+int strmemcmp(const char *str, const uint8_t *mem, size_t mem_size)
+{
+ if (mem_size == 0) {
+ return 1;
+ }
+ size_t cmp_len = strnlen(str, mem_size - 1) + 1;
+ return memcmp(str, mem, cmp_len);
+}
+
char *sprintf_alloc(const char *fmt, ...)
{
char *strp = NULL;
-/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2021 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
*/
uint8_t *memdup(const uint8_t *data, size_t data_size);
+/*!
+ * \brief Compare a zero-terminated string with fixed-size memory.
+ */
+int strmemcmp(const char *str, const uint8_t *mem, size_t mem_size);
+
/*!
* \brief Format string and take care of allocating memory.
*
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+#include <stdio.h>
#include <string.h>
#include <urcu.h>
}
int catalog_add(catalog_t *cat, const knot_dname_t *member,
- const knot_dname_t *owner, const knot_dname_t *catzone)
+ const knot_dname_t *owner, const knot_dname_t *catzone,
+ const char *group)
{
if (cat->rw_txn == NULL) {
return KNOT_EINVAL;
}
assert(bail >= 0 && bail < 256);
MDB_val key = knot_lmdb_make_key("BN", 0, member); // 0 for future purposes
- MDB_val val = knot_lmdb_make_key("BBN", 0, bail, owner);
+ MDB_val val = knot_lmdb_make_key("BBNS", 0, bail, owner, group);
knot_lmdb_insert(cat->rw_txn, &key, &val);
free(key.mv_data);
return cat->rw_txn->ret;
}
-static void unmake_val(MDB_val *val, const knot_dname_t **owner, const knot_dname_t **catz)
+static void unmake_val(MDB_val *val, const knot_dname_t **owner,
+ const knot_dname_t **catz, const char **group)
{
uint8_t zero, shift;
- knot_lmdb_unmake_key(val->mv_data, val->mv_size, "BBN", &zero, &shift, owner);
+ *group = ""; // backward compatibility with Knot 3.0
+ knot_lmdb_unmake_key(val->mv_data, val->mv_size, "BBNS", &zero, &shift,
+ owner, group);
*catz = *owner + shift;
}
static int find_threadsafe(catalog_t *cat, const knot_dname_t *member,
const knot_dname_t **owner, const knot_dname_t **catz,
- void **tofree)
+ const char **group, void **tofree)
{
*tofree = NULL;
if (cat->ro_txn == NULL) {
int ret = knot_lmdb_find_threadsafe(cat->ro_txn, &key, &val, KNOT_LMDB_EXACT);
if (ret == KNOT_EOK) {
- unmake_val(&val, owner, catz);
+ unmake_val(&val, owner, catz, group);
*tofree = val.mv_data;
}
free(key.mv_data);
}
int catalog_get_catz(catalog_t *cat, const knot_dname_t *member,
- const knot_dname_t **catz, void **tofree)
+ const knot_dname_t **catz, const char **group, void **tofree)
{
const knot_dname_t *unused;
- return find_threadsafe(cat, member, &unused, catz, tofree);
+ return find_threadsafe(cat, member, &unused, catz, group, tofree);
}
bool catalog_has_member(catalog_t *cat, const knot_dname_t *member)
{
const knot_dname_t *catz;
+ const char *group;
void *tofree = NULL;
- int ret = catalog_get_catz(cat, member, &catz, &tofree);
+ int ret = catalog_get_catz(cat, member, &catz, &group, &tofree);
free(tofree);
return (ret == KNOT_EOK);
}
const knot_dname_t *owner, const knot_dname_t *catz)
{
const knot_dname_t *found_owner, *found_catz;
+ const char *found_group;
void *tofree = NULL;
- int ret = find_threadsafe(cat, member, &found_owner, &found_catz, &tofree);
+ int ret = find_threadsafe(cat, member, &found_owner, &found_catz, &found_group, &tofree);
if (ret == KNOT_EOK && (!knot_dname_is_equal(owner, found_owner) ||
!knot_dname_is_equal(catz, found_catz))) {
ret = KNOT_ENOENT;
catalog_apply_ctx_t *iter_ctx = ctx;
uint8_t zero;
const knot_dname_t *mem = NULL, *ow = NULL, *cz = NULL;
+ const char *gr = NULL;
knot_lmdb_unmake_key(key->mv_data, key->mv_size, "BN", &zero, &mem);
- unmake_val(val, &ow, &cz);
+ unmake_val(val, &ow, &cz, &gr);
if (mem == NULL || ow == NULL || cz == NULL) {
return KNOT_EMALF;
}
- return iter_ctx->cb(mem, ow, cz, iter_ctx->ctx);
+ return iter_ctx->cb(mem, ow, cz, gr, iter_ctx->ctx);
}
int catalog_apply(catalog_t *cat, const knot_dname_t *for_member,
return true;
}
const knot_dname_t *txn_cat = NULL, *unused;
- unmake_val(&txn->cur_val, &unused, &txn_cat);
+ const char *grunused;
+ unmake_val(&txn->cur_val, &unused, &txn_cat, &grunused);
return knot_dname_is_equal(txn_cat, catalog);
}
#define CATALOG_VERSION "1.0"
#define CATALOG_ZONE_VERSION "2" // must be just one char long
#define CATALOG_ZONES_LABEL "\x05""zones"
+#define CATALOG_GROUP_LABEL "\x05""group"
typedef struct catalog {
knot_lmdb_db_t db;
* \param member Member zone name.
* \param owner Owner of the PTR record in catalog zone, respective to the member zone.
* \param catzone Name of the catalog zone whose it's the member.
+ * \param group Configuration group of the member.
*
* \return KNOT_E*
*/
int catalog_add(catalog_t *cat, const knot_dname_t *member,
- const knot_dname_t *owner, const knot_dname_t *catzone);
+ const knot_dname_t *owner, const knot_dname_t *catzone,
+ const char *group);
/*!
* \brief Delete a member zone from the catalog database.
* \param cat Catalog datatase.
* \param member Member to search for.
* \param catz Out: name of catalog zone it resides in.
+ * \param group Out: configuration group the member resides in.
* \param tofree Out: a pointer that has to be freed later.
*
* \return KNOT_E*
*/
int catalog_get_catz(catalog_t *cat, const knot_dname_t *member,
- const knot_dname_t **catz, void **tofree);
+ const knot_dname_t **catz, const char **group, void **tofree);
/*!
* \brief Check if this member exists in any catalog zone.
const knot_dname_t *owner, const knot_dname_t *catz);
typedef int (*catalog_apply_cb_t)(const knot_dname_t *member, const knot_dname_t *owner,
- const knot_dname_t *catz, void *ctx);
+ const knot_dname_t *catz, const char *group, void *ctx);
/*!
* \brief Iterate through catalog database, applying callback.
*
{
free(val->add_owner);
free(val->rem_owner);
+ free(val->new_group);
free(val);
}
}
static catalog_upd_val_t *upd_val_new(const knot_dname_t *member, int bail,
- const knot_dname_t *owner, bool rem)
+ const knot_dname_t *owner, catalog_upd_type_t type)
{
size_t member_size = knot_dname_size(member);
size_t owner_size = knot_dname_size(owner);
free(val);
return NULL;
}
- if (rem) {
- val->type = CAT_UPD_REM;
+ val->type = type;
+ val->new_group = NULL;
+ if (type == CAT_UPD_REM) {
val->add_owner = NULL;
val->add_catz = NULL;
val->rem_owner = owner_cpy;
val->rem_catz = owner_cpy + bail;
} else {
- val->type = CAT_UPD_ADD;
val->add_owner = owner_cpy;
val->add_catz = owner_cpy + bail;
val->rem_owner = NULL;
return KNOT_EOK;
}
+static int upd_val_set_prop(catalog_upd_val_t *val, const knot_dname_t *check_ow,
+ const knot_dname_t *check_catz, const char *group,
+ size_t group_len)
+{
+ if (check_catz != NULL) {
+ if (val->type == CAT_UPD_REM ||
+ !knot_dname_is_equal(check_ow, val->add_owner) || // TODO consider removing those checks. Are they worth the performance?
+ !knot_dname_is_equal(check_catz, val->add_catz)) {
+ return KNOT_EOK; // ignore invalid property set
+ }
+ }
+ if (val->new_group != NULL) {
+ free(val->new_group);
+ }
+ val->new_group = strndup(group, group_len);
+ return val->new_group == NULL ? KNOT_ENOMEM : KNOT_EOK;
+}
+
int catalog_update_add(catalog_update_t *u, const knot_dname_t *member,
const knot_dname_t *owner, const knot_dname_t *catzone,
- bool remove, catalog_t *check_rem)
+ catalog_upd_type_t type, const char *group,
+ size_t group_len, catalog_t *check_rem)
{
- if (remove && check_rem != NULL &&
+ if ((type == CAT_UPD_REM || type == CAT_UPD_PROP) && check_rem != NULL &&
!catalog_contains_exact(check_rem, member, owner, catzone)) {
return KNOT_EOK;
// we need to perform this check immediately because
if (found != NULL) {
catalog_upd_val_t *val = *found;
assert(knot_dname_is_equal(val->member, member));
- return upd_val_update(val, bail, owner, remove);
+ if (type == CAT_UPD_PROP) {
+ return upd_val_set_prop(val, owner, catzone, group, group_len);
+ } else {
+ return upd_val_update(val, bail, owner, type == CAT_UPD_REM);
+ }
}
- catalog_upd_val_t *val = upd_val_new(member, bail, owner, remove);
+ catalog_upd_val_t *val = upd_val_new(member, bail, owner, type);
if (val == NULL) {
return KNOT_ENOMEM;
}
+ if (type == CAT_UPD_PROP) {
+ int ret = upd_val_set_prop(val, NULL, NULL, group, group_len);
+ if (ret != KNOT_EOK) {
+ catalog_upd_val_free(val);
+ return ret;
+ }
+ }
trie_val_t *added = trie_get_ins(u->upd, lf + 1, lf[0]);
if (added == NULL) {
catalog_upd_val_free(val);
static bool check_member(catalog_upd_val_t *val, conf_t *conf, catalog_t *cat)
{
- if (val->type == CAT_UPD_REM || val->type == CAT_UPD_INVALID) {
+ if (val->type == CAT_UPD_REM || val->type == CAT_UPD_INVALID || val->type == CAT_UPD_PROP) {
return true;
}
if (!conf_rawid_exists(conf, C_ZONE, val->add_catz, knot_dname_size(val->add_catz))) {
}
static int rem_conf_conflict(const knot_dname_t *mem, const knot_dname_t *ow,
- const knot_dname_t *cz, void *ctx)
+ const knot_dname_t *cz, const char *gr, void *ctx)
{
+ UNUSED(gr);
+
conf_t *conf = ctx;
if (conf_rawid_exists(conf, C_ZONE, mem, knot_dname_size(mem))) {
- return catalog_update_add(ctx, mem, ow, cz, true, NULL);
+ return catalog_update_add(ctx, mem, ow, cz, CAT_UPD_REM, NULL, 0, NULL);
}
return KNOT_EOK;
}
case CAT_UPD_ADD:
case CAT_UPD_MINOR: // catalog_add will simply update/overwrite existing data
case CAT_UPD_UNIQ:
- ret = catalog_add(cat, val->member, val->add_owner, val->add_catz);
+ case CAT_UPD_PROP:
+ ret = catalog_add(cat, val->member, val->add_owner, val->add_catz,
+ val->new_group == NULL ? "" : val->new_group);
break;
case CAT_UPD_REM:
ret = catalog_del(cat, val->member);
} del_all_ctx_t;
static int del_all_cb(const knot_dname_t *member, const knot_dname_t *owner,
- const knot_dname_t *catz, void *dactx)
+ const knot_dname_t *catz, const char *group, void *dactx)
{
+ UNUSED(group);
+
del_all_ctx_t *ctx = dactx;
if (knot_dname_is_equal(catz, ctx->zone)) {
// TODO possible speedup by indexing which member zones belong to a catalog zone
- return catalog_update_add(ctx->u, member, owner, catz, true, NULL);
+ return catalog_update_add(ctx->u, member, owner, catz, CAT_UPD_REM, NULL, 0, NULL);
} else {
return KNOT_EOK;
}
CAT_UPD_REM, // member removal
CAT_UPD_MINOR, // owner or catzone change, uniqID preserved
CAT_UPD_UNIQ, // uniqID change
+ CAT_UPD_PROP, // ONLY change of properties of existing member
CAT_UPD_MAX, // number of options in ths enum
} catalog_upd_type_t;
knot_dname_t *rem_catz; // catalog zone the member being removed from
knot_dname_t *add_owner; // owner of PTR record being added
knot_dname_t *add_catz; // catalog zone the member being added to
+
+ char *new_group; // the desired configuration group for the member
} catalog_upd_val_t;
typedef struct {
* \param member Member zone name to be added.
* \param owner Owner of respective PTR record.
* \param catzone Catalog zone holding the member.
- * \param remove Add a removal of such record.
+ * \param type CAT_UPD_REM, CAT_UPD_ADD, CAT_UPD_PROP.
+ * \param group Optional: member group property value.
+ * \param group_len Length of 'group' string (if not NULL).
* \param check_rem Check catalog DB for existing record to be removed.
*
* \return KNOT_E*
*/
int catalog_update_add(catalog_update_t *u, const knot_dname_t *member,
const knot_dname_t *owner, const knot_dname_t *catzone,
- bool remove, catalog_t *check_rem);
+ catalog_upd_type_t type, const char *group,
+ size_t group_len, catalog_t *check_rem);
/*!
* \brief Read catalog update record for given member zone.
knot_zonedb_iter_next(it);
continue;
}
- int ret = catalog_update_add(catz->cat_members, zone->name, owner, cg, true, NULL);
+ int ret = catalog_update_add(catz->cat_members, zone->name, owner,
+ cg, CAT_UPD_REM, NULL, 0, NULL);
free(owner);
if (ret != KNOT_EOK) {
catz->cat_members->error = ret;
knot_zonedb_iter_next(it);
continue;
}
- int ret = catalog_update_add(catz->cat_members, zone->name, owner, cg, false, NULL);
+ int ret = catalog_update_add(catz->cat_members, zone->name, owner,
+ cg, CAT_UPD_ADD, NULL, 0, NULL);
free(owner);
if (ret != KNOT_EOK) {
catz->cat_members->error = ret;
*/
#include <pthread.h>
+#include <stdio.h>
#include "knot/catalog/interpret.h"
#include "knot/zone/contents.h"
typedef struct {
catalog_update_t *u;
- const knot_dname_t *apex;
+ const zone_contents_t *complete_conts;
int apex_labels;
bool remove;
catalog_t *check;
return false;
}
-static int cat_update_add_node(zone_node_t *node, void *data)
+static const knot_dname_t *property_get_member(const zone_node_t *prop_node,
+ const zone_contents_t *complete_conts,
+ const knot_dname_t **owner)
{
- cat_upd_ctx_t *ctx = data;
- int labels_diff = knot_dname_labels(node->owner, NULL) - ctx->apex_labels
- - 1 /* "zones" label */ - 1 /* unique-N label */;
- assert(labels_diff >= 0);
- if (labels_diff > 0) {
- return KNOT_EOK;
+ assert(prop_node != NULL);
+ knot_rdataset_t *ptr = node_rdataset(prop_node->parent, KNOT_RRTYPE_PTR);
+ if (ptr == NULL) {
+ // fallback: search in provided complete zone contents
+ const knot_dname_t *memb_name = knot_wire_next_label(prop_node->owner, NULL);
+ const zone_node_t *memb_node = zone_contents_find_node(complete_conts, memb_name);
+ ptr = node_rdataset(memb_node, KNOT_RRTYPE_PTR);
+ if (memb_node != NULL) {
+ *owner = memb_node->owner;
+ }
+ } else {
+ *owner = knot_wire_next_label(prop_node->owner, NULL);
}
-
- const knot_rdataset_t *ptr = node_rdataset(node, KNOT_RRTYPE_PTR);
- if (ptr == NULL || ptr->count == 0) {
- return KNOT_EOK;
+ if (ptr == NULL || ptr->count != 1) {
+ return NULL;
}
+ return knot_ptr_name(ptr->rdata);
+}
+
+static int cat_update_add_memb(const knot_dname_t *owner, const knot_rdataset_t *ptr,
+ cat_upd_ctx_t *ctx)
+{
knot_rdata_t *rdata = ptr->rdata;
int ret = KNOT_EOK;
for (int i = 0; ret == KNOT_EOK && i < ptr->count; i++) {
const knot_dname_t *member = knot_ptr_name(rdata);
- ret = catalog_update_add(ctx->u, member, node->owner, ctx->apex,
- ctx->remove, ctx->check);
+ ret = catalog_update_add(ctx->u, member, owner, ctx->complete_conts->apex->owner,
+ ctx->remove ? CAT_UPD_REM : CAT_UPD_ADD,
+ NULL, 0, ctx->check);
rdata = knot_rdataset_next(rdata);
}
return ret;
}
+static int cat_update_add_grp(const knot_dname_t *member, const knot_dname_t *owner,
+ const knot_rdataset_t *txt, cat_upd_ctx_t *ctx)
+{
+ if (member == NULL) {
+ return KNOT_EOK; // just ignore property w/o member
+ }
+
+ const char *newgr = "";
+ size_t grlen = 0;
+ if (!ctx->remove) {
+ assert(txt->count == 1);
+ // TXT rdata consists of one or more 1-byte prefixed strings.
+ if (txt->rdata->len != txt->rdata->data[0] + 1) {
+ return KNOT_EMALF;
+ }
+ newgr = (const char *)txt->rdata->data + 1;
+ grlen = txt->rdata->data[0];
+ }
+
+ return catalog_update_add(ctx->u, member, owner, ctx->complete_conts->apex->owner,
+ CAT_UPD_PROP, newgr, grlen, ctx->check);
+}
+
+static int cat_update_add_node(zone_node_t *node, void *data)
+{
+ cat_upd_ctx_t *ctx = data;
+ int labels_diff = knot_dname_labels(node->owner, NULL) - ctx->apex_labels
+ - 1 /* "zones" label */ - 1 /* unique-N label */;
+ assert(labels_diff >= 0);
+
+ const knot_rdataset_t *ptr = node_rdataset(node, KNOT_RRTYPE_PTR);
+ const knot_rdataset_t *txt = node_rdataset(node, KNOT_RRTYPE_TXT);
+
+ if (labels_diff == 0 && ptr != NULL && ptr->count > 0) {
+ return cat_update_add_memb(node->owner, ptr, ctx);
+ }
+ if (labels_diff == 1 && txt != NULL && txt->count == 1 &&
+ node->owner[0] == CATALOG_GROUP_LABEL[0] &&
+ memcmp(node->owner, CATALOG_GROUP_LABEL, CATALOG_GROUP_LABEL[0] + 1) == 0) {
+ const knot_dname_t *own, *memb = property_get_member(node, ctx->complete_conts, &own);
+ return cat_update_add_grp(memb, own, txt, ctx);
+ }
+ return KNOT_EOK;
+}
+
int catalog_update_from_zone(catalog_update_t *u, struct zone_contents *zone,
+ const struct zone_contents *complete_contents,
bool remove, bool check_ver, catalog_t *check)
{
if (check_ver && !check_zone_version(zone)) {
return KNOT_EOK;
}
- cat_upd_ctx_t ctx = { u, zone->apex->owner, knot_dname_labels(zone->apex->owner, NULL), remove, check };
+ cat_upd_ctx_t ctx = { u, complete_contents, knot_dname_labels(zone->apex->owner, NULL),
+ remove, check };
pthread_mutex_lock(&u->mutex);
int ret = zone_tree_sub_apply(zone->nodes, sub, true, cat_update_add_node, &ctx);
pthread_mutex_unlock(&u->mutex);
/*!
* \brief Iterate over PTR records in given zone contents and add members to catalog update.
*
- * \param u Catalog update to be updated.
- * \param zone Zone contents to be searched for member PTR records.
- * \param remove Add removals of found member zones.
- * \param check_ver Do check catalog zone version record first.
- * \param check Optional: existing catalog database to be checked for existence
- * of such record (useful for removals).
+ * \param u Catalog update to be updated.
+ * \param zone Zone contents to be searched for member PTR records.
+ * \param complete_contents Complete zone contents (zone might be from a changeset).
+ * \param remove Add removals of found member zones.
+ * \param check_ver Do check catalog zone version record first.
+ * \param check Optional: existing catalog database to be checked for existence
+ * of such record (useful for removals).
*
* \return KNOT_E*
*/
int catalog_update_from_zone(catalog_update_t *u, struct zone_contents *zone,
+ const struct zone_contents *complete_contents,
bool remove, bool check_ver, catalog_t *check);
if (conf->catalog != NULL) {
void *tofree = NULL;
const knot_dname_t *catalog;
- int ret = catalog_get_catz(conf->catalog, dname, &catalog, &tofree);
+ const char *group;
+ int ret = catalog_get_catz(conf->catalog, dname, &catalog, &group, &tofree);
if (ret == KNOT_EOK) {
conf_db_get(conf, txn, C_ZONE, C_CATALOG_TPL, catalog,
knot_dname_size(catalog), &val);
- free(tofree);
if (val.code != KNOT_EOK) {
CONF_LOG_ZONE(LOG_ERR, catalog,
"catalog zone has no catalog template (%s)",
knot_strerror(val.code));
+ free(tofree);
return val;
}
conf_val(&val);
+ while (val.code == KNOT_EOK) {
+ if (strmemcmp(group, val.data, val.len) == 0) {
+ break;
+ }
+ conf_val_next(&val);
+ }
+ conf_val(&val); // Use first value if no match.
+ free(tofree);
+
conf_db_get(conf, txn, C_TPL, key1_name, val.data, val.len, &val);
goto got_template;
}
{ C_REFRESH_MIN_INTERVAL,YP_TINT, YP_VINT = { 2, UINT32_MAX, 2, YP_STIME } }, \
{ C_REFRESH_MAX_INTERVAL,YP_TINT, YP_VINT = { 2, UINT32_MAX, UINT32_MAX, YP_STIME } }, \
{ C_CATALOG_ROLE, YP_TOPT, YP_VOPT = { catalog_roles, CATALOG_ROLE_NONE }, FLAGS }, \
- { C_CATALOG_TPL, YP_TREF, YP_VREF = { C_TPL }, FLAGS, { check_ref } }, \
+ { C_CATALOG_TPL, YP_TREF, YP_VREF = { C_TPL }, YP_FMULTI | FLAGS, { check_ref } }, \
{ C_CATALOG_ZONE, YP_TDNAME,YP_VNONE, FLAGS | CONF_IO_FRLD_ZONES }, \
{ C_MODULE, YP_TDATA, YP_VDATA = { 0, NULL, mod_id_to_bin, mod_id_to_txt }, \
YP_FMULTI | FLAGS, { check_modref } }, \
int ret = KNOT_EOK;
if ((update->flags & UPDATE_INCREMENTAL)) {
ret = catalog_update_from_zone(update->zone->catalog_upd,
- update->change.remove,
+ update->change.remove, update->new_cont,
true, false, update->zone->catalog);
if (ret == KNOT_EOK) {
ret = catalog_update_from_zone(update->zone->catalog_upd,
- update->change.add,
+ update->change.add, update->new_cont,
false, false, NULL);
}
} else {
update->zone->name);
if (ret == KNOT_EOK) {
ret = catalog_update_from_zone(update->zone->catalog_upd,
- update->zone->contents,
+ update->zone->contents, update->new_cont, // TODO why update->zone->contents ??
false, true, NULL);
}
}
} reuse_cold_zone_ctx_t;
static int reuse_cold_zone_cb(const knot_dname_t *member, const knot_dname_t *owner,
- const knot_dname_t *catz, void *ctx)
+ const knot_dname_t *catz, const char *group, void *ctx)
{
UNUSED(owner);
UNUSED(catz);
+ UNUSED(group);
reuse_cold_zone_ctx_t *rcz = ctx;
zone_t *zone = reuse_cold_zone(member, rcz->server, rcz->conf);
}
static int catalog_print_cb(const knot_dname_t *mem, const knot_dname_t *ow,
- const knot_dname_t *cz, void *ctx)
+ const knot_dname_t *cz, const char *group, void *ctx)
{
print_dname(mem);
print_dname(ow);
print_dname(cz);
- printf("\n");
+ printf("%s\n", group);
(*(ssize_t *)ctx)++;
return KNOT_EOK;
}
{
ssize_t total = 0;
- printf(";; <catalog zone> <record owner> <record zone>\n");
+ printf(";; <member zone> <record owner> <catalog zone> <group>\n");
if (cat != NULL) {
int ret = catalog_open(cat);
--- /dev/null
+$ORIGIN catalog2.
+$TTL 0
+
+@ SOA ns admin 1 25 25 80 600
+ NS ns
+ns AAAA ::0
+version TXT "2"
+foo.zones PTR cataloged1.
+group.foo.zones TXT catalog-signed
+bar.zones PTR cataloged2.
+group.bar.zones TXT catalog-unsigned
+baz.zones PTR cataloged3.
+;left w/o group to see default
--- /dev/null
+$ORIGIN cataloged1.
+$TTL 1200
+
+@ SOA ns admin 10001 25 25 80 600
+ NS ns
+ns AAAA ::0
--- /dev/null
+$ORIGIN cataloged2.
+$TTL 1200
+
+@ SOA ns admin 1 25 25 80 600
+ NS ns
+ns AAAA ::0
--- /dev/null
+$ORIGIN cataloged3.
+$TTL 1200
+
+@ SOA ns admin 1 25 25 80 600
+ NS ns
+ns AAAA ::0
--- /dev/null
+$ORIGIN cataloged4.
+$TTL 1200
+
+@ SOA ns admin 10001 25 25 80 600
+ NS ns
+ns AAAA ::0
--- /dev/null
+#!/usr/bin/env python3
+
+'''Test of consuming catalog with configuration groups.'''
+
+from dnstest.test import Test
+from dnstest.utils import set_err, detail_log
+from dnstest.module import ModOnlineSign
+import dnstest.params
+
+import glob
+import shutil
+from subprocess import DEVNULL, PIPE, Popen
+import subprocess
+import random
+
+def check_keys(server, zone_name, expect_keys):
+ cmd = Popen([dnstest.params.keymgr_bin, "-d", server.dir + "/keys", zone_name, "list"], \
+ stdout=PIPE, stderr=PIPE, universal_newlines=True)
+ (stdout, stderr) = cmd.communicate()
+ lines = len(stdout.splitlines())
+ if lines != expect_keys:
+ set_err("CHECK # of KEYS (%d != %d)" % (lines, expect_keys))
+
+t = Test(stress=False)
+
+master = t.server("knot")
+
+# Zone setup
+zone = t.zone("catalog2.", storage=".")
+
+t.link(zone, master)
+
+master.zones["catalog2."].catalog = True
+
+for zf in glob.glob(t.data_dir + "/*.zone"):
+ shutil.copy(zf, master.dir + "/master")
+
+t.start()
+
+# Basic: catalogedX are assigned to correct groups
+t.sleep(5)
+resp = master.dig("cataloged1.", "SOA", dnssec=True)
+resp.check(rcode="NOERROR")
+resp.check_count(1, "RRSIG")
+resp = master.dig("cataloged2.", "SOA", dnssec=True)
+resp.check(rcode="NOERROR")
+resp.check_count(0, "RRSIG")
+resp = master.dig("cataloged3.", "SOA", dnssec=True)
+resp.check(rcode="NOERROR")
+resp.check_count(0, "RRSIG")
+
+# Addition of member with group
+up = master.update(zone)
+up.add("added.zones.catalog2.", 0, "PTR", "cataloged4.")
+up.add("group.added.zones.catalog2.", 0, "TXT", "catalog-signed")
+up.send("NOERROR")
+t.sleep(4)
+resp = master.dig("cataloged4.", "SOA", dnssec=True)
+resp.check(rcode="NOERROR")
+resp.check_count(1, "RRSIG")
+
+# Move member between groups
+up = master.update(zone)
+up.delete("group.bar.zones.catalog2.", "TXT")
+up.add("group.bar.zones.catalog2.", 0, "TXT", "catalog-signed")
+up.send("NOERROR")
+t.sleep(4)
+resp = master.dig("cataloged2.", "SOA", dnssec=True)
+resp.check(rcode="NOERROR")
+resp.check_count(1, "RRSIG")
+
+# Add member to a group
+up = master.update(zone)
+up.add("group.baz.zones.catalog2.", 0, "TXT", "catalog-signed")
+up.send("NOERROR")
+t.sleep(4)
+resp = master.dig("cataloged3.", "SOA", dnssec=True)
+resp.check(rcode="NOERROR")
+resp.check_count(1, "RRSIG")
+
+# Remove member from any group
+up = master.update(zone)
+up.delete("group.foo.zones.catalog2.", "TXT")
+up.send("NOERROR")
+t.sleep(4)
+# check that DNSSEC no longer works
+with open(master.dir + "/master/cataloged1.zone", "a") as c1zf:
+ c1zf.write("added A 1.2.3.4")
+master.ctl("zone-reload cataloged1.")
+t.sleep(4)
+resp = master.dig("added.cataloged1.", "A", dnssec=True)
+resp.check(rcode="NOERROR")
+resp.check_count(1, "A")
+resp.check_count(0, "RRSIG")
+
+# Remove member while adding group
+up = master.update(zone)
+up.delete("foo.zones.catalog2.", "PTR")
+up.add("group.foo.zones.catalog2.", 0, "TXT", "catalog-signed")
+up.send("NOERROR")
+t.sleep(4)
+resp = master.dig("cataloged1.", "SOA", dnssec=True)
+resp.check(rcode="REFUSED")
+check_keys(master, "cataloged1.", 0)
+
+t.end()
if z.catalog:
have_catalog = z
if have_catalog is not None:
- s.id_item("id", "catemplate")
+ s.id_item("id", "catalog-default")
s.item_str("file", self.dir + "/master/%s.zone")
s.item_str("zonefile-load", "difference")
s.item_str("journal-content", z.journal_content)
acl += "acl_local, acl_test"
s.item("acl", "[%s]" % acl)
+ s.id_item("id", "catalog-signed")
+ s.item_str("file", self.dir + "/master/%s.zone")
+ s.item_str("journal-content", z.journal_content)
+ s.item_str("dnssec-signing", "on")
+ s.item("acl", "[%s]" % acl)
+
+ s.id_item("id", "catalog-unsigned")
+ s.item_str("file", self.dir + "/master/%s.zone")
+ s.item_str("journal-content", z.journal_content)
+ s.item("acl", "[%s]" % acl)
+
s.end()
s.begin("zone")
if z.catalog:
s.item_str("catalog-role", "interpret")
- s.item_str("catalog-template", "catemplate")
+ s.item("catalog-template", "[ catalog-default, catalog-signed, catalog-unsigned ]")
if z.dnssec.validate:
s.item_str("dnssec-validation", "on")