ddns.c 21 KB
Newer Older
Daniel Salzman's avatar
Daniel Salzman committed
1
/*  Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

17 18
#include <assert.h>

Daniel Salzman's avatar
Daniel Salzman committed
19
#include "knot/common/log.h"
20 21
#include "knot/updates/ddns.h"
#include "knot/updates/changesets.h"
22
#include "knot/updates/zone-update.h"
23
#include "knot/zone/serial.h"
Daniel Salzman's avatar
Daniel Salzman committed
24
#include "libknot/libknot.h"
25
#include "contrib/ucw/lists.h"
26

27
/* ----------------------------- prereq check ------------------------------- */
28

29
/*!< \brief Clears prereq RRSet list. */
30
static void rrset_list_clear(list_t *l)
31
{
32 33 34 35 36 37 38
	node_t *n, *nxt;
	WALK_LIST_DELSAFE(n, nxt, *l) {
		ptrnode_t *ptr_n = (ptrnode_t *)n;
		knot_rrset_t *rrset = (knot_rrset_t *)ptr_n->d;
		knot_rrset_free(&rrset, NULL);
		free(n);
	};
39 40
}

41
/*!< \brief Adds RR to prereq RRSet list, merges RRs into RRSets. */
42
static int add_rr_to_list(list_t *l, const knot_rrset_t *rr)
43
{
44 45 46 47 48
	node_t *n;
	WALK_LIST(n, *l) {
		ptrnode_t *ptr_n = (ptrnode_t *)n;
		knot_rrset_t *rrset = (knot_rrset_t *)ptr_n->d;
		if (knot_rrset_equal(rr, rrset, KNOT_RRSET_COMPARE_HEADER)) {
49
			return knot_rdataset_merge(&rrset->rrs, &rr->rrs, NULL);
Lubos Slovak's avatar
Lubos Slovak committed
50
		}
51
	};
Lubos Slovak's avatar
Lubos Slovak committed
52

53
	knot_rrset_t *rr_copy = knot_rrset_copy(rr, NULL);
54
	if (rr_copy == NULL) {
Lubos Slovak's avatar
Lubos Slovak committed
55 56
		return KNOT_ENOMEM;
	}
57
	return ptrlist_add(l, rr_copy, NULL) != NULL ? KNOT_EOK : KNOT_ENOMEM;
58 59
}

60
/*!< \brief Checks whether RRSet exists in the zone. */
61 62
static int check_rrset_exists(zone_update_t *update, const knot_rrset_t *rrset,
                              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 69 70
		*rcode = KNOT_RCODE_NXRRSET;
		return KNOT_EPREREQ;
	} else {
71
		knot_rrset_t found = node_rrset(node, rrset->type);
72
		assert(!knot_rrset_empty(&found));
73 74 75
		if (knot_rrset_equal(&found, rrset, KNOT_RRSET_COMPARE_WHOLE)) {
			return KNOT_EOK;
		} else {
76 77 78 79
			*rcode = KNOT_RCODE_NXRRSET;
			return KNOT_EPREREQ;
		}
	}
80 81
}

82
/*!< \brief Checks whether RRSets in the list exist in the zone. */
83
static int check_stored_rrsets(list_t *l, zone_update_t *update,
Jan Kadlec's avatar
Jan Kadlec committed
84
                               uint16_t *rcode)
85 86 87 88 89
{
	node_t *n;
	WALK_LIST(n, *l) {
		ptrnode_t *ptr_n = (ptrnode_t *)n;
		knot_rrset_t *rrset = (knot_rrset_t *)ptr_n->d;
90
		int ret = check_rrset_exists(update, rrset, rcode);
91 92 93 94
		if (ret != KNOT_EOK) {
			return ret;
		}
	};
Jan Kadlec's avatar
Jan Kadlec committed
95

96 97
	return KNOT_EOK;
}
98

99
/*!< \brief Checks whether node of given owner, with given type exists. */
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);
Lubos Slovak's avatar
Lubos Slovak committed
104
	if (node == NULL || !node_rrtype_exists(node, rrset->type)) {
105
		return false;
Lubos Slovak's avatar
Lubos Slovak committed
106
	}
107

108
	return true;
