update.c 93.2 KB
Newer Older
Andreas Gustafsson's avatar
Andreas Gustafsson committed
1
/*
Mark Andrews's avatar
Mark Andrews committed
2
 * Copyright (C) 2004-2014  Internet Systems Consortium, Inc. ("ISC")
Mark Andrews's avatar
Mark Andrews committed
3
 * Copyright (C) 1999-2003  Internet Software Consortium.
4
 *
Automatic Updater's avatar
Automatic Updater committed
5
 * Permission to use, copy, modify, and/or distribute this software for any
Andreas Gustafsson's avatar
Andreas Gustafsson committed
6 7
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
8
 *
Mark Andrews's avatar
Mark Andrews committed
9 10 11 12 13 14 15
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
Andreas Gustafsson's avatar
Andreas Gustafsson committed
16 17
 */

18
/* $Id: update.c,v 1.199 2011/12/22 07:32:40 each Exp $ */
David Lawrence's avatar
David Lawrence committed
19

Andreas Gustafsson's avatar
Andreas Gustafsson committed
20 21
#include <config.h>

22
#include <isc/netaddr.h>
Andreas Gustafsson's avatar
Andreas Gustafsson committed
23
#include <isc/print.h>
24
#include <isc/serial.h>
25
#include <isc/stats.h>
26
#include <isc/string.h>
27
#include <isc/taskpool.h>
Andreas Gustafsson's avatar
Andreas Gustafsson committed
28
#include <isc/util.h>
Andreas Gustafsson's avatar
Andreas Gustafsson committed
29

Bob Halley's avatar
Bob Halley committed
30 31
#include <dns/db.h>
#include <dns/dbiterator.h>
32
#include <dns/diff.h>
Bob Halley's avatar
Bob Halley committed
33 34
#include <dns/dnssec.h>
#include <dns/events.h>
Brian Wellington's avatar
Brian Wellington committed
35
#include <dns/fixedname.h>
Bob Halley's avatar
Bob Halley committed
36
#include <dns/journal.h>
37
#include <dns/keyvalues.h>
Bob Halley's avatar
Bob Halley committed
38
#include <dns/message.h>
39
#include <dns/nsec.h>
40
#include <dns/nsec3.h>
41
#include <dns/private.h>
42
#include <dns/rdataclass.h>
Andreas Gustafsson's avatar
Andreas Gustafsson committed
43 44
#include <dns/rdataset.h>
#include <dns/rdatasetiter.h>
45
#include <dns/rdatastruct.h>
46
#include <dns/rdatatype.h>
47
#include <dns/soa.h>
48
#include <dns/ssu.h>
49
#include <dns/tsig.h>
50
#include <dns/update.h>
Andreas Gustafsson's avatar
Andreas Gustafsson committed
51
#include <dns/view.h>
Bob Halley's avatar
Bob Halley committed
52 53
#include <dns/zone.h>
#include <dns/zt.h>
Andreas Gustafsson's avatar
Andreas Gustafsson committed
54 55

#include <named/client.h>
56
#include <named/log.h>
57
#include <named/server.h>
58
#include <named/update.h>
Andreas Gustafsson's avatar
Andreas Gustafsson committed
59

60 61
/*! \file
 * \brief
Andreas Gustafsson's avatar
Andreas Gustafsson committed
62 63
 * This module implements dynamic update as in RFC2136.
 */
64

Andreas Gustafsson's avatar
Andreas Gustafsson committed
65
/*
66 67 68
 *  XXX TODO:
 * - document strict minimality
 */
Andreas Gustafsson's avatar
Andreas Gustafsson committed
69

70
/**************************************************************************/
71

72
/*%
73
 * Log level for tracing dynamic update protocol requests.
74
 */
75
#define LOGLEVEL_PROTOCOL	ISC_LOG_INFO
76

77
/*%
78
 * Log level for low-level debug tracing.
79
 */
80
#define LOGLEVEL_DEBUG		ISC_LOG_DEBUG(8)
81

82
/*%
83 84 85 86
 * Check an operation for failure.  These macros all assume that
 * the function using them has a 'result' variable and a 'failure'
 * label.
 */
87
#define CHECK(op) \
88 89
	do { result = (op); \
		if (result != ISC_R_SUCCESS) goto failure; \
90
	} while (0)
91

92
/*%
93
 * Fail unconditionally with result 'code', which must not
94
 * be ISC_R_SUCCESS.  The reason for failure presumably has
95
 * been logged already.
96 97 98
 *
 * The test against ISC_R_SUCCESS is there to keep the Solaris compiler
 * from complaining about "end-of-loop code not reached".
99 100 101 102 103
 */

#define FAIL(code) \
	do {							\
		result = (code);				\
104
		if (result != ISC_R_SUCCESS) goto failure;	\
105 106
	} while (0)

107
/*%
108
 * Fail unconditionally and log as a client error.
109 110
 * The test against ISC_R_SUCCESS is there to keep the Solaris compiler
 * from complaining about "end-of-loop code not reached".
111
 */
112 113
#define FAILC(code, msg) \
	do {							\
114
		const char *_what = "failed";			\
115
		result = (code);				\
116 117 118 119 120 121 122
		switch (result) {				\
		case DNS_R_NXDOMAIN:				\
		case DNS_R_YXDOMAIN:				\
		case DNS_R_YXRRSET:				\
		case DNS_R_NXRRSET:				\
			_what = "unsuccessful";			\
		}						\
123 124 125
		update_log(client, zone, LOGLEVEL_PROTOCOL,	\
			   "update %s: %s (%s)", _what,		\
			   msg, isc_result_totext(result));	\
126
		if (result != ISC_R_SUCCESS) goto failure;	\
127
	} while (0)
