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(¶m)) {
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