Lubos Slovak's avatar
Lubos Slovak committed
109 110 111
}

/*!< \brief Checks whether RR type exists in the zone. */
112
static int check_type_exist(zone_update_t *update,
Lubos Slovak's avatar
Lubos Slovak committed
113 114 115
                            const knot_rrset_t *rrset, uint16_t *rcode)
{
	assert(rrset->rclass == KNOT_CLASS_ANY);
116
	if (check_type(update, rrset)) {
117 118
		return KNOT_EOK;
	} else {
Lubos Slovak's avatar
Lubos Slovak committed
119
		*rcode = KNOT_RCODE_NXRRSET;
120
		return KNOT_EPREREQ;
121
	}
Lubos Slovak's avatar
Lubos Slovak committed
122 123 124
}

/*!< \brief Checks whether RR type is not in the zone. */
125
static int check_type_not_exist(zone_update_t *update,
Lubos Slovak's avatar
Lubos Slovak committed
126 127 128
                                const knot_rrset_t *rrset, uint16_t *rcode)
{
	assert(rrset->rclass == KNOT_CLASS_NONE);
129
	if (check_type(update, rrset)) {
Lubos Slovak's avatar
Lubos Slovak committed
130 131 132 133 134
		*rcode = KNOT_RCODE_YXRRSET;
		return KNOT_EPREREQ;
	} else {
		return KNOT_EOK;
	}
135 136
}

137
/*!< \brief Checks whether DNAME is in the zone. */
138
static int check_in_use(zone_update_t *update,
Jan Kadlec's avatar
Jan Kadlec committed
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 144
		*rcode = KNOT_RCODE_NXDOMAIN;
		return KNOT_EPREREQ;
145 146
	} else {
		return KNOT_EOK;
147
	}
148 149
}

150
/*!< \brief Checks whether DNAME is not in the zone. */
151
static int check_not_in_use(zone_update_t *update,
Jan Kadlec's avatar
Jan Kadlec committed
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 158 159
	} else {
		*rcode = KNOT_RCODE_YXDOMAIN;
		return KNOT_EPREREQ;
160
	}
161 162
}

Lubos Slovak's avatar
Lubos Slovak committed
163
/*!< \brief Returns true if rrset has 0 data or RDATA of size 0 (we need TTL).*/
164
static bool rrset_empty(const knot_rrset_t *rrset)
165
{
166
	uint16_t rr_count = rrset->rrs.rr_count;
167 168
	if (rr_count == 0) {
		return true;
169
	}
170
	if (rr_count == 1) {
171 172
		const knot_rdata_t *rr = knot_rdataset_at(&rrset->rrs, 0);
		return knot_rdata_rdlen(rr) == 0;
173
	}
174
	return false;
175 176
}

177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
/*< \brief Returns true if DDNS should deny updating DNSSEC-related record. */
static bool is_dnssec_protected(uint16_t type, bool is_apex)
{
	switch (type) {
	case KNOT_RRTYPE_RRSIG:
	case KNOT_RRTYPE_NSEC:
	case KNOT_RRTYPE_NSEC3:
	case KNOT_RRTYPE_CDNSKEY:
	case KNOT_RRTYPE_CDS:
		return true;
	case KNOT_RRTYPE_DNSKEY:
	case KNOT_RRTYPE_NSEC3PARAM:
		return is_apex;
	default:
		return false;
	}
}

195
/*!< \brief Checks prereq for given packet RR. */
Jan Kadlec's avatar
Jan Kadlec committed
196
static int process_prereq(const knot_rrset_t *rrset, uint16_t qclass,
197
                          zone_update_t *update, uint16_t *rcode,
Jan Kadlec's avatar
Jan Kadlec committed
198
                          list_t *rrset_list)
