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 "knot/catalog/interpret.h"
18 #include "knot/common/log.h"
19 #include "knot/dnssec/zone-events.h"
20 #include "knot/updates/zone-update.h"
21 #include "knot/zone/adds_tree.h"
22 #include "knot/zone/adjust.h"
23 #include "knot/zone/digest.h"
24 #include "knot/zone/serial.h"
25 #include "knot/zone/zone-diff.h"
26 #include "knot/zone/zonefile.h"
27 #include "contrib/trim.h"
28 #include "contrib/ucw/lists.h"
29 
30 #include <signal.h>
31 #include <unistd.h>
32 #include <urcu.h>
33 
34 // Call mem_trim() whenever accumuled size of updated zones reaches this size.
35 #define UPDATE_MEMTRIM_AT (10 * 1024 * 1024)
36 
init_incremental(zone_update_t * update,zone_t * zone,zone_contents_t * old_contents)37 static int init_incremental(zone_update_t *update, zone_t *zone, zone_contents_t *old_contents)
38 {
39 	if (old_contents == NULL) {
40 		return KNOT_EINVAL;
41 	}
42 
43 	int ret = changeset_init(&update->change, zone->name);
44 	if (ret != KNOT_EOK) {
45 		return ret;
46 	}
47 
48 	if (update->flags & UPDATE_HYBRID) {
49 		update->new_cont = old_contents;
50 	} else {
51 		ret = zone_contents_cow(old_contents, &update->new_cont);
52 		if (ret != KNOT_EOK) {
53 			changeset_clear(&update->change);
54 			return ret;
55 		}
56 	}
57 
58 	uint32_t apply_flags = (update->flags & UPDATE_STRICT) ? APPLY_STRICT : 0;
59 	apply_flags |= (update->flags & UPDATE_HYBRID) ? APPLY_UNIFY_FULL : 0;
60 	ret = apply_init_ctx(update->a_ctx, update->new_cont, apply_flags);
61 	if (ret != KNOT_EOK) {
62 		changeset_clear(&update->change);
63 		return ret;
64 	}
65 
66 	/* Copy base SOA RR. */
67 	update->change.soa_from =
68 		node_create_rrset(old_contents->apex, KNOT_RRTYPE_SOA);
69 	if (update->change.soa_from == NULL) {
70 		zone_contents_free(update->new_cont);
71 		changeset_clear(&update->change);
72 		return KNOT_ENOMEM;
73 	}
74 
75 	return KNOT_EOK;
76 }
77 
init_full(zone_update_t * update,zone_t * zone)78 static int init_full(zone_update_t *update, zone_t *zone)
79 {
80 	update->new_cont = zone_contents_new(zone->name, true);
81 	if (update->new_cont == NULL) {
82 		return KNOT_ENOMEM;
83 	}
84 
85 	int ret = apply_init_ctx(update->a_ctx, update->new_cont, APPLY_UNIFY_FULL);
86 	if (ret != KNOT_EOK) {
87 		zone_contents_free(update->new_cont);
88 		return ret;
89 	}
90 
91 	return KNOT_EOK;
92 }
93 
replace_soa(zone_contents_t * contents,const knot_rrset_t * rr)94 static int replace_soa(zone_contents_t *contents, const knot_rrset_t *rr)
95 {
96 	/* SOA possible only within apex. */
97 	if (!knot_dname_is_equal(rr->owner, contents->apex->owner)) {
98 		return KNOT_EDENIED;
99 	}
100 
101 	knot_rrset_t old_soa = node_rrset(contents->apex, KNOT_RRTYPE_SOA);
102 	zone_node_t *n = contents->apex;
103 	int ret = zone_contents_remove_rr(contents, &old_soa, &n);
104 	if (ret != KNOT_EOK && ret != KNOT_EINVAL) {
105 		return ret;
106 	}
107 
108 	ret = zone_contents_add_rr(contents, rr, &n);
109 	if (ret == KNOT_ETTL) {
110 		return KNOT_EOK;
111 	}
112 
113 	return ret;
114 }
115 
init_base(zone_update_t * update,zone_t * zone,zone_contents_t * old_contents,zone_update_flags_t flags)116 static int init_base(zone_update_t *update, zone_t *zone, zone_contents_t *old_contents,
117                      zone_update_flags_t flags)
118 {
119 	if (update == NULL || zone == NULL) {
120 		return KNOT_EINVAL;
121 	}
122 
123 	memset(update, 0, sizeof(*update));
124 	update->zone = zone;
125 	update->flags = flags;
126 
127 	update->a_ctx = calloc(1, sizeof(*update->a_ctx));
128 	if (update->a_ctx == NULL) {
129 		return KNOT_ENOMEM;
130 	}
131 
132 	if (zone->control_update != NULL && zone->control_update != update) {
133 		log_zone_warning(zone->name, "blocked zone update due to open control transaction");
134 	}
135 
136 	knot_sem_wait(&zone->cow_lock);
137 	update->a_ctx->cow_mutex = &zone->cow_lock;
138 
139 	if (old_contents == NULL) {
140 		old_contents = zone->contents; // don't obtain this pointer before any other zone_update ceased to exist!
141 	}
142 
143 	int ret = KNOT_EINVAL;
144 	if (flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
145 		ret = init_incremental(update, zone, old_contents);
146 	} else if (flags & UPDATE_FULL) {
147 		ret = init_full(update, zone);
148 	}
149 	if (ret != KNOT_EOK) {
150 		knot_sem_post(&zone->cow_lock);
151 		free(update->a_ctx);
152 	}
153 
154 	return ret;
155 }
156 
157 /* ------------------------------- API -------------------------------------- */
158 
zone_update_init(zone_update_t * update,zone_t * zone,zone_update_flags_t flags)159 int zone_update_init(zone_update_t *update, zone_t *zone, zone_update_flags_t flags)
160 {
161 	return init_base(update, zone, NULL, flags);
162 }
163 
zone_update_from_differences(zone_update_t * update,zone_t * zone,zone_contents_t * old_cont,zone_contents_t * new_cont,zone_update_flags_t flags,bool ignore_dnssec)164 int zone_update_from_differences(zone_update_t *update, zone_t *zone, zone_contents_t *old_cont,
165 				 zone_contents_t *new_cont, zone_update_flags_t flags, bool ignore_dnssec)
166 {
167 	if (update == NULL || zone == NULL || new_cont == NULL ||
168 	    !(flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) || (flags & UPDATE_FULL)) {
169 		return KNOT_EINVAL;
170 	}
171 
172 	changeset_t diff;
173 	int ret = changeset_init(&diff, zone->name);
174 	if (ret != KNOT_EOK) {
175 		return ret;
176 	}
177 
178 	ret = init_base(update, zone, old_cont, flags);
179 	if (ret != KNOT_EOK) {
180 		changeset_clear(&diff);
181 		return ret;
182 	}
183 
184 	if (old_cont == NULL) {
185 		old_cont = zone->contents;
186 	}
187 
188 	ret = zone_contents_diff(old_cont, new_cont, &diff, ignore_dnssec);
189 	switch (ret) {
190 	case KNOT_ENODIFF:
191 	case KNOT_ESEMCHECK:
192 	case KNOT_EOK:
193 		break;
194 	case KNOT_ERANGE:
195 		additionals_tree_free(update->new_cont->adds_tree);
196 		update->new_cont->adds_tree = NULL;
197 		update->new_cont = NULL; // Prevent deep_free as old_cont will be used later.
198 		update->a_ctx->flags &= ~APPLY_UNIFY_FULL; // Prevent Unify of old_cont that will be used later.
199 		// FALLTHROUGH
200 	default:
201 		changeset_clear(&diff);
202 		zone_update_clear(update);
203 		return ret;
204 	}
205 
206 	ret = zone_update_apply_changeset(update, &diff);
207 	changeset_clear(&diff);
208 	if (ret != KNOT_EOK) {
209 		zone_update_clear(update);
210 		return ret;
211 	}
212 
213 	update->init_cont = new_cont;
214 	return KNOT_EOK;
215 }
216 
zone_update_from_contents(zone_update_t * update,zone_t * zone_without_contents,zone_contents_t * new_cont,zone_update_flags_t flags)217 int zone_update_from_contents(zone_update_t *update, zone_t *zone_without_contents,
218                               zone_contents_t *new_cont, zone_update_flags_t flags)
219 {
220 	if (update == NULL || zone_without_contents == NULL || new_cont == NULL) {
221 		return KNOT_EINVAL;
222 	}
223 
224 	memset(update, 0, sizeof(*update));
225 	update->zone = zone_without_contents;
226 	update->flags = flags;
227 	update->new_cont = new_cont;
228 
229 	update->a_ctx = calloc(1, sizeof(*update->a_ctx));
230 	if (update->a_ctx == NULL) {
231 		return KNOT_ENOMEM;
232 	}
233 
234 	if (zone_without_contents->control_update != NULL) {
235 		log_zone_warning(zone_without_contents->name,
236 		                 "blocked zone update due to open control transaction");
237 	}
238 
239 	knot_sem_wait(&update->zone->cow_lock);
240 	update->a_ctx->cow_mutex = &update->zone->cow_lock;
241 
242 	if (flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
243 		int ret = changeset_init(&update->change, zone_without_contents->name);
244 		if (ret != KNOT_EOK) {
245 			free(update->a_ctx);
246 			update->a_ctx = NULL;
247 			knot_sem_post(&update->zone->cow_lock);
248 			return ret;
249 		}
250 
251 		update->change.soa_from = node_create_rrset(new_cont->apex, KNOT_RRTYPE_SOA);
252 		if (update->change.soa_from == NULL) {
253 			changeset_clear(&update->change);
254 			free(update->a_ctx);
255 			update->a_ctx = NULL;
256 			knot_sem_post(&update->zone->cow_lock);
257 			return KNOT_ENOMEM;
258 		}
259 	}
260 
261 	uint32_t apply_flags = (update->flags & UPDATE_STRICT) ? APPLY_STRICT : 0;
262 	int ret = apply_init_ctx(update->a_ctx, update->new_cont, apply_flags | APPLY_UNIFY_FULL);
263 	if (ret != KNOT_EOK) {
264 		changeset_clear(&update->change);
265 		free(update->a_ctx);
266 		update->a_ctx = NULL;
267 		knot_sem_post(&update->zone->cow_lock);
268 		return ret;
269 	}
270 
271 	return KNOT_EOK;
272 }
273 
zone_update_start_extra(zone_update_t * update,conf_t * conf)274 int zone_update_start_extra(zone_update_t *update, conf_t *conf)
275 {
276 	assert((update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)));
277 
278 	int ret = changeset_init(&update->extra_ch, update->new_cont->apex->owner);
279 	if (ret != KNOT_EOK) {
280 		return ret;
281 	}
282 
283 	if (update->init_cont != NULL) {
284 		ret = zone_update_increment_soa(update, conf);
285 		if (ret != KNOT_EOK) {
286 			return ret;
287 		}
288 
289 		ret = zone_contents_diff(update->init_cont, update->new_cont, &update->extra_ch, false);
290 		if (ret != KNOT_EOK) {
291 			return ret;
292 		}
293 	} else {
294 		update->extra_ch.soa_from = node_create_rrset(update->new_cont->apex, KNOT_RRTYPE_SOA);
295 		if (update->extra_ch.soa_from == NULL) {
296 			return KNOT_ENOMEM;
297 		}
298 
299 		ret = zone_update_increment_soa(update, conf);
300 		if (ret != KNOT_EOK) {
301 			return ret;
302 		}
303 
304 		update->extra_ch.soa_to = node_create_rrset(update->new_cont->apex, KNOT_RRTYPE_SOA);
305 		if (update->extra_ch.soa_to == NULL) {
306 			return KNOT_ENOMEM;
307 		}
308 	}
309 
310 	update->flags |= UPDATE_EXTRA_CHSET;
311 	return KNOT_EOK;
312 }
313 
zone_update_get_node(zone_update_t * update,const knot_dname_t * dname)314 const zone_node_t *zone_update_get_node(zone_update_t *update, const knot_dname_t *dname)
315 {
316 	if (update == NULL || dname == NULL) {
317 		return NULL;
318 	}
319 
320 	return zone_contents_node_or_nsec3(update->new_cont, dname);
321 }
322 
zone_update_current_serial(zone_update_t * update)323 uint32_t zone_update_current_serial(zone_update_t *update)
324 {
325 	const zone_node_t *apex = update->new_cont->apex;
326 	if (apex != NULL) {
327 		return knot_soa_serial(node_rdataset(apex, KNOT_RRTYPE_SOA)->rdata);
328 	} else {
329 		return 0;
330 	}
331 }
332 
zone_update_changed_nsec3param(const zone_update_t * update)333 bool zone_update_changed_nsec3param(const zone_update_t *update)
334 {
335 	if (update->zone->contents == NULL) {
336 		return true;
337 	}
338 
339 	dnssec_nsec3_params_t *orig = &update->zone->contents->nsec3_params;
340 	dnssec_nsec3_params_t *upd = &update->new_cont->nsec3_params;
341 	return !dnssec_nsec3_params_match(orig, upd);
342 }
343 
zone_update_from(zone_update_t * update)344 const knot_rdataset_t *zone_update_from(zone_update_t *update)
345 {
346 	if (update == NULL) {
347 		return NULL;
348 	}
349 
350 	if (update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
351 		const zone_node_t *apex = update->zone->contents->apex;
352 		return node_rdataset(apex, KNOT_RRTYPE_SOA);
353 	}
354 
355 	return NULL;
356 }
357 
zone_update_to(zone_update_t * update)358 const knot_rdataset_t *zone_update_to(zone_update_t *update)
359 {
360 	if (update == NULL) {
361 		return NULL;
362 	}
363 
364 	if (update->flags & UPDATE_FULL) {
365 		const zone_node_t *apex = update->new_cont->apex;
366 		return node_rdataset(apex, KNOT_RRTYPE_SOA);
367 	} else {
368 		if (update->change.soa_to == NULL) {
369 			return NULL;
370 		}
371 		return &update->change.soa_to->rrs;
372 	}
373 
374 	return NULL;
375 }
376 
zone_update_clear(zone_update_t * update)377 void zone_update_clear(zone_update_t *update)
378 {
379 	if (update == NULL || update->zone == NULL) {
380 		return;
381 	}
382 
383 	if (update->new_cont != NULL) {
384 		additionals_tree_free(update->new_cont->adds_tree);
385 		update->new_cont->adds_tree = NULL;
386 	}
387 
388 	if (update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
389 		changeset_clear(&update->change);
390 		changeset_clear(&update->extra_ch);
391 	}
392 
393 	zone_contents_deep_free(update->init_cont);
394 
395 	if (update->flags & (UPDATE_FULL | UPDATE_HYBRID)) {
396 		apply_cleanup(update->a_ctx);
397 		zone_contents_deep_free(update->new_cont);
398 	} else {
399 		apply_rollback(update->a_ctx);
400 	}
401 
402 	free(update->a_ctx);
403 	memset(update, 0, sizeof(*update));
404 }
405 
update_affected_rrtype(zone_update_t * update,uint16_t rrtype)406 inline static void update_affected_rrtype(zone_update_t *update, uint16_t rrtype)
407 {
408 	switch (rrtype) {
409 	case KNOT_RRTYPE_NSEC:
410 	case KNOT_RRTYPE_NSEC3:
411 		update->flags |= UPDATE_CHANGED_NSEC;
412 		break;
413 	}
414 }
415 
solve_add_different_ttl(zone_update_t * update,const knot_rrset_t * add)416 static int solve_add_different_ttl(zone_update_t *update, const knot_rrset_t *add)
417 {
418 	if (add->type == KNOT_RRTYPE_RRSIG || add->type == KNOT_RRTYPE_SOA) {
419 		return KNOT_EOK;
420 	}
421 
422 	const zone_node_t *exist_node = zone_contents_find_node(update->new_cont, add->owner);
423 	const knot_rrset_t exist_rr = node_rrset(exist_node, add->type);
424 	if (knot_rrset_empty(&exist_rr) || exist_rr.ttl == add->ttl) {
425 		return KNOT_EOK;
426 	}
427 
428 	knot_dname_txt_storage_t buff;
429 	char *owner = knot_dname_to_str(buff, add->owner, sizeof(buff));
430 	if (owner == NULL) {
431 		owner = "";
432 	}
433 	char type[16] = "";
434 	knot_rrtype_to_string(add->type, type, sizeof(type));
435 	log_zone_notice(update->zone->name, "TTL mismatch, owner %s, type %s, "
436 	                "TTL set to %u", owner, type, add->ttl);
437 
438 	knot_rrset_t *exist_copy = knot_rrset_copy(&exist_rr, NULL);
439 	if (exist_copy == NULL) {
440 		return KNOT_ENOMEM;
441 	}
442 	int ret = zone_update_remove(update, exist_copy);
443 	if (ret == KNOT_EOK) {
444 		exist_copy->ttl = add->ttl;
445 		ret = zone_update_add(update, exist_copy);
446 	}
447 	knot_rrset_free(exist_copy, NULL);
448 	return ret;
449 }
450 
zone_update_add(zone_update_t * update,const knot_rrset_t * rrset)451 int zone_update_add(zone_update_t *update, const knot_rrset_t *rrset)
452 {
453 	if (update == NULL || rrset == NULL) {
454 		return KNOT_EINVAL;
455 	}
456 	if (knot_rrset_empty(rrset)) {
457 		return KNOT_EOK;
458 	}
459 
460 	if (update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
461 		int ret = solve_add_different_ttl(update, rrset);
462 		if (ret == KNOT_EOK) {
463 			ret = changeset_add_addition(&update->change, rrset, CHANGESET_CHECK);
464 		}
465 		if (ret == KNOT_EOK && (update->flags & UPDATE_EXTRA_CHSET)) {
466 			ret = changeset_add_addition(&update->extra_ch, rrset, CHANGESET_CHECK);
467 		}
468 		if (ret != KNOT_EOK) {
469 			return ret;
470 		}
471 	}
472 
473 	if (update->flags & UPDATE_INCREMENTAL) {
474 		if (rrset->type == KNOT_RRTYPE_SOA) {
475 			// replace previous SOA
476 			int ret = apply_replace_soa(update->a_ctx, rrset);
477 			if (ret != KNOT_EOK) {
478 				changeset_remove_addition(&update->change, rrset);
479 			}
480 			return ret;
481 		}
482 
483 		int ret = apply_add_rr(update->a_ctx, rrset);
484 		if (ret != KNOT_EOK) {
485 			changeset_remove_addition(&update->change, rrset);
486 			return ret;
487 		}
488 
489 		update_affected_rrtype(update, rrset->type);
490 		return KNOT_EOK;
491 	} else if (update->flags & (UPDATE_FULL | UPDATE_HYBRID)) {
492 		if (rrset->type == KNOT_RRTYPE_SOA) {
493 			/* replace previous SOA */
494 			return replace_soa(update->new_cont, rrset);
495 		}
496 
497 		zone_node_t *n = NULL;
498 		int ret = zone_contents_add_rr(update->new_cont, rrset, &n);
499 		if (ret == KNOT_ETTL) {
500 			knot_dname_txt_storage_t buff;
501 			char *owner = knot_dname_to_str(buff, rrset->owner, sizeof(buff));
502 			if (owner == NULL) {
503 				owner = "";
504 			}
505 			char type[16] = "";
506 			knot_rrtype_to_string(rrset->type, type, sizeof(type));
507 			log_zone_notice(update->new_cont->apex->owner,
508 			                "TTL mismatch, owner %s, type %s, "
509 			                "TTL set to %u", owner, type, rrset->ttl);
510 			return KNOT_EOK;
511 		}
512 
513 		return ret;
514 	} else {
515 		return KNOT_EINVAL;
516 	}
517 }
518 
zone_update_remove(zone_update_t * update,const knot_rrset_t * rrset)519 int zone_update_remove(zone_update_t *update, const knot_rrset_t *rrset)
520 {
521 	if (update == NULL || rrset == NULL) {
522 		return KNOT_EINVAL;
523 	}
524 	if (knot_rrset_empty(rrset)) {
525 		return KNOT_EOK;
526 	}
527 
528 	if ((update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) && rrset->type != KNOT_RRTYPE_SOA) {
529 		int ret = changeset_add_removal(&update->change, rrset, CHANGESET_CHECK);
530 		if (ret == KNOT_EOK && (update->flags & UPDATE_EXTRA_CHSET)) {
531 			ret = changeset_add_removal(&update->extra_ch, rrset, CHANGESET_CHECK);
532 		}
533 		if (ret != KNOT_EOK) {
534 			return ret;
535 		}
536 	}
537 
538 	if (update->flags & UPDATE_INCREMENTAL) {
539 		if (rrset->type == KNOT_RRTYPE_SOA) {
540 			/* SOA is replaced with addition */
541 			return KNOT_EOK;
542 		}
543 
544 		int ret = apply_remove_rr(update->a_ctx, rrset);
545 		if (ret != KNOT_EOK) {
546 			changeset_remove_removal(&update->change, rrset);
547 			return ret;
548 		}
549 
550 		update_affected_rrtype(update, rrset->type);
551 		return KNOT_EOK;
552 	} else if (update->flags & (UPDATE_FULL | UPDATE_HYBRID)) {
553 		zone_node_t *n = NULL;
554 		return zone_contents_remove_rr(update->new_cont, rrset, &n);
555 	} else {
556 		return KNOT_EINVAL;
557 	}
558 }
559 
zone_update_remove_rrset(zone_update_t * update,knot_dname_t * owner,uint16_t type)560 int zone_update_remove_rrset(zone_update_t *update, knot_dname_t *owner, uint16_t type)
561 {
562 	if (update == NULL || owner == NULL) {
563 		return KNOT_EINVAL;
564 	}
565 
566 	const zone_node_t *node = zone_contents_node_or_nsec3(update->new_cont, owner);
567 	if (node == NULL) {
568 		return KNOT_ENONODE;
569 	}
570 
571 	knot_rrset_t rrset = node_rrset(node, type);
572 	if (rrset.owner == NULL) {
573 		return KNOT_ENOENT;
574 	}
575 
576 	return zone_update_remove(update, &rrset);
577 }
578 
zone_update_remove_node(zone_update_t * update,const knot_dname_t * owner)579 int zone_update_remove_node(zone_update_t *update, const knot_dname_t *owner)
580 {
581 	if (update == NULL || owner == NULL) {
582 		return KNOT_EINVAL;
583 	}
584 
585 	const zone_node_t *node = zone_contents_node_or_nsec3(update->new_cont, owner);
586 	if (node == NULL) {
587 		return KNOT_ENONODE;
588 	}
589 
590 	size_t rrset_count = node->rrset_count;
591 	for (int i = 0; i < rrset_count; ++i) {
592 		knot_rrset_t rrset = node_rrset_at(node, rrset_count - 1 - i);
593 		int ret = zone_update_remove(update, &rrset);
594 		if (ret != KNOT_EOK) {
595 			return ret;
596 		}
597 	}
598 
599 	return KNOT_EOK;
600 }
601 
update_chset_step(const knot_rrset_t * rrset,bool addition,void * ctx)602 static int update_chset_step(const knot_rrset_t *rrset, bool addition, void *ctx)
603 {
604 	zone_update_t *update = ctx;
605 	if (addition) {
606 		return zone_update_add(update, rrset);
607 	} else {
608 		return zone_update_remove(update, rrset);
609 	}
610 }
611 
zone_update_apply_changeset(zone_update_t * update,const changeset_t * changes)612 int zone_update_apply_changeset(zone_update_t *update, const changeset_t *changes)
613 {
614 	return changeset_walk(changes, update_chset_step, update);
615 }
616 
zone_update_apply_changeset_reverse(zone_update_t * update,const changeset_t * changes)617 int zone_update_apply_changeset_reverse(zone_update_t *update, const changeset_t *changes)
618 {
619 	changeset_t reverse;
620 	reverse.remove = changes->add;
621 	reverse.add = changes->remove;
622 	reverse.soa_from = changes->soa_to;
623 	reverse.soa_to = changes->soa_from;
624 	return zone_update_apply_changeset(update, &reverse);
625 }
626 
set_new_soa(zone_update_t * update,unsigned serial_policy)627 static int set_new_soa(zone_update_t *update, unsigned serial_policy)
628 {
629 	assert(update);
630 
631 	knot_rrset_t *soa_cpy = node_create_rrset(update->new_cont->apex,
632 	                                          KNOT_RRTYPE_SOA);
633 	if (soa_cpy == NULL) {
634 		return KNOT_ENOMEM;
635 	}
636 
637 	int ret = zone_update_remove(update, soa_cpy);
638 	if (ret != KNOT_EOK) {
639 		knot_rrset_free(soa_cpy, NULL);
640 		return ret;
641 	}
642 
643 	uint32_t old_serial = knot_soa_serial(soa_cpy->rrs.rdata);
644 	uint32_t new_serial = serial_next(old_serial, serial_policy, 1);
645 	if (serial_compare(old_serial, new_serial) != SERIAL_LOWER) {
646 		log_zone_warning(update->zone->name, "updated SOA serial is lower "
647 		                 "than current, serial %u -> %u",
648 		                 old_serial, new_serial);
649 		ret = KNOT_ESOAINVAL;
650 	} else {
651 		knot_soa_serial_set(soa_cpy->rrs.rdata, new_serial);
652 
653 		ret = zone_update_add(update, soa_cpy);
654 	}
655 	knot_rrset_free(soa_cpy, NULL);
656 
657 	return ret;
658 }
659 
zone_update_increment_soa(zone_update_t * update,conf_t * conf)660 int zone_update_increment_soa(zone_update_t *update, conf_t *conf)
661 {
662 	if (update == NULL || conf == NULL) {
663 		return KNOT_EINVAL;
664 	}
665 
666 	conf_val_t val = conf_zone_get(conf, C_SERIAL_POLICY, update->zone->name);
667 	return set_new_soa(update, conf_opt(&val));
668 }
669 
commit_journal(conf_t * conf,zone_update_t * update)670 static int commit_journal(conf_t *conf, zone_update_t *update)
671 {
672 	conf_val_t val = conf_zone_get(conf, C_JOURNAL_CONTENT, update->zone->name);
673 	unsigned content = conf_opt(&val);
674 	int ret = KNOT_EOK;
675 	if ((update->flags & UPDATE_INCREMENTAL) ||
676 	    (update->flags & UPDATE_HYBRID)) {
677 		changeset_t *extra = (update->flags & UPDATE_EXTRA_CHSET) ? &update->extra_ch : NULL;
678 		if (content != JOURNAL_CONTENT_NONE && !changeset_empty(&update->change)) {
679 			ret = zone_change_store(conf, update->zone, &update->change, extra);
680 		}
681 	} else {
682 		if (content == JOURNAL_CONTENT_ALL) {
683 			return zone_in_journal_store(conf, update->zone, update->new_cont);
684 		} else if (content != JOURNAL_CONTENT_NONE) { // zone_in_journal_store does this automatically
685 			return zone_changes_clear(conf, update->zone);
686 		}
687 	}
688 	return ret;
689 }
690 
commit_incremental(conf_t * conf,zone_update_t * update)691 static int commit_incremental(conf_t *conf, zone_update_t *update)
692 {
693 	assert(update);
694 
695 	if (zone_update_to(update) == NULL && !changeset_empty(&update->change)) {
696 		/* No SOA in the update, create one according to the current policy */
697 		int ret = zone_update_increment_soa(update, conf);
698 		if (ret != KNOT_EOK) {
699 			return ret;
700 		}
701 	}
702 
703 	return KNOT_EOK;
704 }
705 
commit_full(conf_t * conf,zone_update_t * update)706 static int commit_full(conf_t *conf, zone_update_t *update)
707 {
708 	assert(update);
709 
710 	/* Check if we have SOA. We might consider adding full semantic check here.
711 	 * But if we wanted full sem-check I'd consider being it controlled by a flag
712 	 * - to enable/disable it on demand. */
713 	if (!node_rrtype_exists(update->new_cont->apex, KNOT_RRTYPE_SOA)) {
714 		return KNOT_ESEMCHECK;
715 	}
716 
717 	return KNOT_EOK;
718 }
719 
update_catalog(conf_t * conf,zone_update_t * update)720 static int update_catalog(conf_t *conf, zone_update_t *update)
721 {
722 	conf_val_t val = conf_zone_get(conf, C_CATALOG_TPL, update->zone->name);
723 	if (val.code != KNOT_EOK) {
724 		return (val.code == KNOT_ENOENT || val.code == KNOT_YP_EINVAL_ID) ? KNOT_EOK : val.code;
725 	}
726 
727 	zone_set_flag(update->zone, ZONE_IS_CATALOG);
728 
729 	int ret = catalog_zone_verify(update->new_cont);
730 	if (ret != KNOT_EOK) {
731 		return ret;
732 	}
733 
734 	ssize_t upd_count = 0;
735 	if ((update->flags & UPDATE_INCREMENTAL)) {
736 		ret = catalog_update_from_zone(update->zone->catalog_upd,
737 		                               update->change.remove, update->new_cont,
738 		                               true, update->zone->catalog, &upd_count);
739 		if (ret == KNOT_EOK) {
740 			ret = catalog_update_from_zone(update->zone->catalog_upd,
741 			                               update->change.add, update->new_cont,
742 			                               false, NULL, &upd_count);
743 		}
744 	} else {
745 		ret = catalog_update_del_all(update->zone->catalog_upd,
746 		                             update->zone->catalog,
747 		                             update->zone->name, &upd_count);
748 		if (ret == KNOT_EOK) {
749 			ret = catalog_update_from_zone(update->zone->catalog_upd,
750 			                               update->new_cont, update->new_cont,
751 			                               false, NULL, &upd_count);
752 		}
753 	}
754 
755 	if (ret == KNOT_EOK) {
756 		log_zone_info(update->zone->name, "catalog reloaded, %zd updates", upd_count);
757 		if (kill(getpid(), SIGUSR1) != 0) {
758 			ret = knot_map_errno();
759 		}
760 	} else {
761 		// this cant normally happen, just some ENOMEM or so
762 		(void)catalog_update_del_all(update->zone->catalog_upd,
763 		                             update->zone->catalog,
764 		                             update->zone->name, &upd_count);
765 	}
766 
767 	return ret;
768 }
769 
770 typedef struct {
771 	pthread_mutex_t lock;
772 	size_t counter;
773 } counter_reach_t;
774 
counter_reach(counter_reach_t * counter,size_t increment,size_t limit)775 static bool counter_reach(counter_reach_t *counter, size_t increment, size_t limit)
776 {
777 	bool reach = false;
778 	pthread_mutex_lock(&counter->lock);
779 	counter->counter += increment;
780 	if (counter->counter >= limit) {
781 		counter->counter = 0;
782 		reach = true;
783 	}
784 	pthread_mutex_unlock(&counter->lock);
785 	return reach;
786 }
787 
788 /*! \brief Struct for what needs to be cleared after RCU.
789  *
790  * This can't be zone_update_t structure as this might be already freed at that time.
791  */
792 typedef struct {
793 	struct rcu_head rcuhead;
794 
795 	zone_contents_t *free_contents;
796 	void (*free_method)(zone_contents_t *);
797 
798 	apply_ctx_t *cleanup_apply;
799 
800 	size_t new_cont_size;
801 } update_clear_ctx_t;
802 
update_clear(struct rcu_head * param)803 static void update_clear(struct rcu_head *param)
804 {
805 	static counter_reach_t counter = { PTHREAD_MUTEX_INITIALIZER, 0 };
806 
807 	update_clear_ctx_t *ctx = (update_clear_ctx_t *)param;
808 
809 	ctx->free_method(ctx->free_contents);
810 	apply_cleanup(ctx->cleanup_apply);
811 	free(ctx->cleanup_apply);
812 
813 	if (counter_reach(&counter, ctx->new_cont_size, UPDATE_MEMTRIM_AT)) {
814 		mem_trim();
815 	}
816 
817 	free(ctx);
818 }
819 
discard_adds_tree(zone_update_t * update)820 static void discard_adds_tree(zone_update_t *update)
821 {
822 	additionals_tree_free(update->new_cont->adds_tree);
823 	update->new_cont->adds_tree = NULL;
824 }
825 
zone_update_semcheck(zone_update_t * update)826 int zone_update_semcheck(zone_update_t *update)
827 {
828 	if (update == NULL) {
829 		return KNOT_EINVAL;
830 	}
831 
832 	zone_tree_t *node_ptrs = (update->flags & UPDATE_INCREMENTAL) ?
833 	                         update->a_ctx->node_ptrs : NULL;
834 
835 	// adjust_cb_nsec3_pointer not needed as we don't check DNSSEC here
836 	int ret = zone_adjust_contents(update->new_cont, adjust_cb_flags, NULL,
837 	                               false, false, 1, node_ptrs);
838 	if (ret != KNOT_EOK) {
839 		return ret;
840 	}
841 
842 	sem_handler_t handler = {
843 		.cb = err_handler_logger
844 	};
845 
846 	ret = sem_checks_process(update->new_cont, SEMCHECK_MANDATORY_ONLY,
847 	                         &handler, time(NULL));
848 	if (ret != KNOT_EOK) {
849 		// error is logged by the error handler
850 		return ret;
851 	}
852 
853 	return KNOT_EOK;
854 }
855 
zone_update_verify_digest(conf_t * conf,zone_update_t * update)856 int zone_update_verify_digest(conf_t *conf, zone_update_t *update)
857 {
858 	conf_val_t val = conf_zone_get(conf, C_ZONEMD_VERIFY, update->zone->name);
859 	if (!conf_bool(&val)) {
860 		return KNOT_EOK;
861 	}
862 
863 	int ret = zone_contents_digest_verify(update->new_cont);
864 	if (ret != KNOT_EOK) {
865 		log_zone_error(update->zone->name, "ZONEMD, verification failed (%s)",
866 		               knot_strerror(ret));
867 	} else {
868 		log_zone_info(update->zone->name, "ZONEMD, verification successful");
869 	}
870 
871 	return ret;
872 }
873 
zone_update_commit(conf_t * conf,zone_update_t * update)874 int zone_update_commit(conf_t *conf, zone_update_t *update)
875 {
876 	if (conf == NULL || update == NULL) {
877 		return KNOT_EINVAL;
878 	}
879 
880 	int ret = KNOT_EOK;
881 
882 	if ((update->flags & UPDATE_INCREMENTAL) && changeset_empty(&update->change)) {
883 		zone_update_clear(update);
884 		return KNOT_EOK;
885 	}
886 
887 	if (update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
888 		ret = commit_incremental(conf, update);
889 	} else {
890 		ret = commit_full(conf, update);
891 	}
892 	if (ret != KNOT_EOK) {
893 		return ret;
894 	}
895 
896 	conf_val_t val = conf_zone_get(conf, C_DNSSEC_SIGNING, update->zone->name);
897 	bool dnssec = conf_bool(&val);
898 
899 	conf_val_t thr = conf_zone_get(conf, C_ADJUST_THR, update->zone->name);
900 	if ((update->flags & (UPDATE_HYBRID | UPDATE_FULL))) {
901 		ret = zone_adjust_full(update->new_cont, conf_int(&thr));
902 	} else {
903 		ret = zone_adjust_incremental_update(update, conf_int(&thr));
904 	}
905 	if (ret != KNOT_EOK) {
906 		discard_adds_tree(update);
907 		return ret;
908 	}
909 
910 	/* Check the zone size. */
911 	val = conf_zone_get(conf, C_ZONE_MAX_SIZE, update->zone->name);
912 	size_t size_limit = conf_int(&val);
913 
914 	if (update->new_cont->size > size_limit) {
915 		discard_adds_tree(update);
916 		return KNOT_EZONESIZE;
917 	}
918 
919 	val = conf_zone_get(conf, C_DNSSEC_VALIDATION, update->zone->name);
920 	if (conf_bool(&val)) {
921 		bool incr_valid = update->flags & UPDATE_INCREMENTAL;
922 		const char *msg_valid = incr_valid ? "incremental " : "";
923 
924 		ret = knot_dnssec_validate_zone(update, conf, incr_valid);
925 		if (ret != KNOT_EOK) {
926 			log_zone_error(update->zone->name, "DNSSEC, %svalidation failed (%s)",
927 			               msg_valid, knot_strerror(ret));
928 			char name_str[KNOT_DNAME_TXT_MAXLEN], type_str[16];
929 			if (knot_dname_to_str(name_str, update->validation_hint.node, sizeof(name_str)) != NULL &&
930 			    knot_rrtype_to_string(update->validation_hint.rrtype, type_str, sizeof(type_str)) >= 0) {
931 				log_zone_error(update->zone->name, "DNSSEC, validation hint: %s %s",
932 				               name_str, type_str);
933 			}
934 			discard_adds_tree(update);
935 			return ret;
936 		} else {
937 			log_zone_info(update->zone->name, "DNSSEC, %svalidation successful", msg_valid);
938 		}
939 	}
940 
941 	ret = update_catalog(conf, update);
942 	if (ret != KNOT_EOK) {
943 		log_zone_error(update->zone->name, "failed to process catalog zone (%s)", knot_strerror(ret));
944 		discard_adds_tree(update);
945 		return ret;
946 	}
947 
948 	ret = commit_journal(conf, update);
949 	if (ret != KNOT_EOK) {
950 		discard_adds_tree(update);
951 		return ret;
952 	}
953 
954 	if (dnssec && zone_is_slave(conf, update->zone)) {
955 		ret = zone_set_lastsigned_serial(update->zone,
956 		                                 zone_contents_serial(update->new_cont));
957 		if (ret != KNOT_EOK) {
958 			log_zone_warning(update->zone->name,
959 			                 "unable to save lastsigned serial, "
960 			                 "future transfers might be broken");
961 		}
962 	}
963 
964 	/* Switch zone contents. */
965 	zone_contents_t *old_contents;
966 	old_contents = zone_switch_contents(update->zone, update->new_cont);
967 
968 	if (update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
969 		changeset_clear(&update->change);
970 		changeset_clear(&update->extra_ch);
971 	}
972 	zone_contents_deep_free(update->init_cont);
973 
974 	update_clear_ctx_t *clear_ctx = calloc(1, sizeof(*clear_ctx));
975 	if (clear_ctx != NULL) {
976 		clear_ctx->free_contents = old_contents;
977 		clear_ctx->free_method = (
978 			(update->flags & (UPDATE_FULL | UPDATE_HYBRID)) ?
979 			zone_contents_deep_free : update_free_zone
980 		);
981 		clear_ctx->cleanup_apply = update->a_ctx;
982 		clear_ctx->new_cont_size = update->new_cont->size;
983 
984 		call_rcu((struct rcu_head *)clear_ctx, update_clear);
985 	} else {
986 		log_zone_error(update->zone->name, "failed to deallocate unused memory");
987 	}
988 
989 	/* Sync zonefile immediately if configured. */
990 	val = conf_zone_get(conf, C_ZONEFILE_SYNC, update->zone->name);
991 	if (conf_int(&val) == 0) {
992 		zone_events_schedule_now(update->zone, ZONE_EVENT_FLUSH);
993 	}
994 
995 	memset(update, 0, sizeof(*update));
996 
997 	return KNOT_EOK;
998 }
999 
zone_update_no_change(zone_update_t * update)1000 bool zone_update_no_change(zone_update_t *update)
1001 {
1002 	if (update == NULL) {
1003 		return true;
1004 	}
1005 
1006 	if (update->flags & (UPDATE_INCREMENTAL | UPDATE_HYBRID)) {
1007 		return changeset_empty(&update->change);
1008 	} else {
1009 		/* This branch does not make much sense and FULL update will most likely
1010 		 * be a change every time anyway, just return false. */
1011 		return false;
1012 	}
1013 }
1014