128 129 130 131 132
#define PREREQFAILC(code, msg) \
	do {							\
		inc_stats(zone, dns_nsstatscounter_updatebadprereq); \
		FAILC(code, msg);				\
	} while (0)
133

134 135
#define FAILN(code, name, msg) \
	do {								\
136
		const char *_what = "failed";				\
137
		result = (code);					\
138 139 140 141 142 143 144
		switch (result) {					\
		case DNS_R_NXDOMAIN:					\
		case DNS_R_YXDOMAIN:					\
		case DNS_R_YXRRSET:					\
		case DNS_R_NXRRSET:					\
			_what = "unsuccessful";				\
		}							\
145 146 147
		if (isc_log_wouldlog(ns_g_lctx, LOGLEVEL_PROTOCOL)) {	\
			char _nbuf[DNS_NAME_FORMATSIZE];		\
			dns_name_format(name, _nbuf, sizeof(_nbuf));	\
148
			update_log(client, zone, LOGLEVEL_PROTOCOL,	\
149
				   "update %s: %s: %s (%s)", _what, _nbuf, \
150 151 152 153
				   msg, isc_result_totext(result));	\
		}							\
		if (result != ISC_R_SUCCESS) goto failure;		\
	} while (0)
154 155 156 157 158
#define PREREQFAILN(code, name, msg) \
	do {								\
		inc_stats(zone, dns_nsstatscounter_updatebadprereq); \
		FAILN(code, name, msg);					\
	} while (0)
159 160 161

#define FAILNT(code, name, type, msg) \
	do {								\
162
		const char *_what = "failed";				\
163
		result = (code);					\
164 165 166 167 168 169 170
		switch (result) {					\
		case DNS_R_NXDOMAIN:					\
		case DNS_R_YXDOMAIN:					\
		case DNS_R_YXRRSET:					\
		case DNS_R_NXRRSET:					\
			_what = "unsuccessful";				\
		}							\
171 172 173 174 175
		if (isc_log_wouldlog(ns_g_lctx, LOGLEVEL_PROTOCOL)) {	\
			char _nbuf[DNS_NAME_FORMATSIZE];		\
			char _tbuf[DNS_RDATATYPE_FORMATSIZE];		\
			dns_name_format(name, _nbuf, sizeof(_nbuf));	\
			dns_rdatatype_format(type, _tbuf, sizeof(_tbuf)); \
176
			update_log(client, zone, LOGLEVEL_PROTOCOL,	\
177 178
				   "update %s: %s/%s: %s (%s)",		\
				   _what, _nbuf, _tbuf, msg,		\
179 180 181 182
				   isc_result_totext(result));		\
		}							\
		if (result != ISC_R_SUCCESS) goto failure;		\
	} while (0)
183 184 185 186 187 188
#define PREREQFAILNT(code, name, type, msg)				\
	do {								\
		inc_stats(zone, dns_nsstatscounter_updatebadprereq); \
		FAILNT(code, name, type, msg);				\
	} while (0)

189
/*%
190
 * Fail unconditionally and log as a server error.
191 192
 * The test against ISC_R_SUCCESS is there to keep the Solaris compiler
 * from complaining about "end-of-loop code not reached".
193 194 195
 */
#define FAILS(code, msg) \
	do {							\
196
		result = (code);				\
197
		update_log(client, zone, LOGLEVEL_PROTOCOL,	\
198 199
			   "error: %s: %s",			\
			   msg, isc_result_totext(result));	\
200
		if (result != ISC_R_SUCCESS) goto failure;	\
201
	} while (0)
202

203
/*
Francis Dupont's avatar
Francis Dupont committed
204
 * Return TRUE if NS_CLIENTATTR_TCP is set in the attributes other FALSE.
205 206 207
 */
#define TCPCLIENT(client) (((client)->attributes & NS_CLIENTATTR_TCP) != 0)

208 209
/**************************************************************************/

Andreas Gustafsson's avatar
Andreas Gustafsson committed
210 211 212 213
typedef struct rr rr_t;

struct rr {
	/* dns_name_t name; */
214 215
	isc_uint32_t		ttl;
	dns_rdata_t		rdata;
216 217 218 219 220 221
};

typedef struct update_event update_event_t;

struct update_event {
	ISC_EVENT_COMMON(update_event_t);
222
	dns_zone_t		*zone;
223
	isc_result_t		result;
224
	dns_message_t		*answer;
Andreas Gustafsson's avatar
Andreas Gustafsson committed
225 226
};

227 228 229 230 231 232 233
/**************************************************************************/
/*
 * Forward declarations.
 */

static void update_action(isc_task_t *task, isc_event_t *event);
static void updatedone_action(isc_task_t *task, isc_event_t *event);
234
static isc_result_t send_forward_event(ns_client_t *client, dns_zone_t *zone);
235
static void forward_done(isc_task_t *task, isc_event_t *event);
236

Andreas Gustafsson's avatar
Andreas Gustafsson committed
237
/**************************************************************************/
238

239 240 241 242
static void
update_log(ns_client_t *client, dns_zone_t *zone,
	   int level, const char *fmt, ...) ISC_FORMAT_PRINTF(4, 5);