199
{
200
	if (knot_rdata_ttl(knot_rdataset_at(&rrset->rrs, 0)) != 0) {
Jan Kadlec's avatar
Jan Kadlec committed
201
		*rcode = KNOT_RCODE_FORMERR;
202
		return KNOT_EMALF;
203 204
	}

205
	if (!knot_dname_in(update->zone->name, rrset->owner)) {
206
		*rcode = KNOT_RCODE_NOTZONE;
207
		return KNOT_EOUTOFZONE;
208
	}
209

210 211
	if (rrset->rclass == KNOT_CLASS_ANY) {
		if (!rrset_empty(rrset)) {
Jan Kadlec's avatar
Jan Kadlec committed
212
			*rcode = KNOT_RCODE_FORMERR;
213 214
			return KNOT_EMALF;
		}
215
		if (rrset->type == KNOT_RRTYPE_ANY) {
216
			return check_in_use(update, rrset->owner, rcode);
217
		} else {
218
			return check_type_exist(update, rrset, rcode);
219
		}
220 221
	} else if (rrset->rclass == KNOT_CLASS_NONE) {
		if (!rrset_empty(rrset)) {
Jan Kadlec's avatar
Jan Kadlec committed
222
			*rcode = KNOT_RCODE_FORMERR;
223 224
			return KNOT_EMALF;
		}
225
		if (rrset->type == KNOT_RRTYPE_ANY) {
226
			return check_not_in_use(update, rrset->owner, rcode);
227
		} else {
228
			return check_type_not_exist(update, rrset, rcode);
229 230
		}
	} else if (rrset->rclass == qclass) {
231
		// Store RRs for full check into list
Jan Kadlec's avatar
Jan Kadlec committed
232 233 234 235 236
		int ret = add_rr_to_list(rrset_list, rrset);
		if (ret != KNOT_EOK) {
			*rcode = KNOT_RCODE_SERVFAIL;
		}
		return ret;
237
	} else {
Jan Kadlec's avatar
Jan Kadlec committed
238
		*rcode = KNOT_RCODE_FORMERR;
239 240 241 242
		return KNOT_EMALF;
	}
}

243
/* --------------------------- DDNS processing ------------------------------ */
244

245
/* --------------------- true/false helper functions ------------------------ */
246

247
static inline bool is_addition(const knot_rrset_t *rr)
248
{
249 250
	return rr->rclass == KNOT_CLASS_IN;
}
Jan Včelák's avatar
Jan Včelák committed
251

Jan Kadlec's avatar
Jan Kadlec committed
252
static inline bool is_removal(const knot_rrset_t *rr)
253 254
{
	return rr->rclass == KNOT_CLASS_NONE || rr->rclass == KNOT_CLASS_ANY;
255 256
}

257 258 259 260
static inline bool is_rr_removal(const knot_rrset_t *rr)
{
	return rr->rclass == KNOT_CLASS_NONE;
}
261

262
static inline bool is_rrset_removal(const knot_rrset_t *rr)
263
{
264 265
	return rr->rclass == KNOT_CLASS_ANY && rr->type != KNOT_RRTYPE_ANY;
}
266

267 268 269 270
static inline bool is_node_removal(const knot_rrset_t *rr)
{
	return rr->rclass == KNOT_CLASS_ANY && rr->type == KNOT_RRTYPE_ANY;
}
271

272
/*!< \brief Returns true if last addition of certain types is to be replaced. */
273
static bool should_replace(const knot_rrset_t *rrset)
274
{
275 276
	return rrset->type == KNOT_RRTYPE_CNAME ||
	       rrset->type == KNOT_RRTYPE_NSEC3PARAM;
277 278
}

279
/*!< \brief Returns true if node contains given RR in its RRSets. */
280
static bool node_contains_rr(const zone_node_t *node,
281
                             const knot_rrset_t *rr)
282
{
283
	const knot_rdataset_t *zone_rrs = node_rdataset(node, rr->type);
284 285
	if (zone_rrs) {
		assert(rr->rrs.rr_count == 1);
286
		const bool compare_ttls = false;
287 288 289
		return knot_rdataset_member(zone_rrs,
		                            knot_rdataset_at(&rr->rrs, 0),
		                            compare_ttls);
290
	} else {
291
		return false;
292 293 294
	}
}

295
/*!< \brief Returns true if CNAME is in this node. */
296
static bool adding_to_cname(const knot_dname_t *owner,
297
                            const zone_node_t *node)
