/* Copyright (C) 2022 CZ.NIC, z.s.p.o. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include "knot/catalog/generate.h" #include "knot/common/log.h" #include "knot/updates/zone-update.h" #include "knot/zone/zonedb.h" #include "contrib/openbsd/siphash.h" #include "contrib/wire_ctx.h" static knot_dname_t *catalog_member_owner(const knot_dname_t *member, const knot_dname_t *catzone, time_t member_time) { SIPHASH_CTX hash; SIPHASH_KEY shkey = { 0 }; // only used for hashing -> zero key SipHash24_Init(&hash, &shkey); SipHash24_Update(&hash, member, knot_dname_size(member)); uint64_t u64time = htobe64(member_time); SipHash24_Update(&hash, &u64time, sizeof(u64time)); uint64_t hashres = SipHash24_End(&hash); char *hexhash = bin_to_hex((uint8_t *)&hashres, sizeof(hashres), false); if (hexhash == NULL) { return NULL; } size_t hexlen = strlen(hexhash); assert(hexlen == 16); size_t zoneslen = knot_dname_size((uint8_t *)CATALOG_ZONES_LABEL); assert(hexlen <= KNOT_DNAME_MAXLABELLEN && zoneslen <= KNOT_DNAME_MAXLABELLEN); size_t catzlen = knot_dname_size(catzone); size_t outlen = hexlen + zoneslen + catzlen; knot_dname_t *out; if (outlen > KNOT_DNAME_MAXLEN || (out = malloc(outlen)) == NULL) { free(hexhash); return NULL; } wire_ctx_t wire = wire_ctx_init(out, outlen); wire_ctx_write_u8(&wire, hexlen); wire_ctx_write(&wire, hexhash, hexlen); wire_ctx_write(&wire, CATALOG_ZONES_LABEL, zoneslen); wire_ctx_skip(&wire, -1); wire_ctx_write(&wire, catzone, catzlen); assert(wire.error == KNOT_EOK); free(hexhash); return out; } static bool same_group(zone_t *old_z, zone_t *new_z) { if (old_z->catalog_group == NULL || new_z->catalog_group == NULL) { return (old_z->catalog_group == new_z->catalog_group); } else { return (strcmp(old_z->catalog_group, new_z->catalog_group) == 0); } } void catalogs_generate(struct knot_zonedb *db_new, struct knot_zonedb *db_old) { // general comment: catz->contents!=NULL means incremental update of catalog if (db_old != NULL) { knot_zonedb_iter_t *it = knot_zonedb_iter_begin(db_old); while (!knot_zonedb_iter_finished(it)) { zone_t *zone = knot_zonedb_iter_val(it); knot_dname_t *cg = zone->catalog_gen; if (cg != NULL && knot_zonedb_find(db_new, zone->name) == NULL) { zone_t *catz = knot_zonedb_find(db_new, cg); if (catz != NULL && catz->contents != NULL) { assert(catz->cat_members != NULL); // if this failed to allocate, catz wasn't added to zonedb knot_dname_t *owner = catalog_member_owner(zone->name, cg, zone->timers.catalog_member); if (owner == NULL) { catz->cat_members->error = KNOT_ENOENT; knot_zonedb_iter_next(it); continue; } 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; } else { zone_events_schedule_now(catz, ZONE_EVENT_LOAD); } } } knot_zonedb_iter_next(it); } knot_zonedb_iter_free(it); } knot_zonedb_iter_t *it = knot_zonedb_iter_begin(db_new); while (!knot_zonedb_iter_finished(it)) { zone_t *zone = knot_zonedb_iter_val(it); knot_dname_t *cg = zone->catalog_gen; if (cg == NULL) { knot_zonedb_iter_next(it); continue; } zone_t *catz = knot_zonedb_find(db_new, cg); zone_t *old = knot_zonedb_find(db_old, zone->name); knot_dname_t *owner = catalog_member_owner(zone->name, cg, zone->timers.catalog_member); size_t cgroup_size = zone->catalog_group == NULL ? 0 : strlen(zone->catalog_group); if (catz == NULL) { log_zone_warning(zone->name, "member zone belongs to non-existing catalog zone"); } else if (catz->contents == NULL || old == NULL) { assert(catz->cat_members != NULL); if (owner == NULL) { catz->cat_members->error = KNOT_ENOENT; knot_zonedb_iter_next(it); continue; } int ret = catalog_update_add(catz->cat_members, zone->name, owner, cg, CAT_UPD_ADD, zone->catalog_group, cgroup_size, NULL); if (ret != KNOT_EOK) { catz->cat_members->error = ret; } else { zone_events_schedule_now(catz, ZONE_EVENT_LOAD); } } else if (!same_group(zone, old)) { int ret = catalog_update_add(catz->cat_members, zone->name, owner, cg, CAT_UPD_PROP, zone->catalog_group, cgroup_size, NULL); if (ret != KNOT_EOK) { catz->cat_members->error = ret; } else { zone_events_schedule_now(catz, ZONE_EVENT_LOAD); } } free(owner); knot_zonedb_iter_next(it); } knot_zonedb_iter_free(it); } static void set_rdata(knot_rrset_t *rrset, uint8_t *data, uint16_t len) { knot_rdata_init(rrset->rrs.rdata, len, data); rrset->rrs.size = knot_rdata_size(len); } #define def_txt_owner(ptr_owner) \ knot_dname_storage_t txt_owner = "\x05""group"; \ size_t _ptr_ow_len = knot_dname_size(ptr_owner); \ size_t _ptr_ow_ind = strlen((const char *)txt_owner); \ if (_ptr_ow_ind + _ptr_ow_len > sizeof(txt_owner)) { \ return KNOT_ERANGE; \ } \ memcpy(txt_owner + _ptr_ow_ind, (ptr_owner), _ptr_ow_len); static int add_group_txt(const knot_dname_t *ptr_owner, const char *group, zone_contents_t *conts, zone_update_t *up) { assert((conts == NULL) != (up == NULL)); size_t group_len; if (group == NULL || (group_len = strlen(group)) < 1) { return KNOT_EOK; } assert(group_len <= 255); def_txt_owner(ptr_owner); uint8_t data[256] = { group_len }; memcpy(data + 1, group, group_len); knot_rrset_t txt; knot_rrset_init(&txt, txt_owner, KNOT_RRTYPE_TXT, KNOT_CLASS_IN, 0); uint8_t txt_rd[256] = { 0 }; txt.rrs.rdata = (knot_rdata_t *)txt_rd; txt.rrs.count = 1; set_rdata(&txt, data, 1 + group_len ); int ret; if (conts != NULL) { zone_node_t *unused = NULL; ret = zone_contents_add_rr(conts, &txt, &unused); } else { ret = zone_update_add(up, &txt); } return ret; } static int rem_group_txt(const knot_dname_t *ptr_owner, zone_update_t *up) { def_txt_owner(ptr_owner); int ret = zone_update_remove_rrset(up, txt_owner, KNOT_RRTYPE_TXT); if (ret == KNOT_ENOENT || ret == KNOT_ENONODE) { ret = KNOT_EOK; } return ret; } struct zone_contents *catalog_update_to_zone(catalog_update_t *u, const knot_dname_t *catzone, uint32_t soa_serial) { if (u->error != KNOT_EOK) { return NULL; } zone_contents_t *c = zone_contents_new(catzone, true); if (c == NULL) { return c; } zone_node_t *unused = NULL; uint8_t invalid[9] = "\x07""invalid"; uint8_t version[9] = "\x07""version"; uint8_t cat_version[2] = "\x01" CATALOG_ZONE_VERSION; // prepare common rrset with one rdata item uint8_t rdata[256] = { 0 }; knot_rrset_t rrset; knot_rrset_init(&rrset, (knot_dname_t *)catzone, KNOT_RRTYPE_SOA, KNOT_CLASS_IN, 0); rrset.rrs.rdata = (knot_rdata_t *)rdata; rrset.rrs.count = 1; // set catalog zone's SOA uint8_t data[250]; assert(sizeof(knot_rdata_t) + sizeof(data) <= sizeof(rdata)); wire_ctx_t wire = wire_ctx_init(data, sizeof(data)); wire_ctx_write(&wire, invalid, sizeof(invalid)); wire_ctx_write(&wire, invalid, sizeof(invalid)); wire_ctx_write_u32(&wire, soa_serial); wire_ctx_write_u32(&wire, CATALOG_SOA_REFRESH); wire_ctx_write_u32(&wire, CATALOG_SOA_RETRY); wire_ctx_write_u32(&wire, CATALOG_SOA_EXPIRE); wire_ctx_write_u32(&wire, 0); set_rdata(&rrset, data, wire_ctx_offset(&wire)); if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK) { goto fail; } // set catalog zone's NS unused = NULL; rrset.type = KNOT_RRTYPE_NS; set_rdata(&rrset, invalid, sizeof(invalid)); if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK) { goto fail; } // set catalog zone's version TXT unused = NULL; knot_dname_storage_t owner; if (knot_dname_store(owner, version) == 0 || catalog_dname_append(owner, catzone) == 0) { goto fail; } rrset.owner = owner; rrset.type = KNOT_RRTYPE_TXT; set_rdata(&rrset, cat_version, sizeof(cat_version)); if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK) { goto fail; } // insert member zone PTR records rrset.type = KNOT_RRTYPE_PTR; catalog_it_t *it = catalog_it_begin(u); while (!catalog_it_finished(it)) { catalog_upd_val_t *val = catalog_it_val(it); if (val->add_owner == NULL) { continue; } rrset.owner = val->add_owner; set_rdata(&rrset, val->member, knot_dname_size(val->member)); unused = NULL; if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK || add_group_txt(val->add_owner, val->new_group, c, NULL) != KNOT_EOK) { catalog_it_free(it); goto fail; } catalog_it_next(it); } catalog_it_free(it); return c; fail: zone_contents_deep_free(c); return NULL; } int catalog_update_to_update(catalog_update_t *u, struct zone_update *zu) { knot_rrset_t ptr; knot_rrset_init(&ptr, NULL, KNOT_RRTYPE_PTR, KNOT_CLASS_IN, 0); uint8_t tmp[KNOT_DNAME_MAXLEN + sizeof(knot_rdata_t)]; ptr.rrs.rdata = (knot_rdata_t *)tmp; ptr.rrs.count = 1; int ret = u->error; catalog_it_t *it = catalog_it_begin(u); while (!catalog_it_finished(it) && ret == KNOT_EOK) { catalog_upd_val_t *val = catalog_it_val(it); if (val->type == CAT_UPD_INVALID) { catalog_it_next(it); continue; } if (val->type == CAT_UPD_PROP && knot_dname_is_equal(zu->zone->name, val->add_catz)) { ret = rem_group_txt(val->add_owner, zu); if (ret == KNOT_EOK) { ret = add_group_txt(val->add_owner, val->new_group, NULL, zu); } catalog_it_next(it); continue; } set_rdata(&ptr, val->member, knot_dname_size(val->member)); if (val->type == CAT_UPD_REM && knot_dname_is_equal(zu->zone->name, val->rem_catz)) { ptr.owner = val->rem_owner; ret = zone_update_remove(zu, &ptr); if (ret == KNOT_EOK) { ret = rem_group_txt(val->rem_owner, zu); } } if (val->type == CAT_UPD_ADD && knot_dname_is_equal(zu->zone->name, val->add_catz)) { ptr.owner = val->add_owner; ret = zone_update_add(zu, &ptr); if (ret == KNOT_EOK) { ret = add_group_txt(val->add_owner, val->new_group, NULL, zu); } } catalog_it_next(it); } catalog_it_free(it); return ret; }