243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
static void
update_log(ns_client_t *client, dns_zone_t *zone,
	   int level, const char *fmt, ...)
{
	va_list ap;
	char message[4096];
	char namebuf[DNS_NAME_FORMATSIZE];
	char classbuf[DNS_RDATACLASS_FORMATSIZE];

	if (client == NULL || zone == NULL)
		return;

	if (isc_log_wouldlog(ns_g_lctx, level) == ISC_FALSE)
		return;

	dns_name_format(dns_zone_getorigin(zone), namebuf,
			sizeof(namebuf));
	dns_rdataclass_format(dns_zone_getclass(zone), classbuf,
			      sizeof(classbuf));

	va_start(ap, fmt);
Andreas Gustafsson's avatar
Andreas Gustafsson committed
264
	vsnprintf(message, sizeof(message), fmt, ap);
265 266 267
	va_end(ap);

	ns_client_log(client, NS_LOGCATEGORY_UPDATE, NS_LOGMODULE_UPDATE,
268
		      level, "updating zone '%s/%s': %s",
269 270 271
		      namebuf, classbuf, message);
}

272 273 274 275 276
static void
update_log_cb(void *arg, dns_zone_t *zone, int level, const char *message) {
	update_log(arg, zone, level, "%s", message);
}

277 278 279 280
/*%
 * Increment updated-related statistics counters.
 */
static inline void
281 282
inc_stats(dns_zone_t *zone, isc_statscounter_t counter) {
	isc_stats_increment(ns_g_server->nsstats, counter);
283 284

	if (zone != NULL) {
285
		isc_stats_t *zonestats = dns_zone_getrequeststats(zone);
286
		if (zonestats != NULL)
287
			isc_stats_increment(zonestats, counter);
288 289 290
	}
}

291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
/*%
 * Check if we could have queried for the contents of this zone or
 * if the zone is potentially updateable.
 * If the zone can potentially be updated and the check failed then
 * log a error otherwise we log a informational message.
 */
static isc_result_t
checkqueryacl(ns_client_t *client, dns_acl_t *queryacl, dns_name_t *zonename,
	      dns_acl_t *updateacl, dns_ssutable_t *ssutable)
{
	char namebuf[DNS_NAME_FORMATSIZE];
	char classbuf[DNS_RDATACLASS_FORMATSIZE];
	int level;
	isc_result_t result;

	result = ns_client_checkaclsilent(client, NULL, queryacl, ISC_TRUE);
	if (result != ISC_R_SUCCESS) {
		dns_name_format(zonename, namebuf, sizeof(namebuf));
		dns_rdataclass_format(client->view->rdclass, classbuf,
				      sizeof(classbuf));

		level = (updateacl == NULL && ssutable == NULL) ?
				ISC_LOG_INFO : ISC_LOG_ERROR;

		ns_client_log(client, NS_LOGCATEGORY_UPDATE_SECURITY,
			      NS_LOGMODULE_UPDATE, level,
			      "update '%s/%s' denied due to allow-query",
			      namebuf, classbuf);
	} else if (updateacl == NULL && ssutable == NULL) {
320 321 322 323
		dns_name_format(zonename, namebuf, sizeof(namebuf));
		dns_rdataclass_format(client->view->rdclass, classbuf,
				      sizeof(classbuf));

324 325 326 327 328 329 330 331
		result = DNS_R_REFUSED;
		ns_client_log(client, NS_LOGCATEGORY_UPDATE_SECURITY,
			      NS_LOGMODULE_UPDATE, ISC_LOG_INFO,
			      "update '%s/%s' denied", namebuf, classbuf);
	}
	return (result);
}

332 333 334 335 336 337 338 339 340 341 342 343 344 345
/*%
 * Override the default acl logging when checking whether a client
 * can update the zone or whether we can forward the request to the
 * master based on IP address.
 *
 * 'message' contains the type of operation that is being attempted.
 * 'slave' indicates if this is a slave zone.  If 'acl' is NULL then
 * log at debug=3.
 * If the zone has no access controls configured ('acl' == NULL &&
 * 'has_ssutable == ISC_FALS) log the attempt at info, otherwise
 * at error.
 *
 * If the request was signed log that we received it.
 */
346 347
static isc_result_t
checkupdateacl(ns_client_t *client, dns_acl_t *acl, const char *message,
348 349
	       dns_name_t *zonename, isc_boolean_t slave,
	       isc_boolean_t has_ssutable)
350 351 352 353 354 355 356
{
	char namebuf[DNS_NAME_FORMATSIZE];
	char classbuf[DNS_RDATACLASS_FORMATSIZE];
	int level = ISC_LOG_ERROR;
	const char *msg = "denied";
	isc_result_t result;

357 358 359 360
	if (slave && acl == NULL) {
		result = DNS_R_NOTIMP;
		level = ISC_LOG_DEBUG(3);
		msg = "disabled";
361
	} else {
362
		result = ns_client_checkaclsilent(client, NULL, acl, ISC_FALSE);
363 364 365 366 367 368
		if (result == ISC_R_SUCCESS) {
			level = ISC_LOG_DEBUG(3);
			msg = "approved";
		} else if (acl == NULL && !has_ssutable) {
			level = ISC_LOG_INFO;
		}
369 370
	}

371 372 373 374 375 376 377
	if (client->signer != NULL) {
		dns_name_format(client->signer, namebuf, sizeof(namebuf));
		ns_client_log(client, NS_LOGCATEGORY_UPDATE_SECURITY,
			      NS_LOGMODULE_UPDATE, ISC_LOG_INFO,
			      "signer \"%s\" %s", namebuf, msg);
	}

378 379 380 381 382
	dns_name_format(zonename, namebuf, sizeof(namebuf));
	dns_rdataclass_format(client->view->rdclass, classbuf,
			      sizeof(classbuf));

	ns_client_log(client, NS_LOGCATEGORY_UPDATE_SECURITY,
383 384
		      NS_LOGMODULE_UPDATE, level, "%s '%s/%s' %s",
		      message, namebuf, classbuf, msg);
385 386 387
	return (result);
}