298
{
299 300 301
	if (node == NULL) {
		// Node did not exist before update.
		return false;
302
	}
Jan Včelák's avatar
Jan Včelák committed
303

304
	knot_rrset_t cname = node_rrset(node, KNOT_RRTYPE_CNAME);
305 306 307 308
	if (knot_rrset_empty(&cname)) {
		// Node did not contain CNAME before update.
		return false;
	}
309

310 311
	// CNAME present
	return true;
312
}
Jan Včelák's avatar
Jan Včelák committed
313

314 315 316
/*!< \brief Used to ignore SOA deletions and SOAs with lower serial than zone. */
static bool skip_soa(const knot_rrset_t *rr, int64_t sn)
{
317 318
	if (rr->type == KNOT_RRTYPE_SOA &&
	    (rr->rclass == KNOT_CLASS_NONE || rr->rclass == KNOT_CLASS_ANY ||
319
	     serial_compare(knot_soa_serial(&rr->rrs), sn) <= 0)) {
320
		return true;
321
	}
Jan Včelák's avatar
Jan Včelák committed
322

323
	return false;
324 325
}

326 327
/* ---------------------- changeset manipulation ---------------------------- */

Jan Kadlec's avatar
Jan Kadlec committed
328 329 330
/*!< \brief Replaces possible singleton RR type in changeset. */
static bool singleton_replaced(changeset_t *changeset,
                               const knot_rrset_t *rr)
Jan Kadlec's avatar
Jan Kadlec committed
331
{
332 333 334
	if (!should_replace(rr)) {
		return false;
	}
335

336 337 338
	zone_node_t *n = zone_contents_find_node_for_rr(changeset->add, rr);
	if (n == NULL) {
		return false;
339
	}
Lubos Slovak's avatar
Lubos Slovak committed
340

341 342 343 344
	knot_rdataset_t *rrs = node_rdataset(n, rr->type);
	if (rrs == NULL) {
		return false;
	}
345

346 347 348
	// Replace singleton RR.
	knot_rdataset_clear(rrs, NULL);
	node_remove_rdataset(n, rr->type);
349
	node_add_rrset(n, rr, NULL);
350

351
	return true;
352
}
353

354
/*!< \brief Adds RR into add section of changeset if it is deemed worthy. */
355
static int add_rr_to_chgset(const knot_rrset_t *rr,
356
                            zone_update_t *update)
357
{
358
	if (singleton_replaced(&update->change, rr)) {
Jan Kadlec's avatar
Jan Kadlec committed
359
		return KNOT_EOK;
360
	}
Jan Včelák's avatar
Jan Včelák committed
361

362
	return zone_update_add(update, rr);
363
}
Jan Včelák's avatar
Jan Včelák committed
364

365
/* ------------------------ RR processing logic ----------------------------- */
Lubos Slovak's avatar
Lubos Slovak committed
366

367
/* --------------------------- RR additions --------------------------------- */
Jan Včelák's avatar
Jan Včelák committed
368

369
/*!< \brief Processes CNAME addition (replace or ignore) */
370
static int process_add_cname(const zone_node_t *node,
Jan Kadlec's avatar
Jan Kadlec committed
371
                             const knot_rrset_t *rr,
372
                             zone_update_t *update)
373
{
374
	knot_rrset_t cname = node_rrset(node, KNOT_RRTYPE_CNAME);
375
	if (!knot_rrset_empty(&cname)) {
376
		// If they are identical, ignore.
Jan Kadlec's avatar
Jan Kadlec committed
377
		if (knot_rrset_equal(&cname, rr, KNOT_RRSET_COMPARE_WHOLE)) {
378
			return KNOT_EOK;
Lubos Slovak's avatar
Lubos Slovak committed
379
		}
380

381
		int ret = zone_update_remove(update, &cname);
382
		if (ret != KNOT_EOK) {
383
			return ret;
384
		}
Jan Včelák's avatar
Jan Včelák committed
385

386
		return add_rr_to_chgset(rr, update);
387
	} else if (!node_empty(node)) {
388
		// Other occupied node => ignore.
Lubos Slovak's avatar
Lubos Slovak committed
389
		return KNOT_EOK;
390
	} else {
391
		// Can add.
392
		return add_rr_to_chgset(rr, update);
Lubos Slovak's avatar
Lubos Slovak committed
393 394
	}
}
Jan Včelák's avatar
Jan Včelák committed
395

