]> git.ipfire.org Git - thirdparty/knot-dns.git/commitdiff
catalog: implemented interpreting member groups
authorLibor Peltan <libor.peltan@nic.cz>
Fri, 26 Mar 2021 17:32:39 +0000 (18:32 +0100)
committerDaniel Salzman <daniel.salzman@nic.cz>
Sat, 1 May 2021 08:07:49 +0000 (10:07 +0200)
24 files changed:
doc/configuration.rst
doc/man/knot.conf.5in
doc/reference.rst
src/contrib/string.c
src/contrib/string.h
src/knot/catalog/catalog_db.c
src/knot/catalog/catalog_db.h
src/knot/catalog/catalog_update.c
src/knot/catalog/catalog_update.h
src/knot/catalog/generate.c
src/knot/catalog/interpret.c
src/knot/catalog/interpret.h
src/knot/conf/conf.c
src/knot/conf/schema.c
src/knot/updates/zone-update.c
src/knot/zone/zonedb-load.c
src/utils/kcatalogprint/main.c
tests-extra/tests/catalog/groups/data/catalog2.zone [new file with mode: 0644]
tests-extra/tests/catalog/groups/data/cataloged1.zone [new file with mode: 0644]
tests-extra/tests/catalog/groups/data/cataloged2.zone [new file with mode: 0644]
tests-extra/tests/catalog/groups/data/cataloged3.zone [new file with mode: 0644]
tests-extra/tests/catalog/groups/data/cataloged4.zone [new file with mode: 0644]
tests-extra/tests/catalog/groups/test.py [new file with mode: 0644]
tests-extra/tools/dnstest/server.py

index b8454b03abe2a4e7562d613f3c655b6e146b5a16..ea7732e248b21314a7804ee14922b46ee94fa0c6 100644 (file)
@@ -586,6 +586,8 @@ of the DNS tree, but it contains information about the set of member zones and
 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
@@ -606,7 +608,7 @@ Upon catalog zone (re)load or change, all the PTR records in the format
 ``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:
 