388
/*%
Andreas Gustafsson's avatar
Andreas Gustafsson committed
389 390
 * Update a single RR in version 'ver' of 'db' and log the
 * update in 'diff'.
391
 *
Andreas Gustafsson's avatar
Andreas Gustafsson committed
392
 * Ensures:
393 394
 * \li	'*tuple' == NULL.  Either the tuple is freed, or its
 *	ownership has been transferred to the diff.
Andreas Gustafsson's avatar
Andreas Gustafsson committed
395
 */
396
static isc_result_t
397
do_one_tuple(dns_difftuple_t **tuple, dns_db_t *db, dns_dbversion_t *ver,
Andreas Gustafsson's avatar
Andreas Gustafsson committed
398 399 400
	     dns_diff_t *diff)
{
	dns_diff_t temp_diff;
401
	isc_result_t result;
402

403 404 405
	/*
	 * Create a singleton diff.
	 */
Andreas Gustafsson's avatar
Andreas Gustafsson committed
406 407 408
	dns_diff_init(diff->mctx, &temp_diff);
	ISC_LIST_APPEND(temp_diff.tuples, *tuple, link);

409 410 411
	/*
	 * Apply it to the database.
	 */
Andreas Gustafsson's avatar
Andreas Gustafsson committed
412
	result = dns_diff_apply(&temp_diff, db, ver);
Mark Andrews's avatar
Mark Andrews committed
413
	ISC_LIST_UNLINK(temp_diff.tuples, *tuple, link);
414
	if (result != ISC_R_SUCCESS) {
Andreas Gustafsson's avatar
Andreas Gustafsson committed
415 416 417 418
		dns_difftuple_free(tuple);
		return (result);
	}

419 420 421
	/*
	 * Merge it into the current pending journal entry.
	 */
Andreas Gustafsson's avatar
Andreas Gustafsson committed
422 423
	dns_diff_appendminimal(diff, tuple);

424 425 426
	/*
	 * Do not clear temp_diff.
	 */
427
	return (ISC_R_SUCCESS);
Andreas Gustafsson's avatar
Andreas Gustafsson committed
428
}
429

430
/*%
431 432 433 434
 * Perform the updates in 'updates' in version 'ver' of 'db' and log the
 * update in 'diff'.
 *
 * Ensures:
435
 * \li	'updates' is empty.
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453
 */
static isc_result_t
do_diff(dns_diff_t *updates, dns_db_t *db, dns_dbversion_t *ver,
	dns_diff_t *diff)
{
	isc_result_t result;
	while (! ISC_LIST_EMPTY(updates->tuples)) {
		dns_difftuple_t *t = ISC_LIST_HEAD(updates->tuples);
		ISC_LIST_UNLINK(updates->tuples, t, link);
		CHECK(do_one_tuple(&t, db, ver, diff));
	}
	return (ISC_R_SUCCESS);

 failure:
	dns_diff_clear(diff);
	return (result);
}

454
static isc_result_t
Andreas Gustafsson's avatar
Andreas Gustafsson committed
455
update_one_rr(dns_db_t *db, dns_dbversion_t *ver, dns_diff_t *diff,
456 457
	      dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl,
	      dns_rdata_t *rdata)
Andreas Gustafsson's avatar
Andreas Gustafsson committed
458 459
{
	dns_difftuple_t *tuple = NULL;
460
	isc_result_t result;
Andreas Gustafsson's avatar
Andreas Gustafsson committed
461 462
	result = dns_difftuple_create(diff->mctx, op,
				      name, ttl, rdata, &tuple);
463
	if (result != ISC_R_SUCCESS)
Andreas Gustafsson's avatar
Andreas Gustafsson committed
464 465 466 467 468 469 470 471
		return (result);
	return (do_one_tuple(&tuple, db, ver, diff));
}

/**************************************************************************/
/*
 * Callback-style iteration over rdatasets and rdatas.
 *
472
 * foreach_rrset() can be used to iterate over the RRsets
Andreas Gustafsson's avatar
Andreas Gustafsson committed
473 474
 * of a name and call a callback function with each
 * one.  Similarly, foreach_rr() can be used to iterate
475
 * over the individual RRs at name, optionally restricted
Andreas Gustafsson's avatar
Andreas Gustafsson committed
476 477 478 479 480 481 482 483 484 485 486 487
 * to RRs of a given type.
 *
 * The callback functions are called "actions" and take
 * two arguments: a void pointer for passing arbitrary
 * context information, and a pointer to the current RRset
 * or RR.  By convention, their names end in "_action".
 */

/*
 * XXXRTH  We might want to make this public somewhere in libdns.
 */

488
/*%
489 490
 * Function type for foreach_rrset() iterator actions.
 */
491
typedef isc_result_t rrset_func(void *data, dns_rdataset_t *rrset);
Andreas Gustafsson's avatar
Andreas Gustafsson committed
492

493
/*%
494 495
 * Function type for foreach_rr() iterator actions.
 */
496
typedef isc_result_t rr_func(void *data, rr_t *rr);
Andreas Gustafsson's avatar
Andreas Gustafsson committed
497

498
/*%
499 500
 * Internal context struct for foreach_node_rr().
 */
Andreas Gustafsson's avatar
Andreas Gustafsson committed
501 502 503 504 505
typedef struct {
	rr_func *	rr_action;
	void *		rr_action_data;
} foreach_node_rr_ctx_t;