396
/*!< \brief Processes NSEC3PARAM addition (ignore when not removed, or non-apex) */
397
static int process_add_nsec3param(const zone_node_t *node,
Jan Kadlec's avatar
Jan Kadlec committed
398
                                  const knot_rrset_t *rr,
399
                                  zone_update_t *update)
400
{
401
	if (node == NULL || !node_rrtype_exists(node, KNOT_RRTYPE_SOA)) {
Jan Kadlec's avatar
Jan Kadlec committed
402
		// Ignore non-apex additions
403
		char *owner = knot_dname_to_str_alloc(rr->owner);
Jan Včelák's avatar
Jan Včelák committed
404 405
		log_warning("DDNS, refusing to add NSEC3PARAM to non-apex "
		            "node '%s'", owner);
406
		free(owner);
407
		return KNOT_EDENIED;
408
	}
409
	knot_rrset_t param = node_rrset(node, KNOT_RRTYPE_NSEC3PARAM);
410
	if (knot_rrset_empty(&param)) {
411
		return add_rr_to_chgset(rr, update);
412
	}
Jan Včelák's avatar
Jan Včelák committed
413

414
	char *owner = knot_dname_to_str_alloc(rr->owner);
Jan Včelák's avatar
Jan Včelák committed
415
	log_warning("DDNS, refusing to add second NSEC3PARAM to node '%s'", owner);
416
	free(owner);
417

418 419 420
	return KNOT_EOK;
}

Lubos Slovak's avatar
Lubos Slovak committed
421
/*!
422 423
 * \brief Processes SOA addition (ignore when non-apex), lower serials
 *        dropped before.
Lubos Slovak's avatar
Lubos Slovak committed
424
 */
425
static int process_add_soa(const zone_node_t *node,
426
                           const knot_rrset_t *rr,
427
                           zone_update_t *update)
428
{
429
	if (node == NULL || !node_rrtype_exists(node, KNOT_RRTYPE_SOA)) {
430
		// Adding SOA to non-apex node, ignore.
Lubos Slovak's avatar
Lubos Slovak committed
431 432
		return KNOT_EOK;
	}
433

434
	// Get current SOA RR.
435
	knot_rrset_t removed = node_rrset(node, KNOT_RRTYPE_SOA);
436
	if (knot_rrset_equal(&removed, rr, KNOT_RRSET_COMPARE_WHOLE)) {
437
		// If they are identical, ignore.
438 439
		return KNOT_EOK;
	}
Jan Včelák's avatar
Jan Včelák committed
440

441
	return add_rr_to_chgset(rr, update);
442
}
443

444
/*!< \brief Adds normal RR, ignores when CNAME exists in node. */
445
static int process_add_normal(const zone_node_t *node,
446
                              const knot_rrset_t *rr,
447
                              zone_update_t *update)
448
{
449
	if (adding_to_cname(rr->owner, node)) {
450 451
		// Adding RR to CNAME node, ignore.
		return KNOT_EOK;
452
	}
453

454
	if (node && node_contains_rr(node, rr)) {
455
		// Adding existing RR, ignore.
456 457 458
		return KNOT_EOK;
	}

459
	return add_rr_to_chgset(rr, update);
460
}
461

462 463
/*!< \brief Decides what to do with RR addition. */
static int process_add(const knot_rrset_t *rr,
464
                       const zone_node_t *node,
465
                       zone_update_t *update)
466 467 468
{
	switch(rr->type) {
	case KNOT_RRTYPE_CNAME:
469
		return process_add_cname(node, rr, update);
470
	case KNOT_RRTYPE_SOA:
471
		return process_add_soa(node, rr, update);
472
	case KNOT_RRTYPE_NSEC3PARAM:
473
		return process_add_nsec3param(node, rr, update);
474
	default:
475
		return process_add_normal(node, rr, update);
476
	}
477
}
478

479
/* --------------------------- RR deletions --------------------------------- */
480

481
/*!< \brief Removes single RR from zone. */
482
static int process_rem_rr(const knot_rrset_t *rr,
483
                          const zone_node_t *node,
484
                          zone_update_t *update)
