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