506
/*%
507 508
 * Internal helper function for foreach_node_rr().
 */
509
static isc_result_t
510
foreach_node_rr_action(void *data, dns_rdataset_t *rdataset) {
511
	isc_result_t result;
Andreas Gustafsson's avatar
Andreas Gustafsson committed
512 513
	foreach_node_rr_ctx_t *ctx = data;
	for (result = dns_rdataset_first(rdataset);
514
	     result == ISC_R_SUCCESS;
Andreas Gustafsson's avatar
Andreas Gustafsson committed
515 516
	     result = dns_rdataset_next(rdataset))
	{
517
		rr_t rr = { 0, DNS_RDATA_INIT };
Automatic Updater's avatar
Automatic Updater committed
518

Andreas Gustafsson's avatar
Andreas Gustafsson committed
519 520 521
		dns_rdataset_current(rdataset, &rr.rdata);
		rr.ttl = rdataset->ttl;
		result = (*ctx->rr_action)(ctx->rr_action_data, &rr);
522
		if (result != ISC_R_SUCCESS)
Andreas Gustafsson's avatar
Andreas Gustafsson committed
523 524
			return (result);
	}
525
	if (result != ISC_R_NOMORE)
Andreas Gustafsson's avatar
Andreas Gustafsson committed
526
		return (result);
527
	return (ISC_R_SUCCESS);
Andreas Gustafsson's avatar
Andreas Gustafsson committed
528 529
}

530
/*%
Andreas Gustafsson's avatar
Andreas Gustafsson committed
531 532
 * For each rdataset of 'name' in 'ver' of 'db', call 'action'
 * with the rdataset and 'action_data' as arguments.  If the name
533
 * does not exist, do nothing.
Andreas Gustafsson's avatar
Andreas Gustafsson committed
534 535 536
 *
 * If 'action' returns an error, abort iteration and return the error.
 */
537
static isc_result_t
538 539
foreach_rrset(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
	      rrset_func *action, void *action_data)
Andreas Gustafsson's avatar
Andreas Gustafsson committed
540
{
541
	isc_result_t result;
Andreas Gustafsson's avatar
Andreas Gustafsson committed
542 543
	dns_dbnode_t *node;
	dns_rdatasetiter_t *iter;
544

Andreas Gustafsson's avatar
Andreas Gustafsson committed
545 546
	node = NULL;
	result = dns_db_findnode(db, name, ISC_FALSE, &node);
547 548 549
	if (result == ISC_R_NOTFOUND)
		return (ISC_R_SUCCESS);
	if (result != ISC_R_SUCCESS)
Andreas Gustafsson's avatar
Andreas Gustafsson committed
550
		return (result);
551

Andreas Gustafsson's avatar
Andreas Gustafsson committed
552 553 554
	iter = NULL;
	result = dns_db_allrdatasets(db, node, ver,
				     (isc_stdtime_t) 0, &iter);
555
	if (result != ISC_R_SUCCESS)
Andreas Gustafsson's avatar
Andreas Gustafsson committed
556 557 558
		goto cleanup_node;

	for (result = dns_rdatasetiter_first(iter);
559
	     result == ISC_R_SUCCESS;
Andreas Gustafsson's avatar
Andreas Gustafsson committed
560 561 562 563 564 565 566 567 568 569
	     result = dns_rdatasetiter_next(iter))
	{
		dns_rdataset_t rdataset;

		dns_rdataset_init(&rdataset);
		dns_rdatasetiter_current(iter, &rdataset);

		result = (*action)(action_data, &rdataset);

		dns_rdataset_disassociate(&rdataset);
570
		if (result != ISC_R_SUCCESS)
Andreas Gustafsson's avatar
Andreas Gustafsson committed
571 572
			goto cleanup_iterator;
	}
573 574
	if (result == ISC_R_NOMORE)
		result = ISC_R_SUCCESS;
Andreas Gustafsson's avatar
Andreas Gustafsson committed
575 576 577 578 579

 cleanup_iterator:
	dns_rdatasetiter_destroy(&iter);

 cleanup_node:
580
	dns_db_detachnode(db, &node);
Andreas Gustafsson's avatar
Andreas Gustafsson committed
581 582 583 584

	return (result);
}

585
/*%
Andreas Gustafsson's avatar
Andreas Gustafsson committed
586 587 588 589 590 591 592
 * For each RR of 'name' in 'ver' of 'db', call 'action'
 * with the RR and 'action_data' as arguments.  If the name
 * does not exist, do nothing.
 *
 * If 'action' returns an error, abort iteration
 * and return the error.
 */
593
static isc_result_t
594 595
foreach_node_rr(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
		rr_func *rr_action, void *rr_action_data)
Andreas Gustafsson's avatar
Andreas Gustafsson committed
596 597 598 599 600 601 602 603 604
{
	foreach_node_rr_ctx_t ctx;
	ctx.rr_action = rr_action;
	ctx.rr_action_data = rr_action_data;
	return (foreach_rrset(db, ver, name,
			      foreach_node_rr_action, &ctx));
}


605
/*%
Bob Halley's avatar
Bob Halley committed
606 607
 * For each of the RRs specified by 'db', 'ver', 'name', 'type',
 * (which can be dns_rdatatype_any to match any type), and 'covers', call
608
 * 'action' with the RR and 'action_data' as arguments. If the name
Andreas Gustafsson's avatar
Andreas Gustafsson committed
609 610
 * does not exist, or if no RRset of the given type exists at the name,
 * do nothing.
611
 *
Andreas Gustafsson's avatar
Andreas Gustafsson committed
612 613
 * If 'action' returns an error, abort iteration and return the error.
 */