485
{
486 487 488 489 490
	if (node == NULL) {
		// Removing from node that does not exist
		return KNOT_EOK;
	}

491
	const bool apex_ns = node_rrtype_exists(node, KNOT_RRTYPE_SOA) &&
492
	                     rr->type == KNOT_RRTYPE_NS;
493
	if (apex_ns) {
494 495
		const knot_rdataset_t *ns_rrs =
			node_rdataset(node, KNOT_RRTYPE_NS);
496 497 498 499
		if (ns_rrs == NULL) {
			// Zone without apex NS.
			return KNOT_EOK;
		}
500
		if (ns_rrs->rr_count == 1) {
501
			// Cannot remove last apex NS RR.
502 503
			return KNOT_EOK;
		}
504 505
	}

506
	knot_rrset_t to_modify = node_rrset(node, rr->type);
507
	if (knot_rrset_empty(&to_modify)) {
508
		// No such RRSet
509
		return KNOT_EOK;
510
	}
511

512 513 514
	knot_rdataset_t *rrs = node_rdataset(node, rr->type);
	if (!knot_rdataset_member(rrs, rr->rrs.data, false)) {
		// Node does not contain this RR
515
		return KNOT_EOK;
516
	}
517

518
	return zone_update_remove(update, rr);
519 520
}

521
/*!< \brief Removes RRSet from zone. */
Jan Kadlec's avatar
Jan Kadlec committed
522
static int process_rem_rrset(const knot_rrset_t *rrset,
523
                             const zone_node_t *node,
524
                             zone_update_t *update)
525
{
526 527 528
	bool is_apex = node_rrtype_exists(node, KNOT_RRTYPE_SOA);

	if (rrset->type == KNOT_RRTYPE_SOA || is_dnssec_protected(rrset->type, is_apex)) {
529
		// Ignore SOA and DNSSEC removals.
Lubos Slovak's avatar
Lubos Slovak committed
530 531
		return KNOT_EOK;
	}
Jan Včelák's avatar
Jan Včelák committed
532

533
	if (is_apex && rrset->type == KNOT_RRTYPE_NS) {
534
		// Ignore NS apex RRSet removals.
Jan Kadlec's avatar
Jan Kadlec committed
535
		return KNOT_EOK;
536
	}
Lubos Slovak's avatar
Lubos Slovak committed
537

538
	if (node == NULL) {
539
		// no such node in zone, ignore
540 541
		return KNOT_EOK;
	}
Jan Včelák's avatar
Jan Včelák committed
542

543
	if (!node_rrtype_exists(node, rrset->type)) {
544
		// no such RR, ignore
Jan Kadlec's avatar
Jan Kadlec committed
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);
Jan Kadlec's avatar
Jan Kadlec committed
550 551
}

552
/*!< \brief Removes node from zone. */
553
static int process_rem_node(const knot_rrset_t *rr,
554
                            const zone_node_t *node, zone_update_t *update)
Jan Kadlec's avatar
Jan Kadlec committed
555
{
556
	if (node == NULL) {
557 558 559
		return KNOT_EOK;
	}

560
	zone_node_t *node_copy = node_shallow_copy(node, NULL);
Dominik Taborsky's avatar
Dominik Taborsky committed
561 562 563
	if (node_copy == NULL) {
		return KNOT_ENOMEM;
	}
564

565
	// Remove all RRSets from node
566 567 568 569
	size_t rrset_count = node_copy->rrset_count;
	for (int i = 0; i < rrset_count; ++i) {
		knot_rrset_t rrset = node_rrset_at(node_copy, rrset_count - i - 1);
		int ret = process_rem_rrset(&rrset, node_copy, update);
570
		if (ret != KNOT_EOK) {
Dominik Taborsky's avatar
Dominik Taborsky committed
571
			node_free(&node_copy, NULL);
572 573
			return ret;
		}
574 575
	}

576 577
	node_free(&node_copy, NULL);

Jan Kadlec's avatar
Jan Kadlec committed
578 579
	return KNOT_EOK;
}
Jan Včelák's avatar
Jan Včelák committed
580

581
/*!< \brief Decides what to with removal. */
Jan Kadlec's avatar
Jan Kadlec committed
582
static int process_remove(const knot_rrset_t *rr,
583
                          const zone_node_t *node,
584
                          zone_update_t *update)