@@ -614,13 +616,18 @@ 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
@@ -631,6 +638,13 @@ A catalog zone may be modified using any standard means (e.g. AXFR/IXFR, DDNS,
 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.
index 94a4e295213c6094a21737bbf7cc1a45beceafbd..c5bfbfdb43bc9afea29269fd79121b7969179864 100644 (file)
@@ -1509,7 +1509,7 @@ zone:
     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
@@ -1832,7 +1832,10 @@ assigned member zones.
 \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
index 87bca1d56abe3fb7d1ce94be7c818bc52407dfb8..f3a5bd730b9de0b7a8461ef2c335240664c01d49 100644 (file)
@@ -1646,7 +1646,7 @@ Definition of zones served by the server.
      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 ...
 
@@ -1997,7 +1997,10 @@ Trigger zone catalog feature. Possible values:
 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*.
index 4146c9655a1a78436070386568058ef601b7d92b..01866860ff580931bb1b2f1304093fa0448f7df9 100644 (file)
@@ -1,4 +1,4 @@
-/*  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
@@ -46,6 +46,15 @@ uint8_t *memdup(const uint8_t *data, size_t data_size)
        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;
index 65c2eb63568e621c6876e33041bca6115a55093c..6e524e56b98e0ad1203f9918e495b03fc8d536b8 100644 (file)
@@ -1,4 +1,4 @@
-/*  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.
  *
index 736c3e75db21be0884655420378e800153b39afa..debc3e5670f981d32aa567f6e87c103f6bce6c3b 100644 (file)
@@ -14,6 +14,7 @@
     along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
+#include <stdio.h>
 #include <string.h>
 #include <urcu.h>
 
@@ -165,7 +166,8 @@ int catalog_deinit(catalog_t *cat)
 }
 
 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;
@@ -176,7 +178,7 @@ int catalog_add(catalog_t *cat, const knot_dname_t *member,
        }
        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);
@@ -195,16 +197,19 @@ int catalog_del(catalog_t *cat, const knot_dname_t *member)
        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) {
@@ -215,7 +220,7 @@ static int find_threadsafe(catalog_t *cat, const knot_dname_t *member,
 
        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);
@@ -223,17 +228,18 @@ static int find_threadsafe(catalog_t *cat, const knot_dname_t *member,
 }
 
 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);
 }
@@ -242,8 +248,9 @@ bool catalog_contains_exact(catalog_t *cat, const knot_dname_t *member,
                             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;
@@ -262,12 +269,13 @@ static int catalog_apply_cb(MDB_val *key, MDB_val *val, void *ctx)
        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,
@@ -287,7 +295,8 @@ static bool same_catalog(knot_lmdb_txn_t *txn, const knot_dname_t *catalog)
                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);
 }
 
index a9fcef8fc2cc90fa0c9692c2ca5215a456b4c151..5f68088145ee93eee9201640c0e0fabf888f9dbf 100644 (file)
@@ -22,6 +22,7 @@
 #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;
@@ -112,11 +113,13 @@ int catalog_deinit(catalog_t *cat);
  * \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.
@@ -136,12 +139,13 @@ int catalog_del(catalog_t *cat, const knot_dname_t *member);
  * \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.
@@ -155,7 +159,7 @@ bool catalog_contains_exact(catalog_t *cat, const knot_dname_t *member,
                             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.
  *
index 094751600811792e08a0efbaf6e3dcd4a10283c5..f801b731b89a9abee15702e355a1282ff336b733 100644 (file)
@@ -50,6 +50,7 @@ static void catalog_upd_val_free(catalog_upd_val_t *val)
 {
        free(val->add_owner);
        free(val->rem_owner);
+       free(val->new_group);
        free(val);
 }
 
@@ -85,7 +86,7 @@ void catalog_update_free(catalog_update_t *u)
 }
 
 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);
@@ -102,14 +103,14 @@ static catalog_upd_val_t *upd_val_new(const knot_dname_t *member, int bail,
                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;
@@ -163,11 +164,30 @@ static int upd_val_update(catalog_upd_val_t *val, int bail,
        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
@@ -187,13 +207,24 @@ int catalog_update_add(catalog_update_t *u, const knot_dname_t *member,
        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);
@@ -215,7 +246,7 @@ catalog_upd_val_t *catalog_update_get(catalog_update_t *u, const knot_dname_t *m
 
 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))) {
@@ -236,12 +267,14 @@ static bool check_member(catalog_upd_val_t *val, conf_t *conf, catalog_t *cat)
 }
 
 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;
 }
@@ -279,7 +312,9 @@ int catalog_update_commit(catalog_update_t *u, catalog_t *cat)
                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);
@@ -307,12 +342,14 @@ typedef struct {
 } 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;
        }
index 4fd7675f886bf0014ce10dac12082266528cc71c..949fbd730774519a2e1f9f6aa58b3db110352fca 100644 (file)
@@ -26,6 +26,7 @@ typedef enum {
        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;
 
@@ -37,6 +38,8 @@ typedef struct catalog_upd_val {
        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 {
@@ -77,14 +80,17 @@ void catalog_update_free(catalog_update_t *u);
  * \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.
index 05230e3ef6e3c853258eae1983526e2ec098efa6..d85d6064c9eb1e4fe90379c1fabae4608f925032 100644 (file)
@@ -83,7 +83,8 @@ void catalogs_generate(struct knot_zonedb *db_new, struct knot_zonedb *db_old)
                                                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;
@@ -112,7 +113,8 @@ void catalogs_generate(struct knot_zonedb *db_new, struct knot_zonedb *db_old)
                                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;
index f5c3b371e01b4f9b5f74d1845ca42780a2515394..81a7a5f0a14c4e587d12f7b6c607d688ead3cbc0 100644 (file)
  */
 
 #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;
@@ -50,32 +51,91 @@ static bool check_zone_version(const zone_contents_t *zone)
        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)) {
@@ -92,7 +152,8 @@ int catalog_update_from_zone(catalog_update_t *u, struct zone_contents *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);
index 851c6cf85e5b076f7878406b9a60334eb99a974a..b1dbfb72eca40a56ab18b387269a1b056e5f81bb 100644 (file)
@@ -23,14 +23,16 @@ struct zone_contents;
 /*!
  * \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);
index 85ebf146e978d4adec888f74c6d7deb03b025580..dabb26af6cdb290c72eb6abaca4d1359f26e7458 100644 (file)
@@ -220,18 +220,28 @@ conf_val_t conf_zone_get_txn(
        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;
                }
index df0bf3f4354b993fe1eb8b4eb6b9610cac372c2a..839e74b71984fa956f2e87b653847cc80cf51e06 100644 (file)
@@ -374,7 +374,7 @@ static const yp_item_t desc_policy[] = {
        { 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 } }, \
index e1b6a2c5f4017f07834990a571a6ad56ad6417c7..a63768ba00e8a72675bd400676188eaacc231fd9 100644 (file)
@@ -731,11 +731,11 @@ static int update_catalog(conf_t *conf, zone_update_t *update)
        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 {
@@ -744,7 +744,7 @@ static int update_catalog(conf_t *conf, zone_update_t *update)
                                             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);
                }
        }
index 9bb2845bce900981603865b1a06c60927c623101..7b514314ecbd862ba7181ed064867432edb4a3b7 100644 (file)
@@ -310,10 +310,11 @@ typedef struct {
 } 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);
index 367157797c5afdbf07657fdda57c9fcf48ce907f..1ce797a1cf784f9f6d159029bf1d0c509635fce6 100644 (file)
@@ -41,12 +41,12 @@ static void print_dname(const knot_dname_t *d)
 }
 
 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;
 }
@@ -55,7 +55,7 @@ static void catalog_print(catalog_t *cat)
 {
        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);
diff --git a/tests-extra/tests/catalog/groups/data/catalog2.zone b/tests-extra/tests/catalog/groups/data/catalog2.zone
new file mode 100644 (file)
index 0000000..c62cb8a
--- /dev/null
@@ -0,0 +1,13 @@
+$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
diff --git a/tests-extra/tests/catalog/groups/data/cataloged1.zone b/tests-extra/tests/catalog/groups/data/cataloged1.zone
new file mode 100644 (file)
index 0000000..67031c7
--- /dev/null
@@ -0,0 +1,6 @@
+$ORIGIN cataloged1.
+$TTL 1200
+
+@ SOA ns admin 10001 25 25 80 600
+  NS ns
+ns AAAA ::0
diff --git a/tests-extra/tests/catalog/groups/data/cataloged2.zone b/tests-extra/tests/catalog/groups/data/cataloged2.zone
new file mode 100644 (file)
index 0000000..29aea7f
--- /dev/null
@@ -0,0 +1,6 @@
+$ORIGIN cataloged2.
+$TTL 1200
+
+@ SOA ns admin 1 25 25 80 600
+  NS ns
+ns AAAA ::0
diff --git a/tests-extra/tests/catalog/groups/data/cataloged3.zone b/tests-extra/tests/catalog/groups/data/cataloged3.zone
new file mode 100644 (file)
index 0000000..d981ff2
--- /dev/null
@@ -0,0 +1,6 @@
+$ORIGIN cataloged3.
+$TTL 1200
+
+@ SOA ns admin 1 25 25 80 600
+  NS ns
+ns AAAA ::0
diff --git a/tests-extra/tests/catalog/groups/data/cataloged4.zone b/tests-extra/tests/catalog/groups/data/cataloged4.zone
new file mode 100644 (file)
index 0000000..e4e6869
--- /dev/null
@@ -0,0 +1,6 @@
+$ORIGIN cataloged4.
+$TTL 1200
+
+@ SOA ns admin 10001 25 25 80 600
+  NS ns
+ns AAAA ::0
diff --git a/tests-extra/tests/catalog/groups/test.py b/tests-extra/tests/catalog/groups/test.py
new file mode 100644 (file)
index 0000000..764e1d3
--- /dev/null
@@ -0,0 +1,106 @@
+#!/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()
index b4e4162afb79f2c4307cf8741c70978f2d3c4389..2132616a5b5fd33f1842f7215a71e7b29fb31d80 100644 (file)
@@ -1402,7 +1402,7 @@ class Knot(Server):
             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)
@@ -1442,6 +1442,17 @@ class Knot(Server):
             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")
@@ -1499,7 +1510,7 @@ class Knot(Server):
 
             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")