614
static isc_result_t
615 616
foreach_rr(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
	   dns_rdatatype_t type, dns_rdatatype_t covers, rr_func *rr_action,
Andreas Gustafsson's avatar
Andreas Gustafsson committed
617 618 619
	   void *rr_action_data)
{

620
	isc_result_t result;
Andreas Gustafsson's avatar
Andreas Gustafsson committed
621 622 623 624
	dns_dbnode_t *node;
	dns_rdataset_t rdataset;

	if (type == dns_rdatatype_any)
625
		return (foreach_node_rr(db, ver, name,
Andreas Gustafsson's avatar
Andreas Gustafsson committed
626
					rr_action, rr_action_data));
627

Andreas Gustafsson's avatar
Andreas Gustafsson committed
628
	node = NULL;
629 630 631 632 633
	if (type == dns_rdatatype_nsec3 ||
	    (type == dns_rdatatype_rrsig && covers == dns_rdatatype_nsec3))
		result = dns_db_findnsec3node(db, name, ISC_FALSE, &node);
	else
		result = dns_db_findnode(db, name, ISC_FALSE, &node);
634 635 636
	if (result == ISC_R_NOTFOUND)
		return (ISC_R_SUCCESS);
	if (result != ISC_R_SUCCESS)
Andreas Gustafsson's avatar
Andreas Gustafsson committed
637 638 639
		return (result);

	dns_rdataset_init(&rdataset);
Bob Halley's avatar
Bob Halley committed
640
	result = dns_db_findrdataset(db, node, ver, type, covers,
Bob Halley's avatar
Bob Halley committed
641
				     (isc_stdtime_t) 0, &rdataset, NULL);
642 643
	if (result == ISC_R_NOTFOUND) {
		result = ISC_R_SUCCESS;
Andreas Gustafsson's avatar
Andreas Gustafsson committed
644 645
		goto cleanup_node;
	}
646
	if (result != ISC_R_SUCCESS)
Andreas Gustafsson's avatar
Andreas Gustafsson committed
647 648 649
		goto cleanup_node;

	for (result = dns_rdataset_first(&rdataset);
650
	     result == ISC_R_SUCCESS;
Andreas Gustafsson's avatar
Andreas Gustafsson committed
651 652
	     result = dns_rdataset_next(&rdataset))
	{
653
		rr_t rr = { 0, DNS_RDATA_INIT };
Andreas Gustafsson's avatar
Andreas Gustafsson committed
654 655 656
		dns_rdataset_current(&rdataset, &rr.rdata);
		rr.ttl = rdataset.ttl;
		result = (*rr_action)(rr_action_data, &rr);
657
		if (result != ISC_R_SUCCESS)
Andreas Gustafsson's avatar
Andreas Gustafsson committed
658 659
			goto cleanup_rdataset;
	}
660
	if (result != ISC_R_NOMORE)
Andreas Gustafsson's avatar
Andreas Gustafsson committed
661
		goto cleanup_rdataset;
662
	result = ISC_R_SUCCESS;
Andreas Gustafsson's avatar
Andreas Gustafsson committed
663 664 665 666

 cleanup_rdataset:
	dns_rdataset_disassociate(&rdataset);
 cleanup_node:
667
	dns_db_detachnode(db, &node);
Andreas Gustafsson's avatar
Andreas Gustafsson committed
668 669 670 671 672 673 674 675 676

	return (result);
}

/**************************************************************************/
/*
 * Various tests on the database contents (for prerequisites, etc).
 */

677
/*%
Andreas Gustafsson's avatar
Andreas Gustafsson committed
678 679 680 681 682
 * Function type for predicate functions that compare a database RR 'db_rr'
 * against an update RR 'update_rr'.
 */
typedef isc_boolean_t rr_predicate(dns_rdata_t *update_rr, dns_rdata_t *db_rr);

683
/*%
684 685
 * Helper function for rrset_exists().
 */
686
static isc_result_t
687
rrset_exists_action(void *data, rr_t *rr) {
Andreas Gustafsson's avatar
Andreas Gustafsson committed
688 689
	UNUSED(data);
	UNUSED(rr);
690
	return (ISC_R_EXISTS);
Andreas Gustafsson's avatar
Andreas Gustafsson committed
691 692
}

693
/*%
694 695
 * Utility macro for RR existence checking functions.
 *
696 697
 * If the variable 'result' has the value ISC_R_EXISTS or
 * ISC_R_SUCCESS, set *exists to ISC_TRUE or ISC_FALSE,
698 699 700 701 702 703 704 705
 * respectively, and return success.
 *
 * If 'result' has any other value, there was a failure.
 * Return the failure result code and do not set *exists.
 *
 * This would be more readable as "do { if ... } while(0)",
 * but that form generates tons of warnings on Solaris 2.6.
 */
706 707 708
#define RETURN_EXISTENCE_FLAG				\
	return ((result == ISC_R_EXISTS) ?		\
		(*exists = ISC_TRUE, ISC_R_SUCCESS) :	\
709 710
		((result == ISC_R_SUCCESS) ?		\
		 (*exists = ISC_FALSE, ISC_R_SUCCESS) :	\
711
		 result))
712

713
/*%
Andreas Gustafsson's avatar
Andreas Gustafsson committed
714 715 716
 * Set '*exists' to true iff an rrset of the given type exists,
 * to false otherwise.
 */
717
static isc_result_t
718 719
rrset_exists(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
	     dns_rdatatype_t type, dns_rdatatype_t covers,
Bob Halley's avatar
Bob Halley committed
720
	     isc_boolean_t *exists)