Jan Kadlec's avatar
Jan Kadlec committed
585 586
{
	if (is_rr_removal(rr)) {
587
		return process_rem_rr(rr, node, update);
Jan Kadlec's avatar
Jan Kadlec committed
588
	} else if (is_rrset_removal(rr)) {
589
		return process_rem_rrset(rr, node, update);
Jan Kadlec's avatar
Jan Kadlec committed
590
	} else if (is_node_removal(rr)) {
591
		return process_rem_node(rr, node, update);
Jan Kadlec's avatar
Jan Kadlec committed
592 593
	} else {
		return KNOT_EINVAL;
594
	}
Jan Kadlec's avatar
Jan Kadlec committed
595
}
Jan Včelák's avatar
Jan Včelák committed
596

597
/* --------------------------- validity checks ------------------------------ */
598

599
/*!< \brief Checks whether addition has not violated DNAME rules. */
600 601
static bool sem_check(const knot_rrset_t *rr, const zone_node_t *zone_node,
                      zone_update_t *update)
602
{
603
	// Check that we have not added DNAME child
604
	const knot_dname_t *parent_dname = knot_wire_next_label(rr->owner, NULL);
605
	const zone_node_t *parent = zone_update_get_node(update, parent_dname);
606 607
	if (parent == NULL) {
		return true;
608
	}
Jan Včelák's avatar
Jan Včelák committed
609

610
	if (node_rrtype_exists(parent, KNOT_RRTYPE_DNAME)) {
611
		// Parent has DNAME RRSet, refuse update
612
		return false;
Lubos Slovak's avatar
Lubos Slovak committed
613
	}
614

615
	if (rr->type != KNOT_RRTYPE_DNAME || zone_node == NULL) {
616
		return true;
617
	}
Jan Včelák's avatar
Jan Včelák committed
618

619
	// Check that we have not created node with DNAME children.
620
	if (zone_node->children > 0) {
621 622
		// Updated node has children and DNAME was added, refuse update
		return false;
623
	}
624

625
	return true;
626 627
}

628
/*!< \brief Checks whether we can accept this RR. */
629 630
static int check_update(const knot_rrset_t *rrset, const knot_pkt_t *query,
                        uint16_t *rcode)
631
{
632 633 634
	/* Accept both subdomain and dname match. */
	const knot_dname_t *owner = rrset->owner;
	const knot_dname_t *qname = knot_pkt_qname(query);
635
	const bool is_sub = knot_dname_is_sub(owner, qname);
636 637
	const bool is_apex = knot_dname_is_equal(owner, qname);
	if (!is_sub && !is_apex) {
638 639
		*rcode = KNOT_RCODE_NOTZONE;
		return KNOT_EOUTOFZONE;
Lubos Slovak's avatar
Lubos Slovak committed
640 641
	}

642
	if (is_dnssec_protected(rrset->type, is_apex)) {
643
		*rcode = KNOT_RCODE_REFUSED;
Jan Včelák's avatar
Jan Včelák committed
644
		log_warning("DDNS, refusing to update DNSSEC-related record");
645 646
		return KNOT_EDENIED;
	}
647

648 649 650 651
	if (rrset->rclass == knot_pkt_qclass(query)) {
		if (knot_rrtype_is_metatype(rrset->type)) {
			*rcode = KNOT_RCODE_FORMERR;
			return KNOT_EMALF;
652
		}
653
	} else if (rrset->rclass == KNOT_CLASS_ANY) {
654 655 656
		if (!rrset_empty(rrset) ||
		    (knot_rrtype_is_metatype(rrset->type) &&
		     rrset->type != KNOT_RRTYPE_ANY)) {
657 658
			*rcode = KNOT_RCODE_FORMERR;
			return KNOT_EMALF;
659
		}
660
	} else if (rrset->rclass == KNOT_CLASS_NONE) {
661 662
		if ((knot_rdata_ttl(knot_rdataset_at(&rrset->rrs, 0)) != 0) ||
		    knot_rrtype_is_metatype(rrset->type)) {
663 664
			*rcode = KNOT_RCODE_FORMERR;
			return KNOT_EMALF;
665
		}
666 667 668
	} else {
		*rcode = KNOT_RCODE_FORMERR;
		return KNOT_EMALF;
669
	}
Jan Včelák's avatar
Jan Včelák committed
670

671 672 673
	return KNOT_EOK;
}

