1 /* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2
3 This program is free software: you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation, either version 3 of the License, or
6 (at your option) any later version.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program. If not, see <https://www.gnu.org/licenses/>.
15 */
16
17 #include <string.h>
18
19 #include "knot/catalog/generate.h"
20 #include "knot/common/log.h"
21 #include "knot/updates/zone-update.h"
22 #include "knot/zone/zonedb.h"
23 #include "contrib/openbsd/siphash.h"
24 #include "contrib/wire_ctx.h"
25
catalog_member_owner(const knot_dname_t * member,const knot_dname_t * catzone,time_t member_time)26 static knot_dname_t *catalog_member_owner(const knot_dname_t *member,
27 const knot_dname_t *catzone,
28 time_t member_time)
29 {
30 SIPHASH_CTX hash;
31 SIPHASH_KEY shkey = { 0 }; // only used for hashing -> zero key
32 SipHash24_Init(&hash, &shkey);
33 SipHash24_Update(&hash, member, knot_dname_size(member));
34 uint64_t u64time = htobe64(member_time);
35 SipHash24_Update(&hash, &u64time, sizeof(u64time));
36 uint64_t hashres = SipHash24_End(&hash);
37
38 char *hexhash = bin_to_hex((uint8_t *)&hashres, sizeof(hashres));
39 if (hexhash == NULL) {
40 return NULL;
41 }
42 size_t hexlen = strlen(hexhash);
43 assert(hexlen == 16);
44 size_t zoneslen = knot_dname_size((uint8_t *)CATALOG_ZONES_LABEL);
45 assert(hexlen <= KNOT_DNAME_MAXLABELLEN && zoneslen <= KNOT_DNAME_MAXLABELLEN);
46 size_t catzlen = knot_dname_size(catzone);
47
48 size_t outlen = hexlen + zoneslen + catzlen;
49 knot_dname_t *out;
50 if (outlen > KNOT_DNAME_MAXLEN || (out = malloc(outlen)) == NULL) {
51 free(hexhash);
52 return NULL;
53 }
54
55 wire_ctx_t wire = wire_ctx_init(out, outlen);
56 wire_ctx_write_u8(&wire, hexlen);
57 wire_ctx_write(&wire, hexhash, hexlen);
58 wire_ctx_write(&wire, CATALOG_ZONES_LABEL, zoneslen);
59 wire_ctx_skip(&wire, -1);
60 wire_ctx_write(&wire, catzone, catzlen);
61 assert(wire.error == KNOT_EOK);
62
63 free(hexhash);
64 return out;
65 }
66
same_group(zone_t * old_z,zone_t * new_z)67 static bool same_group(zone_t *old_z, zone_t *new_z)
68 {
69 if (old_z->catalog_group == NULL || new_z->catalog_group == NULL) {
70 return (old_z->catalog_group == new_z->catalog_group);
71 } else {
72 return (strcmp(old_z->catalog_group, new_z->catalog_group) == 0);
73 }
74 }
75
catalogs_generate(struct knot_zonedb * db_new,struct knot_zonedb * db_old)76 void catalogs_generate(struct knot_zonedb *db_new, struct knot_zonedb *db_old)
77 {
78 // general comment: catz->contents!=NULL means incremental update of catalog
79
80 if (db_old != NULL) {
81 knot_zonedb_iter_t *it = knot_zonedb_iter_begin(db_old);
82 while (!knot_zonedb_iter_finished(it)) {
83 zone_t *zone = knot_zonedb_iter_val(it);
84 knot_dname_t *cg = zone->catalog_gen;
85 if (cg != NULL && knot_zonedb_find(db_new, zone->name) == NULL) {
86 zone_t *catz = knot_zonedb_find(db_new, cg);
87 if (catz != NULL && catz->contents != NULL) {
88 assert(catz->cat_members != NULL); // if this failed to allocate, catz wasn't added to zonedb
89 knot_dname_t *owner = catalog_member_owner(zone->name, cg, zone->timers.catalog_member);
90 if (owner == NULL) {
91 catz->cat_members->error = KNOT_ENOENT;
92 knot_zonedb_iter_next(it);
93 continue;
94 }
95 int ret = catalog_update_add(catz->cat_members, zone->name, owner,
96 cg, CAT_UPD_REM, NULL, 0, NULL);
97 free(owner);
98 if (ret != KNOT_EOK) {
99 catz->cat_members->error = ret;
100 } else {
101 zone_events_schedule_now(catz, ZONE_EVENT_LOAD);
102 }
103 }
104 }
105 knot_zonedb_iter_next(it);
106 }
107 knot_zonedb_iter_free(it);
108 }
109
110 knot_zonedb_iter_t *it = knot_zonedb_iter_begin(db_new);
111 while (!knot_zonedb_iter_finished(it)) {
112 zone_t *zone = knot_zonedb_iter_val(it);
113 knot_dname_t *cg = zone->catalog_gen;
114 if (cg == NULL) {
115 knot_zonedb_iter_next(it);
116 continue;
117 }
118 zone_t *catz = knot_zonedb_find(db_new, cg);
119 zone_t *old = knot_zonedb_find(db_old, zone->name);
120 knot_dname_t *owner = catalog_member_owner(zone->name, cg, zone->timers.catalog_member);
121 size_t cgroup_size = zone->catalog_group == NULL ? 0 : strlen(zone->catalog_group);
122 if (catz == NULL) {
123 log_zone_warning(zone->name, "member zone belongs to non-existing catalog zone");
124 } else if (catz->contents == NULL || old == NULL) {
125 assert(catz->cat_members != NULL);
126 if (owner == NULL) {
127 catz->cat_members->error = KNOT_ENOENT;
128 knot_zonedb_iter_next(it);
129 continue;
130 }
131 int ret = catalog_update_add(catz->cat_members, zone->name, owner,
132 cg, CAT_UPD_ADD, zone->catalog_group,
133 cgroup_size, NULL);
134 if (ret != KNOT_EOK) {
135 catz->cat_members->error = ret;
136 } else {
137 zone_events_schedule_now(catz, ZONE_EVENT_LOAD);
138 }
139 } else if (!same_group(zone, old)) {
140 int ret = catalog_update_add(catz->cat_members, zone->name, owner,
141 cg, CAT_UPD_PROP, zone->catalog_group,
142 cgroup_size, NULL);
143 if (ret != KNOT_EOK) {
144 catz->cat_members->error = ret;
145 } else {
146 zone_events_schedule_now(catz, ZONE_EVENT_LOAD);
147 }
148 }
149 free(owner);
150 knot_zonedb_iter_next(it);
151 }
152 knot_zonedb_iter_free(it);
153 }
154
set_rdata(knot_rrset_t * rrset,uint8_t * data,uint16_t len)155 static void set_rdata(knot_rrset_t *rrset, uint8_t *data, uint16_t len)
156 {
157 knot_rdata_init(rrset->rrs.rdata, len, data);
158 rrset->rrs.size = knot_rdata_size(len);
159 }
160
161 #define def_txt_owner(ptr_owner) \
162 knot_dname_storage_t txt_owner = "\x05""group"; \
163 size_t _ptr_ow_len = knot_dname_size(ptr_owner); \
164 size_t _ptr_ow_ind = strlen((const char *)txt_owner); \
165 if (_ptr_ow_ind + _ptr_ow_len > sizeof(txt_owner)) { \
166 return KNOT_ERANGE; \
167 } \
168 memcpy(txt_owner + _ptr_ow_ind, (ptr_owner), _ptr_ow_len);
169
add_group_txt(const knot_dname_t * ptr_owner,const char * group,zone_contents_t * conts,zone_update_t * up)170 static int add_group_txt(const knot_dname_t *ptr_owner, const char *group,
171 zone_contents_t *conts, zone_update_t *up)
172 {
173 assert((conts == NULL) != (up == NULL));
174 size_t group_len;
175 if (group == NULL || (group_len = strlen(group)) < 1) {
176 return KNOT_EOK;
177 }
178 assert(group_len <= 255);
179
180 def_txt_owner(ptr_owner);
181
182 uint8_t data[256] = { group_len };
183 memcpy(data + 1, group, group_len);
184
185 knot_rrset_t txt;
186 knot_rrset_init(&txt, txt_owner, KNOT_RRTYPE_TXT, KNOT_CLASS_IN, 0);
187 uint8_t txt_rd[256] = { 0 };
188 txt.rrs.rdata = (knot_rdata_t *)txt_rd;
189 txt.rrs.count = 1;
190 set_rdata(&txt, data, 1 + group_len );
191
192 int ret;
193 if (conts != NULL) {
194 zone_node_t *unused = NULL;
195 ret = zone_contents_add_rr(conts, &txt, &unused);
196 } else {
197 ret = zone_update_add(up, &txt);
198 }
199
200 return ret;
201 }
202
rem_group_txt(const knot_dname_t * ptr_owner,zone_update_t * up)203 static int rem_group_txt(const knot_dname_t *ptr_owner, zone_update_t *up)
204 {
205 def_txt_owner(ptr_owner);
206
207 int ret = zone_update_remove_rrset(up, txt_owner, KNOT_RRTYPE_TXT);
208 if (ret == KNOT_ENOENT || ret == KNOT_ENONODE) {
209 ret = KNOT_EOK;
210 }
211
212 return ret;
213 }
214
catalog_update_to_zone(catalog_update_t * u,const knot_dname_t * catzone,uint32_t soa_serial)215 struct zone_contents *catalog_update_to_zone(catalog_update_t *u, const knot_dname_t *catzone,
216 uint32_t soa_serial)
217 {
218 if (u->error != KNOT_EOK) {
219 return NULL;
220 }
221 zone_contents_t *c = zone_contents_new(catzone, true);
222 if (c == NULL) {
223 return c;
224 }
225
226 zone_node_t *unused = NULL;
227 uint8_t invalid[9] = "\x07""invalid";
228 uint8_t version[9] = "\x07""version";
229 uint8_t cat_version[2] = "\x01" CATALOG_ZONE_VERSION;
230
231 // prepare common rrset with one rdata item
232 uint8_t rdata[256] = { 0 };
233 knot_rrset_t rrset;
234 knot_rrset_init(&rrset, (knot_dname_t *)catzone, KNOT_RRTYPE_SOA, KNOT_CLASS_IN, 0);
235 rrset.rrs.rdata = (knot_rdata_t *)rdata;
236 rrset.rrs.count = 1;
237
238 // set catalog zone's SOA
239 uint8_t data[250];
240 assert(sizeof(knot_rdata_t) + sizeof(data) <= sizeof(rdata));
241 wire_ctx_t wire = wire_ctx_init(data, sizeof(data));
242 wire_ctx_write(&wire, invalid, sizeof(invalid));
243 wire_ctx_write(&wire, invalid, sizeof(invalid));
244 wire_ctx_write_u32(&wire, soa_serial);
245 wire_ctx_write_u32(&wire, CATALOG_SOA_REFRESH);
246 wire_ctx_write_u32(&wire, CATALOG_SOA_RETRY);
247 wire_ctx_write_u32(&wire, CATALOG_SOA_EXPIRE);
248 wire_ctx_write_u32(&wire, 0);
249 set_rdata(&rrset, data, wire_ctx_offset(&wire));
250 if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK) {
251 goto fail;
252 }
253
254 // set catalog zone's NS
255 unused = NULL;
256 rrset.type = KNOT_RRTYPE_NS;
257 set_rdata(&rrset, invalid, sizeof(invalid));
258 if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK) {
259 goto fail;
260 }
261
262 // set catalog zone's version TXT
263 unused = NULL;
264 knot_dname_storage_t owner;
265 if (knot_dname_store(owner, version) == 0 || catalog_dname_append(owner, catzone) == 0) {
266 goto fail;
267 }
268 rrset.owner = owner;
269 rrset.type = KNOT_RRTYPE_TXT;
270 set_rdata(&rrset, cat_version, sizeof(cat_version));
271 if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK) {
272 goto fail;
273 }
274
275 // insert member zone PTR records
276 rrset.type = KNOT_RRTYPE_PTR;
277 catalog_it_t *it = catalog_it_begin(u);
278 while (!catalog_it_finished(it)) {
279 catalog_upd_val_t *val = catalog_it_val(it);
280 if (val->add_owner == NULL) {
281 continue;
282 }
283 rrset.owner = val->add_owner;
284 set_rdata(&rrset, val->member, knot_dname_size(val->member));
285 unused = NULL;
286 if (zone_contents_add_rr(c, &rrset, &unused) != KNOT_EOK ||
287 add_group_txt(val->add_owner, val->new_group, c, NULL) != KNOT_EOK) {
288 catalog_it_free(it);
289 goto fail;
290 }
291 catalog_it_next(it);
292 }
293 catalog_it_free(it);
294
295 return c;
296 fail:
297 zone_contents_deep_free(c);
298 return NULL;
299 }
300
catalog_update_to_update(catalog_update_t * u,struct zone_update * zu)301 int catalog_update_to_update(catalog_update_t *u, struct zone_update *zu)
302 {
303 knot_rrset_t ptr;
304 knot_rrset_init(&ptr, NULL, KNOT_RRTYPE_PTR, KNOT_CLASS_IN, 0);
305 uint8_t tmp[KNOT_DNAME_MAXLEN + sizeof(knot_rdata_t)];
306 ptr.rrs.rdata = (knot_rdata_t *)tmp;
307 ptr.rrs.count = 1;
308
309 int ret = u->error;
310 catalog_it_t *it = catalog_it_begin(u);
311 while (!catalog_it_finished(it) && ret == KNOT_EOK) {
312 catalog_upd_val_t *val = catalog_it_val(it);
313 if (val->type == CAT_UPD_INVALID) {
314 catalog_it_next(it);
315 continue;
316 }
317
318 if (val->type == CAT_UPD_PROP && knot_dname_is_equal(zu->zone->name, val->add_catz)) {
319 ret = rem_group_txt(val->add_owner, zu);
320 if (ret == KNOT_EOK) {
321 ret = add_group_txt(val->add_owner, val->new_group, NULL, zu);
322 }
323 catalog_it_next(it);
324 continue;
325 }
326
327 set_rdata(&ptr, val->member, knot_dname_size(val->member));
328 if (val->type == CAT_UPD_REM && knot_dname_is_equal(zu->zone->name, val->rem_catz)) {
329 ptr.owner = val->rem_owner;
330 ret = zone_update_remove(zu, &ptr);
331 if (ret == KNOT_EOK) {
332 ret = rem_group_txt(val->rem_owner, zu);
333 }
334 }
335 if (val->type == CAT_UPD_ADD && knot_dname_is_equal(zu->zone->name, val->add_catz)) {
336 ptr.owner = val->add_owner;
337 ret = zone_update_add(zu, &ptr);
338 if (ret == KNOT_EOK) {
339 ret = add_group_txt(val->add_owner, val->new_group, NULL, zu);
340 }
341 }
342 catalog_it_next(it);
343 }
344 catalog_it_free(it);
345 return ret;
346 }
347