Andreas Gustafsson's avatar
Andreas Gustafsson committed
721
{
722
	isc_result_t result;
Bob Halley's avatar
Bob Halley committed
723
	result = foreach_rr(db, ver, name, type, covers,
Andreas Gustafsson's avatar
Andreas Gustafsson committed
724 725 726 727
			    rrset_exists_action, NULL);
	RETURN_EXISTENCE_FLAG;
}

728
/*%
729 730
 * Helper function for cname_incompatible_rrset_exists.
 */
731
static isc_result_t
732
cname_compatibility_action(void *data, dns_rdataset_t *rrset) {
Andreas Gustafsson's avatar
Andreas Gustafsson committed
733
	UNUSED(data);
Andreas Gustafsson's avatar
Andreas Gustafsson committed
734
	if (rrset->type != dns_rdatatype_cname &&
735
	    ! dns_rdatatype_isdnssec(rrset->type))
736 737
		return (ISC_R_EXISTS);
	return (ISC_R_SUCCESS);
Andreas Gustafsson's avatar
Andreas Gustafsson committed
738 739
}

740
/*%
Andreas Gustafsson's avatar
Andreas Gustafsson committed
741 742 743 744 745 746 747
 * Check whether there is an rrset incompatible with adding a CNAME RR,
 * i.e., anything but another CNAME (which can be replaced) or a
 * DNSSEC RR (which can coexist).
 *
 * If such an incompatible rrset exists, set '*exists' to ISC_TRUE.
 * Otherwise, set it to ISC_FALSE.
 */
748
static isc_result_t
Andreas Gustafsson's avatar
Andreas Gustafsson committed
749 750
cname_incompatible_rrset_exists(dns_db_t *db, dns_dbversion_t *ver,
				dns_name_t *name, isc_boolean_t *exists) {
751 752
	isc_result_t result;
	result = foreach_rrset(db, ver, name,
Andreas Gustafsson's avatar
Andreas Gustafsson committed
753 754 755 756
			       cname_compatibility_action, NULL);
	RETURN_EXISTENCE_FLAG;
}

757
/*%
758 759
 * Helper function for rr_count().
 */
760
static isc_result_t
761
count_rr_action(void *data, rr_t *rr) {
Andreas Gustafsson's avatar
Andreas Gustafsson committed
762
	int *countp = data;
Andreas Gustafsson's avatar
Andreas Gustafsson committed
763
	UNUSED(rr);
Andreas Gustafsson's avatar
Andreas Gustafsson committed
764
	(*countp)++;
765
	return (ISC_R_SUCCESS);
Andreas Gustafsson's avatar
Andreas Gustafsson committed
766 767
}

768
/*%
Andreas Gustafsson's avatar
Andreas Gustafsson committed
769 770
 * Count the number of RRs of 'type' belonging to 'name' in 'ver' of 'db'.
 */
771
static isc_result_t
Andreas Gustafsson's avatar
Andreas Gustafsson committed
772
rr_count(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
Bob Halley's avatar
Bob Halley committed
773
	 dns_rdatatype_t type, dns_rdatatype_t covers, int *countp)
Andreas Gustafsson's avatar
Andreas Gustafsson committed
774 775
{
	*countp = 0;
Bob Halley's avatar
Bob Halley committed
776
	return (foreach_rr(db, ver, name, type, covers,
Andreas Gustafsson's avatar
Andreas Gustafsson committed
777 778 779
			   count_rr_action, countp));
}

780
/*%
781 782
 * Context struct and helper function for name_exists().
 */
Andreas Gustafsson's avatar
Andreas Gustafsson committed
783

784
static isc_result_t
785
name_exists_action(void *data, dns_rdataset_t *rrset) {
Andreas Gustafsson's avatar
Andreas Gustafsson committed
786 787
	UNUSED(data);
	UNUSED(rrset);
788
	return (ISC_R_EXISTS);
Andreas Gustafsson's avatar
Andreas Gustafsson committed
789 790
}

791
/*%
Andreas Gustafsson's avatar
Andreas Gustafsson committed
792 793
 * Set '*exists' to true iff the given name exists, to false otherwise.
 */
794
static isc_result_t
Andreas Gustafsson's avatar
Andreas Gustafsson committed
795 796 797
name_exists(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
	    isc_boolean_t *exists)
{
798
	isc_result_t result;
Andreas Gustafsson's avatar
Andreas Gustafsson committed
799 800 801 802 803
	result = foreach_rrset(db, ver, name,
			       name_exists_action, NULL);
	RETURN_EXISTENCE_FLAG;
}

804
/*
Francis Dupont's avatar
Francis Dupont committed
805
 *	'ssu_check_t' is used to pass the arguments to
806 807 808
 *	dns_ssutable_checkrules() to the callback function
 *	ssu_checkrule().
 */
809
typedef struct {
810
	/* The ownername of the record to be updated. */
811
	dns_name_t *name;
812 813

	/* The signature's name if the request was signed. */
814
	dns_name_t *signer;
815 816 817 818 819

	/* The address of the client if the request was received via TCP. */
	isc_netaddr_t *tcpaddr;

	/* The ssu table to check against. */
820
	dns_ssutable_t *table;
821 822 823

	/* the key used for TKEY requests */
	dst_key_t *key;
824 825 826
} ssu_check_t;

