1 /*  Copyright (C) 2019 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 <assert.h>
18 
19 #include "knot/common/log.h"
20 #include "knot/updates/ddns.h"
21 #include "knot/updates/changesets.h"
22 #include "knot/updates/zone-update.h"
23 #include "knot/zone/serial.h"
24 #include "libknot/libknot.h"
25 #include "contrib/ucw/lists.h"
26 
27 /* ----------------------------- prereq check ------------------------------- */
28 
29 /*!< \brief Clears prereq RRSet list. */
rrset_list_clear(list_t * l)30 static void rrset_list_clear(list_t *l)
31 {
32 	node_t *n, *nxt;
33 	WALK_LIST_DELSAFE(n, nxt, *l) {
34 		ptrnode_t *ptr_n = (ptrnode_t *)n;
35 		knot_rrset_t *rrset = (knot_rrset_t *)ptr_n->d;
36 		knot_rrset_free(rrset, NULL);
37 		free(n);
38 	};
39 }
40 
41 /*!< \brief Adds RR to prereq RRSet list, merges RRs into RRSets. */
add_rr_to_list(list_t * l,const knot_rrset_t * rr)42 static int add_rr_to_list(list_t *l, const knot_rrset_t *rr)
43 {
44 	node_t *n;
45 	WALK_LIST(n, *l) {
46 		ptrnode_t *ptr_n = (ptrnode_t *)n;
47 		knot_rrset_t *rrset = (knot_rrset_t *)ptr_n->d;
48 		if (rrset->type == rr->type && knot_dname_is_equal(rrset->owner, rr->owner)) {
49 			return knot_rdataset_merge(&rrset->rrs, &rr->rrs, NULL);
50 		}
51 	};
52 
53 	knot_rrset_t *rr_copy = knot_rrset_copy(rr, NULL);
54 	if (rr_copy == NULL) {
55 		return KNOT_ENOMEM;
56 	}
57 	return ptrlist_add(l, rr_copy, NULL) != NULL ? KNOT_EOK : KNOT_ENOMEM;
58 }
59 
60 /*!< \brief Checks whether RRSet exists in the zone. */
check_rrset_exists(zone_update_t * update,const knot_rrset_t * rrset,uint16_t * rcode)61 static int check_rrset_exists(zone_update_t *update, const knot_rrset_t *rrset,
62                               uint16_t *rcode)
63 {
64 	assert(rrset->type != KNOT_RRTYPE_ANY);
65 
66 	const zone_node_t *node = zone_update_get_node(update, rrset->owner);
67 	if (node == NULL || !node_rrtype_exists(node, rrset->type)) {
68 		*rcode = KNOT_RCODE_NXRRSET;
69 		return KNOT_EPREREQ;
70 	} else {
71 		knot_rrset_t found = node_rrset(node, rrset->type);
72 		assert(!knot_rrset_empty(&found));
73 		if (knot_rrset_equal(&found, rrset, false)) {
74 			return KNOT_EOK;
75 		} else {
76 			*rcode = KNOT_RCODE_NXRRSET;
77 			return KNOT_EPREREQ;
78 		}
79 	}
80 }
81 
82 /*!< \brief Checks whether RRSets in the list exist in the zone. */
check_stored_rrsets(list_t * l,zone_update_t * update,uint16_t * rcode)83 static int check_stored_rrsets(list_t *l, zone_update_t *update,
84                                uint16_t *rcode)
85 {
86 	node_t *n;
87 	WALK_LIST(n, *l) {
88 		ptrnode_t *ptr_n = (ptrnode_t *)n;
89 		knot_rrset_t *rrset = (knot_rrset_t *)ptr_n->d;
90 		int ret = check_rrset_exists(update, rrset, rcode);
91 		if (ret != KNOT_EOK) {
92 			return ret;
93 		}
94 	};
95 
96 	return KNOT_EOK;
97 }
98 
99 /*!< \brief Checks whether node of given owner, with given type exists. */
check_type(zone_update_t * update,const knot_rrset_t * rrset)100 static bool check_type(zone_update_t *update, const knot_rrset_t *rrset)
101 {
102 	assert(rrset->type != KNOT_RRTYPE_ANY);
103 	const zone_node_t *node = zone_update_get_node(update, rrset->owner);
104 	if (node == NULL || !node_rrtype_exists(node, rrset->type)) {
105 		return false;
106 	}
107 
108 	return true;
109 }
110 
111 /*!< \brief Checks whether RR type exists in the zone. */
check_type_exist(zone_update_t * update,const knot_rrset_t * rrset,uint16_t * rcode)112 static int check_type_exist(zone_update_t *update,
113                             const knot_rrset_t *rrset, uint16_t *rcode)
114 {
115 	assert(rrset->rclass == KNOT_CLASS_ANY);
116 	if (check_type(update, rrset)) {
117 		return KNOT_EOK;
118 	} else {
119 		*rcode = KNOT_RCODE_NXRRSET;
120 		return KNOT_EPREREQ;
121 	}
122 }
123 
124 /*!< \brief Checks whether RR type is not in the zone. */
check_type_not_exist(zone_update_t * update,const knot_rrset_t * rrset,uint16_t * rcode)125 static int check_type_not_exist(zone_update_t *update,
126                                 const knot_rrset_t *rrset, uint16_t *rcode)
127 {
128 	assert(rrset->rclass == KNOT_CLASS_NONE);
129 	if (check_type(update, rrset)) {
130 		*rcode = KNOT_RCODE_YXRRSET;
131 		return KNOT_EPREREQ;
132 	} else {
133 		return KNOT_EOK;
134 	}
135 }
136 
137 /*!< \brief Checks whether DNAME is in the zone. */
check_in_use(zone_update_t * update,const knot_dname_t * dname,uint16_t * rcode)138 static int check_in_use(zone_update_t *update,
139                         const knot_dname_t *dname, uint16_t *rcode)
140 {
141 	const zone_node_t *node = zone_update_get_node(update, dname);
142 	if (node == NULL || node->rrset_count == 0) {
143 		*rcode = KNOT_RCODE_NXDOMAIN;
144 		return KNOT_EPREREQ;
145 	} else {
146 		return KNOT_EOK;
147 	}
148 }
149 
150 /*!< \brief Checks whether DNAME is not in the zone. */
check_not_in_use(zone_update_t * update,const knot_dname_t * dname,uint16_t * rcode)151 static int check_not_in_use(zone_update_t *update,
152                             const knot_dname_t *dname, uint16_t *rcode)
153 {
154 	const zone_node_t *node = zone_update_get_node(update, dname);
155 	if (node == NULL || node->rrset_count == 0) {
156 		return KNOT_EOK;
157 	} else {
158 		*rcode = KNOT_RCODE_YXDOMAIN;
159 		return KNOT_EPREREQ;
160 	}
161 }
162 
163 /*!< \brief Returns true if rrset has 0 data or RDATA of size 0 (we need TTL). */
rrset_empty(const knot_rrset_t * rrset)164 static bool rrset_empty(const knot_rrset_t *rrset)
165 {
166 	switch (rrset->rrs.count) {
167 	case 0:
168 		return true;
169 	case 1:
170 		return rrset->rrs.rdata->len == 0;
171 	default:
172 		return false;
173 	}
174 }
175 
176 /*< \brief Returns true if DDNS should deny updating DNSSEC-related record. */
is_dnssec_protected(uint16_t type,bool is_apex)177 static bool is_dnssec_protected(uint16_t type, bool is_apex)
178 {
179 	switch (type) {
180 	case KNOT_RRTYPE_RRSIG:
181 	case KNOT_RRTYPE_NSEC:
182 	case KNOT_RRTYPE_NSEC3:
183 	case KNOT_RRTYPE_CDNSKEY:
184 	case KNOT_RRTYPE_CDS:
185 		return true;
186 	case KNOT_RRTYPE_DNSKEY:
187 	case KNOT_RRTYPE_NSEC3PARAM:
188 		return is_apex;
189 	default:
190 		return false;
191 	}
192 }
193 
194 /*!< \brief Checks prereq for given packet RR. */
process_prereq(const knot_rrset_t * rrset,uint16_t qclass,zone_update_t * update,uint16_t * rcode,list_t * rrset_list)195 static int process_prereq(const knot_rrset_t *rrset, uint16_t qclass,
196                           zone_update_t *update, uint16_t *rcode,
197                           list_t *rrset_list)
198 {
199 	if (rrset->ttl != 0) {
200 		*rcode = KNOT_RCODE_FORMERR;
201 		return KNOT_EMALF;
202 	}
203 
204 	if (knot_dname_in_bailiwick(rrset->owner, update->zone->name) < 0) {
205 		*rcode = KNOT_RCODE_NOTZONE;
206 		return KNOT_EOUTOFZONE;
207 	}
208 
209 	if (rrset->rclass == KNOT_CLASS_ANY) {
210 		if (!rrset_empty(rrset)) {
211 			*rcode = KNOT_RCODE_FORMERR;
212 			return KNOT_EMALF;
213 		}
214 		if (rrset->type == KNOT_RRTYPE_ANY) {
215 			return check_in_use(update, rrset->owner, rcode);
216 		} else {
217 			return check_type_exist(update, rrset, rcode);
218 		}
219 	} else if (rrset->rclass == KNOT_CLASS_NONE) {
220 		if (!rrset_empty(rrset)) {
221 			*rcode = KNOT_RCODE_FORMERR;
222 			return KNOT_EMALF;
223 		}
224 		if (rrset->type == KNOT_RRTYPE_ANY) {
225 			return check_not_in_use(update, rrset->owner, rcode);
226 		} else {
227 			return check_type_not_exist(update, rrset, rcode);
228 		}
229 	} else if (rrset->rclass == qclass) {
230 		// Store RRs for full check into list
231 		int ret = add_rr_to_list(rrset_list, rrset);
232 		if (ret != KNOT_EOK) {
233 			*rcode = KNOT_RCODE_SERVFAIL;
234 		}
235 		return ret;
236 	} else {
237 		*rcode = KNOT_RCODE_FORMERR;
238 		return KNOT_EMALF;
239 	}
240 }
241 
242 /* --------------------------- DDNS processing ------------------------------ */
243 
244 /* --------------------- true/false helper functions ------------------------ */
245 
is_addition(const knot_rrset_t * rr)246 static inline bool is_addition(const knot_rrset_t *rr)
247 {
248 	return rr->rclass == KNOT_CLASS_IN;
249 }
250 
is_removal(const knot_rrset_t * rr)251 static inline bool is_removal(const knot_rrset_t *rr)
252 {
253 	return rr->rclass == KNOT_CLASS_NONE || rr->rclass == KNOT_CLASS_ANY;
254 }
255 
is_rr_removal(const knot_rrset_t * rr)256 static inline bool is_rr_removal(const knot_rrset_t *rr)
257 {
258 	return rr->rclass == KNOT_CLASS_NONE;
259 }
260 
is_rrset_removal(const knot_rrset_t * rr)261 static inline bool is_rrset_removal(const knot_rrset_t *rr)
262 {
263 	return rr->rclass == KNOT_CLASS_ANY && rr->type != KNOT_RRTYPE_ANY;
264 }
265 
is_node_removal(const knot_rrset_t * rr)266 static inline bool is_node_removal(const knot_rrset_t *rr)
267 {
268 	return rr->rclass == KNOT_CLASS_ANY && rr->type == KNOT_RRTYPE_ANY;
269 }
270 
271 /*!< \brief Returns true if last addition of certain types is to be replaced. */
should_replace(const knot_rrset_t * rrset)272 static bool should_replace(const knot_rrset_t *rrset)
273 {
274 	return rrset->type == KNOT_RRTYPE_CNAME ||
275 	       rrset->type == KNOT_RRTYPE_DNAME ||
276 	       rrset->type == KNOT_RRTYPE_NSEC3PARAM;
277 }
278 
279 /*!< \brief Returns true if node contains given RR in its RRSets. */
node_contains_rr(const zone_node_t * node,const knot_rrset_t * rrset)280 static bool node_contains_rr(const zone_node_t *node,
281                              const knot_rrset_t *rrset)
282 {
283 	const knot_rdataset_t *zone_rrs = node_rdataset(node, rrset->type);
284 	if (zone_rrs != NULL) {
285 		assert(rrset->rrs.count == 1);
286 		return knot_rdataset_member(zone_rrs, rrset->rrs.rdata);
287 	} else {
288 		return false;
289 	}
290 }
291 
292 /*!< \brief Returns true if CNAME is in this node. */
adding_to_cname(const knot_dname_t * owner,const zone_node_t * node)293 static bool adding_to_cname(const knot_dname_t *owner,
294                             const zone_node_t *node)
295 {
296 	if (node == NULL) {
297 		// Node did not exist before update.
298 		return false;
299 	}
300 
301 	knot_rrset_t cname = node_rrset(node, KNOT_RRTYPE_CNAME);
302 	if (knot_rrset_empty(&cname)) {
303 		// Node did not contain CNAME before update.
304 		return false;
305 	}
306 
307 	// CNAME present
308 	return true;
309 }
310 
311 /*!< \brief Used to ignore SOA deletions and SOAs with lower serial than zone. */
skip_soa(const knot_rrset_t * rr,int64_t sn)312 static bool skip_soa(const knot_rrset_t *rr, int64_t sn)
313 {
314 	if (rr->type == KNOT_RRTYPE_SOA &&
315 	    (rr->rclass == KNOT_CLASS_NONE || rr->rclass == KNOT_CLASS_ANY ||
316 	     (serial_compare(knot_soa_serial(rr->rrs.rdata), sn) != SERIAL_GREATER))) {
317 		return true;
318 	}
319 
320 	return false;
321 }
322 
323 /* ---------------------- changeset manipulation ---------------------------- */
324 
325 /*!< \brief Replaces possible singleton RR type in changeset. */
singleton_replaced(changeset_t * changeset,const knot_rrset_t * rr)326 static bool singleton_replaced(changeset_t *changeset,
327                                const knot_rrset_t *rr)
328 {
329 	if (!should_replace(rr)) {
330 		return false;
331 	}
332 
333 	zone_node_t *n = zone_contents_find_node_for_rr(changeset->add, rr);
334 	if (n == NULL) {
335 		return false;
336 	}
337 
338 	knot_rdataset_t *rrs = node_rdataset(n, rr->type);
339 	if (rrs == NULL) {
340 		return false;
341 	}
342 
343 	// Replace singleton RR.
344 	knot_rdataset_clear(rrs, NULL);
345 	node_remove_rdataset(n, rr->type);
346 	node_add_rrset(n, rr, NULL);
347 
348 	return true;
349 }
350 
351 /*!< \brief Adds RR into add section of changeset if it is deemed worthy. */
add_rr_to_chgset(const knot_rrset_t * rr,zone_update_t * update)352 static int add_rr_to_chgset(const knot_rrset_t *rr,
353                             zone_update_t *update)
354 {
355 	if (singleton_replaced(&update->change, rr)) {
356 		return KNOT_EOK;
357 	}
358 
359 	return zone_update_add(update, rr);
360 }
361 
362 /* ------------------------ RR processing logic ----------------------------- */
363 
364 /* --------------------------- RR additions --------------------------------- */
365 
366 /*!< \brief Processes CNAME addition (replace or ignore) */
process_add_cname(const zone_node_t * node,const knot_rrset_t * rr,zone_update_t * update)367 static int process_add_cname(const zone_node_t *node,
368                              const knot_rrset_t *rr,
369                              zone_update_t *update)
370 {
371 	knot_rrset_t cname = node_rrset(node, KNOT_RRTYPE_CNAME);
372 	if (!knot_rrset_empty(&cname)) {
373 		// If they are identical, ignore.
374 		if (knot_rrset_equal(&cname, rr, true)) {
375 			return KNOT_EOK;
376 		}
377 
378 		int ret = zone_update_remove(update, &cname);
379 		if (ret != KNOT_EOK) {
380 			return ret;
381 		}
382 
383 		return add_rr_to_chgset(rr, update);
384 	} else if (!node_empty(node)) {
385 		// Other occupied node => ignore.
386 		return KNOT_EOK;
387 	} else {
388 		// Can add.
389 		return add_rr_to_chgset(rr, update);
390 	}
391 }
392 
393 /*!< \brief Processes NSEC3PARAM addition (ignore when not removed, or non-apex) */
process_add_nsec3param(const zone_node_t * node,const knot_rrset_t * rr,zone_update_t * update)394 static int process_add_nsec3param(const zone_node_t *node,
395                                   const knot_rrset_t *rr,
396                                   zone_update_t *update)
397 {
398 	if (node == NULL || !node_rrtype_exists(node, KNOT_RRTYPE_SOA)) {
399 		// Ignore non-apex additions
400 		char *owner = knot_dname_to_str_alloc(rr->owner);
401 		log_warning("DDNS, refusing to add NSEC3PARAM to non-apex "
402 		            "node '%s'", owner);
403 		free(owner);
404 		return KNOT_EDENIED;
405 	}
406 	knot_rrset_t param = node_rrset(node, KNOT_RRTYPE_NSEC3PARAM);
407 	if (knot_rrset_empty(&param)) {
408 		return add_rr_to_chgset(rr, update);
409 	}
410 
411 	char *owner = knot_dname_to_str_alloc(rr->owner);
412 	log_warning("DDNS, refusing to add second NSEC3PARAM to node '%s'", owner);
413 	free(owner);
414 
415 	return KNOT_EOK;
416 }
417 
418 /*!
419  * \brief Processes SOA addition (ignore when non-apex), lower serials
420  *        dropped before.
421  */
process_add_soa(const zone_node_t * node,const knot_rrset_t * rr,zone_update_t * update)422 static int process_add_soa(const zone_node_t *node,
423                            const knot_rrset_t *rr,
424                            zone_update_t *update)
425 {
426 	if (node == NULL || !node_rrtype_exists(node, KNOT_RRTYPE_SOA)) {
427 		// Adding SOA to non-apex node, ignore.
428 		return KNOT_EOK;
429 	}
430 
431 	// Get current SOA RR.
432 	knot_rrset_t removed = node_rrset(node, KNOT_RRTYPE_SOA);
433 	if (knot_rrset_equal(&removed, rr, true)) {
434 		// If they are identical, ignore.
435 		return KNOT_EOK;
436 	}
437 
438 	return add_rr_to_chgset(rr, update);
439 }
440 
441 /*!< \brief Adds normal RR, ignores when CNAME exists in node. */
process_add_normal(const zone_node_t * node,const knot_rrset_t * rr,zone_update_t * update)442 static int process_add_normal(const zone_node_t *node,
443                               const knot_rrset_t *rr,
444                               zone_update_t *update)
445 {
446 	if (adding_to_cname(rr->owner, node)) {
447 		// Adding RR to CNAME node, ignore.
448 		return KNOT_EOK;
449 	}
450 
451 	if (node && node_contains_rr(node, rr)) {
452 		// Adding existing RR, ignore.
453 		return KNOT_EOK;
454 	}
455 
456 	return add_rr_to_chgset(rr, update);
457 }
458 
459 /*!< \brief Decides what to do with RR addition. */
process_add(const knot_rrset_t * rr,const zone_node_t * node,zone_update_t * update)460 static int process_add(const knot_rrset_t *rr,
461                        const zone_node_t *node,
462                        zone_update_t *update)
463 {
464 	switch(rr->type) {
465 	case KNOT_RRTYPE_CNAME:
466 		return process_add_cname(node, rr, update);
467 	case KNOT_RRTYPE_SOA:
468 		return process_add_soa(node, rr, update);
469 	case KNOT_RRTYPE_NSEC3PARAM:
470 		return process_add_nsec3param(node, rr, update);
471 	default:
472 		return process_add_normal(node, rr, update);
473 	}
474 }
475 
476 /* --------------------------- RR deletions --------------------------------- */
477 
478 /*!< \brief Removes single RR from zone. */
process_rem_rr(const knot_rrset_t * rr,const zone_node_t * node,zone_update_t * update)479 static int process_rem_rr(const knot_rrset_t *rr,
480                           const zone_node_t *node,
481                           zone_update_t *update)
482 {
483 	if (node == NULL) {
484 		// Removing from node that does not exist
485 		return KNOT_EOK;
486 	}
487 
488 	const bool apex_ns = node_rrtype_exists(node, KNOT_RRTYPE_SOA) &&
489 	                     rr->type == KNOT_RRTYPE_NS;
490 	if (apex_ns) {
491 		const knot_rdataset_t *ns_rrs =
492 			node_rdataset(node, KNOT_RRTYPE_NS);
493 		if (ns_rrs == NULL) {
494 			// Zone without apex NS.
495 			return KNOT_EOK;
496 		}
497 		if (ns_rrs->count == 1) {
498 			// Cannot remove last apex NS RR.
499 			return KNOT_EOK;
500 		}
501 	}
502 
503 	knot_rrset_t to_modify = node_rrset(node, rr->type);
504 	if (knot_rrset_empty(&to_modify)) {
505 		// No such RRSet
506 		return KNOT_EOK;
507 	}
508 
509 	knot_rdataset_t *rrs = node_rdataset(node, rr->type);
510 	if (!knot_rdataset_member(rrs, rr->rrs.rdata)) {
511 		// Node does not contain this RR
512 		return KNOT_EOK;
513 	}
514 
515 	knot_rrset_t rr_ttl = *rr;
516 	rr_ttl.ttl = to_modify.ttl;
517 
518 	return zone_update_remove(update, &rr_ttl);
519 }
520 
521 /*!< \brief Removes RRSet from zone. */
process_rem_rrset(const knot_rrset_t * rrset,const zone_node_t * node,zone_update_t * update)522 static int process_rem_rrset(const knot_rrset_t *rrset,
523                              const zone_node_t *node,
524                              zone_update_t *update)
525 {
526 	bool is_apex = node_rrtype_exists(node, KNOT_RRTYPE_SOA);
527 
528 	if (rrset->type == KNOT_RRTYPE_SOA || is_dnssec_protected(rrset->type, is_apex)) {
529 		// Ignore SOA and DNSSEC removals.
530 		return KNOT_EOK;
531 	}
532 
533 	if (is_apex && rrset->type == KNOT_RRTYPE_NS) {
534 		// Ignore NS apex RRSet removals.
535 		return KNOT_EOK;
536 	}
537 
538 	if (node == NULL) {
539 		// no such node in zone, ignore
540 		return KNOT_EOK;
541 	}
542 
543 	if (!node_rrtype_exists(node, rrset->type)) {
544 		// no such RR, ignore
545 		return KNOT_EOK;
546 	}
547 
548 	knot_rrset_t to_remove = node_rrset(node, rrset->type);
549 	return zone_update_remove(update, &to_remove);
550 }
551 
552 /*!< \brief Removes node from zone. */
process_rem_node(const knot_rrset_t * rr,const zone_node_t * node,zone_update_t * update)553 static int process_rem_node(const knot_rrset_t *rr,
554                             const zone_node_t *node, zone_update_t *update)
555 {
556 	if (node == NULL) {
557 		return KNOT_EOK;
558 	}
559 
560 	// Remove all RRSets from node
561 	size_t rrset_count = node->rrset_count;
562 	for (int i = 0; i < rrset_count; ++i) {
563 		knot_rrset_t rrset = node_rrset_at(node, rrset_count - i - 1);
564 		int ret = process_rem_rrset(&rrset, node, update);
565 		if (ret != KNOT_EOK) {
566 			return ret;
567 		}
568 	}
569 
570 	return KNOT_EOK;
571 }
572 
573 /*!< \brief Decides what to with removal. */
process_remove(const knot_rrset_t * rr,const zone_node_t * node,zone_update_t * update)574 static int process_remove(const knot_rrset_t *rr,
575                           const zone_node_t *node,
576                           zone_update_t *update)
577 {
578 	if (is_rr_removal(rr)) {
579 		return process_rem_rr(rr, node, update);
580 	} else if (is_rrset_removal(rr)) {
581 		return process_rem_rrset(rr, node, update);
582 	} else if (is_node_removal(rr)) {
583 		return process_rem_node(rr, node, update);
584 	} else {
585 		return KNOT_EINVAL;
586 	}
587 }
588 
589 /* --------------------------- validity checks ------------------------------ */
590 
591 /*!< \brief Checks whether addition has not violated DNAME rules. */
sem_check(const knot_rrset_t * rr,const zone_node_t * zone_node,zone_update_t * update)592 static bool sem_check(const knot_rrset_t *rr, const zone_node_t *zone_node,
593                       zone_update_t *update)
594 {
595 	const zone_node_t *added_node = zone_contents_find_node(update->new_cont, rr->owner);
596 
597 	// we do this sem check AFTER adding the RR, so the node must exist
598 	assert(added_node != NULL);
599 
600 	for (const zone_node_t *parent = added_node->parent;
601 	     parent != NULL; parent = parent->parent) {
602 		if (node_rrtype_exists(parent, KNOT_RRTYPE_DNAME)) {
603 			// Parent has DNAME RRSet, refuse update
604 			return false;
605 		}
606 	}
607 
608 	if (rr->type != KNOT_RRTYPE_DNAME || zone_node == NULL) {
609 		return true;
610 	}
611 
612 	// Check that we have not created node with DNAME children.
613 	if (zone_node->children > 0) {
614 		// Updated node has children and DNAME was added, refuse update
615 		return false;
616 	}
617 
618 	return true;
619 }
620 
621 /*!< \brief Checks whether we can accept this RR. */
check_update(const knot_rrset_t * rrset,const knot_pkt_t * query,uint16_t * rcode)622 static int check_update(const knot_rrset_t *rrset, const knot_pkt_t *query,
623                         uint16_t *rcode)
624 {
625 	/* Accept both subdomain and dname match. */
626 	const knot_dname_t *owner = rrset->owner;
627 	const knot_dname_t *qname = knot_pkt_qname(query);
628 	const int in_bailiwick = knot_dname_in_bailiwick(owner, qname);
629 	if (in_bailiwick < 0) {
630 		*rcode = KNOT_RCODE_NOTZONE;
631 		return KNOT_EOUTOFZONE;
632 	}
633 	const bool is_apex = in_bailiwick == 0;
634 
635 	if (is_dnssec_protected(rrset->type, is_apex)) {
636 		*rcode = KNOT_RCODE_REFUSED;
637 		log_warning("DDNS, refusing to update DNSSEC-related record");
638 		return KNOT_EDENIED;
639 	}
640 
641 	if (rrset->rclass == knot_pkt_qclass(query)) {
642 		if (knot_rrtype_is_metatype(rrset->type)) {
643 			*rcode = KNOT_RCODE_FORMERR;
644 			return KNOT_EMALF;
645 		}
646 	} else if (rrset->rclass == KNOT_CLASS_ANY) {
647 		if (!rrset_empty(rrset) ||
648 		    (knot_rrtype_is_metatype(rrset->type) &&
649 		     rrset->type != KNOT_RRTYPE_ANY)) {
650 			*rcode = KNOT_RCODE_FORMERR;
651 			return KNOT_EMALF;
652 		}
653 	} else if (rrset->rclass == KNOT_CLASS_NONE) {
654 		if (rrset->ttl != 0 || knot_rrtype_is_metatype(rrset->type)) {
655 			*rcode = KNOT_RCODE_FORMERR;
656 			return KNOT_EMALF;
657 		}
658 	} else {
659 		*rcode = KNOT_RCODE_FORMERR;
660 		return KNOT_EMALF;
661 	}
662 
663 	return KNOT_EOK;
664 }
665 
666 /*!< \brief Checks RR and decides what to do with it. */
process_rr(const knot_rrset_t * rr,zone_update_t * update)667 static int process_rr(const knot_rrset_t *rr, zone_update_t *update)
668 {
669 	const zone_node_t *node = zone_update_get_node(update, rr->owner);
670 
671 	if (is_addition(rr)) {
672 		int ret = process_add(rr, node, update);
673 		if (ret == KNOT_EOK) {
674 			if (!sem_check(rr, node, update)) {
675 				return KNOT_EDENIED;
676 			}
677 		}
678 		return ret;
679 	} else if (is_removal(rr)) {
680 		return process_remove(rr, node, update);
681 	} else {
682 		return KNOT_EMALF;
683 	}
684 }
685 
686 /*!< \brief Maps Knot return code to RCODE. */
ret_to_rcode(int ret)687 static uint16_t ret_to_rcode(int ret)
688 {
689 	if (ret == KNOT_EMALF) {
690 		return KNOT_RCODE_FORMERR;
691 	} else if (ret == KNOT_EDENIED) {
692 		return KNOT_RCODE_REFUSED;
693 	} else {
694 		return KNOT_RCODE_SERVFAIL;
695 	}
696 }
697 
698 /* ---------------------------------- API ----------------------------------- */
699 
ddns_process_prereqs(const knot_pkt_t * query,zone_update_t * update,uint16_t * rcode)700 int ddns_process_prereqs(const knot_pkt_t *query, zone_update_t *update,
701                          uint16_t *rcode)
702 {
703 	if (query == NULL || rcode == NULL || update == NULL) {
704 		return KNOT_EINVAL;
705 	}
706 
707 	int ret = KNOT_EOK;
708 	list_t rrset_list; // List used to store merged RRSets
709 	init_list(&rrset_list);
710 
711 	const knot_pktsection_t *answer = knot_pkt_section(query, KNOT_ANSWER);
712 	const knot_rrset_t *answer_rr = knot_pkt_rr(answer, 0);
713 	for (int i = 0; i < answer->count; ++i) {
714 		// Check what can be checked, store full RRs into list
715 		ret = process_prereq(&answer_rr[i], knot_pkt_qclass(query),
716 		                     update, rcode, &rrset_list);
717 		if (ret != KNOT_EOK) {
718 			rrset_list_clear(&rrset_list);
719 			return ret;
720 		}
721 	}
722 
723 	// Check stored RRSets
724 	ret = check_stored_rrsets(&rrset_list, update, rcode);
725 	rrset_list_clear(&rrset_list);
726 	return ret;
727 }
728 
ddns_process_update(const zone_t * zone,const knot_pkt_t * query,zone_update_t * update,uint16_t * rcode)729 int ddns_process_update(const zone_t *zone, const knot_pkt_t *query,
730                         zone_update_t *update, uint16_t *rcode)
731 {
732 	if (zone == NULL || query == NULL || update == NULL || rcode == NULL) {
733 		if (rcode) {
734 			*rcode = ret_to_rcode(KNOT_EINVAL);
735 		}
736 		return KNOT_EINVAL;
737 	}
738 
739 	uint32_t sn_old = knot_soa_serial(zone_update_from(update)->rdata);
740 
741 	// Process all RRs in the authority section.
742 	const knot_pktsection_t *authority = knot_pkt_section(query, KNOT_AUTHORITY);
743 	const knot_rrset_t *authority_rr = knot_pkt_rr(authority, 0);
744 	for (uint16_t i = 0; i < authority->count; ++i) {
745 		const knot_rrset_t *rr = &authority_rr[i];
746 		// Check if RR is correct.
747 		int ret = check_update(rr, query, rcode);
748 		if (ret != KNOT_EOK) {
749 			assert(*rcode != KNOT_RCODE_NOERROR);
750 			return ret;
751 		}
752 
753 		if (skip_soa(rr, sn_old)) {
754 			continue;
755 		}
756 
757 		ret = process_rr(rr, update);
758 		if (ret != KNOT_EOK) {
759 			*rcode = ret_to_rcode(ret);
760 			return ret;
761 		}
762 	}
763 
764 	*rcode = KNOT_RCODE_NOERROR;
765 	return KNOT_EOK;
766 }
767