674
/*!< \brief Checks RR and decides what to do with it. */
675
static int process_rr(const knot_rrset_t *rr, zone_update_t *update)
676
{
677
	const zone_node_t *node = zone_update_get_node(update, rr->owner);
678

679
	if (is_addition(rr)) {
680
		int ret = process_add(rr, node, update);
681
		if (ret == KNOT_EOK) {
682
			if (!sem_check(rr, node, update)) {
683 684 685 686
				return KNOT_EDENIED;
			}
		}
		return ret;
Jan Kadlec's avatar
Jan Kadlec committed
687
	} else if (is_removal(rr)) {
688
		return process_remove(rr, node, update);
689
	} else {
690
		return KNOT_EMALF;
691 692 693
	}
}

694
/*!< \brief Maps Knot return code to RCODE. */
695
static uint16_t ret_to_rcode(int ret)
696
{
697 698
	if (ret == KNOT_EMALF) {
		return KNOT_RCODE_FORMERR;
699
	} else if (ret == KNOT_EDENIED || ret == KNOT_ETTL) {
700 701 702 703 704
		return KNOT_RCODE_REFUSED;
	} else {
		return KNOT_RCODE_SERVFAIL;
	}
}
705

706
/* ---------------------------------- API ----------------------------------- */
707

708
int ddns_process_prereqs(const knot_pkt_t *query, zone_update_t *update,
709
                         uint16_t *rcode)
710
{
711
	if (query == NULL || rcode == NULL || update == NULL) {
712 713
		return KNOT_EINVAL;
	}
714

715 716 717 718 719
	int ret = KNOT_EOK;
	list_t rrset_list; // List used to store merged RRSets
	init_list(&rrset_list);

	const knot_pktsection_t *answer = knot_pkt_section(query, KNOT_ANSWER);
720
	const knot_rrset_t *answer_rr = knot_pkt_rr(answer, 0);
721 722
	for (int i = 0; i < answer->count; ++i) {
		// Check what can be checked, store full RRs into list
723
		ret = process_prereq(&answer_rr[i], knot_pkt_qclass(query),
724
		                     update, rcode, &rrset_list);
725 726 727
		if (ret != KNOT_EOK) {
			rrset_list_clear(&rrset_list);
			return ret;
728 729
		}
	}
730 731

	// Check stored RRSets
732
	ret = check_stored_rrsets(&rrset_list, update, rcode);
733 734
	rrset_list_clear(&rrset_list);
	return ret;
735 736
}

737
int ddns_process_update(const zone_t *zone, const knot_pkt_t *query,
738
                        zone_update_t *update, uint16_t *rcode)
739
{
740
	if (zone == NULL || query == NULL || update == NULL || rcode == NULL) {
741 742 743
		if (rcode) {
			*rcode = ret_to_rcode(KNOT_EINVAL);
		}
744 745
		return KNOT_EINVAL;
	}
746

747
	uint32_t sn_old = knot_soa_serial(zone_update_from(update));
748

749
	// Process all RRs in the authority section.
750 751
	const knot_pktsection_t *authority = knot_pkt_section(query, KNOT_AUTHORITY);
	const knot_rrset_t *authority_rr = knot_pkt_rr(authority, 0);
752
	for (uint16_t i = 0; i < authority->count; ++i) {
753
		const knot_rrset_t *rr = &authority_rr[i];
754
		// Check if RR is correct.
755
		int ret = check_update(rr, query, rcode);
756
		if (ret != KNOT_EOK) {
Jan Kadlec's avatar
Jan Kadlec committed
757
			assert(*rcode != KNOT_RCODE_NOERROR);
758 759 760
			return ret;
		}

761
		if (skip_soa(rr, sn_old)) {
762 763 764
			continue;
		}

765
		ret = process_rr(rr, update);
766
		if (ret != KNOT_EOK) {
767
			*rcode = ret_to_rcode(ret);
768 769 770 771
			return ret;
		}
	}

Jan Kadlec's avatar
Jan Kadlec committed
772
	*rcode = KNOT_RCODE_NOERROR;