static isc_result_t
827
ssu_checkrule(void *data, dns_rdataset_t *rrset) {
828 829 830 831
	ssu_check_t *ssuinfo = data;
	isc_boolean_t result;

	/*
832
	 * If we're deleting all records, it's ok to delete RRSIG and NSEC even
833 834
	 * if we're normally not allowed to.
	 */
835 836
	if (rrset->type == dns_rdatatype_rrsig ||
	    rrset->type == dns_rdatatype_nsec)
837
		return (ISC_R_SUCCESS);
838
	result = dns_ssutable_checkrules(ssuinfo->table, ssuinfo->signer,
839
					 ssuinfo->name, ssuinfo->tcpaddr,
840
					 rrset->type, ssuinfo->key);
841 842 843 844 845
	return (result == ISC_TRUE ? ISC_R_SUCCESS : ISC_R_FAILURE);
}

static isc_boolean_t
ssu_checkall(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
846
	     dns_ssutable_t *ssutable, dns_name_t *signer,
847
	     isc_netaddr_t *tcpaddr, dst_key_t *key)
848 849 850 851 852 853 854
{
	isc_result_t result;
	ssu_check_t ssuinfo;

	ssuinfo.name = name;
	ssuinfo.table = ssutable;
	ssuinfo.signer = signer;
855
	ssuinfo.tcpaddr = tcpaddr;
856
	ssuinfo.key = key;
857 858 859 860
	result = foreach_rrset(db, ver, name, ssu_checkrule, &ssuinfo);
	return (ISC_TF(result == ISC_R_SUCCESS));
}

Andreas Gustafsson's avatar
Andreas Gustafsson committed
861 862 863 864 865 866 867
/**************************************************************************/
/*
 * Checking of "RRset exists (value dependent)" prerequisites.
 *
 * In the RFC2136 section 3.2.5, this is the pseudocode involving
 * a variable called "temp", a mapping of <name, type> tuples to rrsets.
 *
Francis Dupont's avatar
Francis Dupont committed
868 869
 * Here, we represent the "temp" data structure as (non-minimal) "dns_diff_t"
 * where each tuple has op==DNS_DIFFOP_EXISTS.
Andreas Gustafsson's avatar
Andreas Gustafsson committed
870 871 872
 */


873
/*%
Andreas Gustafsson's avatar
Andreas Gustafsson committed
874 875
 * Append a tuple asserting the existence of the RR with
 * 'name' and 'rdata' to 'diff'.
876
 */
877
static isc_result_t
878
temp_append(dns_diff_t *diff, dns_name_t *name, dns_rdata_t *rdata) {
879
	isc_result_t result;
Andreas Gustafsson's avatar
Andreas Gustafsson committed
880
	dns_difftuple_t *tuple = NULL;
881

Bob Halley's avatar
Bob Halley committed
882
	REQUIRE(DNS_DIFF_VALID(diff));
883
	CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_EXISTS,
884
				   name, 0, rdata, &tuple));
Andreas Gustafsson's avatar
Andreas Gustafsson committed
885
	ISC_LIST_APPEND(diff->tuples, tuple, link);
886 887
 failure:
	return (result);
Andreas Gustafsson's avatar
Andreas Gustafsson committed
888 889
}

890
/*%
Andreas Gustafsson's avatar
Andreas Gustafsson committed
891 892
 * Compare two rdatasets represented as sorted lists of tuples.
 * All list elements must have the same owner name and type.
893
 * Return ISC_R_SUCCESS if the rdatasets are equal, rcode(dns_rcode_nxrrset)
Andreas Gustafsson's avatar
Andreas Gustafsson committed
894 895
 * if not.
 */
896
static isc_result_t
Andreas Gustafsson's avatar
Andreas Gustafsson committed
897 898 899 900
temp_check_rrset(dns_difftuple_t *a, dns_difftuple_t *b) {
	for (;;) {
		if (a == NULL || b == NULL)
			break;
901 902
		INSIST(a->op == DNS_DIFFOP_EXISTS &&
		       b->op == DNS_DIFFOP_EXISTS);
Andreas Gustafsson's avatar
Andreas Gustafsson committed
903 904
		INSIST(a->rdata.type == b->rdata.type);
		INSIST(dns_name_equal(&a->name, &b->name));
905
		if (dns_rdata_casecompare(&a->rdata, &b->rdata) != 0)
Andreas Gustafsson's avatar
Andreas Gustafsson committed
906 907 908 909 910 911
			return (DNS_R_NXRRSET);
		a = ISC_LIST_NEXT(a, link);
		b = ISC_LIST_NEXT(b, link);
	}
	if (a != NULL || b != NULL)
		return (DNS_R_NXRRSET);
912
	return (ISC_R_SUCCESS);
Andreas Gustafsson's avatar
Andreas Gustafsson committed
913 914
}

915
/*%
Andreas Gustafsson's avatar
Andreas Gustafsson committed
916 917 918 919
 * A comparison function defining the sorting order for the entries
 * in the "temp" data structure.  The major sort key is the owner name,
 * followed by the type and rdata.
 */
920
static int
921
temp_order(const void *av, const void *bv) {
Brian Wellington's avatar
Brian Wellington committed
922 923 924 925
	dns_difftuple_t const * const *ap = av;
	dns_difftuple_t const * const *bp = bv;
	dns_difftuple_t const *a = *ap;
	dns_difftuple_t const *b = *bp;
Andreas Gustafsson's avatar
Andreas Gustafsson committed
926 927 928 929 930 931 932
	int r;
	r = dns_name_compare(&a->name, &b->name);
	if (r != 0)
		return (r);
	r = (b->rdata.type - a->rdata.type);
	if (r != 0)
		return (r);
933
	r = dns_rdata_casecompare(&a->rdata, &b->rdata);
Andreas Gustafsson's avatar
Andreas Gustafsson committed
<