Home | History | Annotate | Line # | Download | only in dns
diff.c revision 1.10
      1  1.10  christos /*	$NetBSD: diff.c,v 1.10 2025/01/26 16:25:22 christos Exp $	*/
      2   1.1  christos 
      3   1.1  christos /*
      4   1.1  christos  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
      5   1.1  christos  *
      6   1.7  christos  * SPDX-License-Identifier: MPL-2.0
      7   1.7  christos  *
      8   1.1  christos  * This Source Code Form is subject to the terms of the Mozilla Public
      9   1.1  christos  * License, v. 2.0. If a copy of the MPL was not distributed with this
     10   1.5  christos  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
     11   1.1  christos  *
     12   1.1  christos  * See the COPYRIGHT file distributed with this work for additional
     13   1.1  christos  * information regarding copyright ownership.
     14   1.1  christos  */
     15   1.1  christos 
     16   1.1  christos /*! \file */
     17   1.1  christos 
     18   1.3  christos #include <inttypes.h>
     19   1.3  christos #include <stdbool.h>
     20  1.10  christos #include <stddef.h>
     21   1.1  christos #include <stdlib.h>
     22   1.1  christos 
     23   1.1  christos #include <isc/buffer.h>
     24   1.1  christos #include <isc/file.h>
     25   1.1  christos #include <isc/mem.h>
     26   1.9  christos #include <isc/result.h>
     27   1.1  christos #include <isc/string.h>
     28   1.1  christos #include <isc/util.h>
     29   1.1  christos 
     30  1.10  christos #include <dns/callbacks.h>
     31   1.1  christos #include <dns/db.h>
     32   1.1  christos #include <dns/diff.h>
     33   1.1  christos #include <dns/log.h>
     34   1.1  christos #include <dns/rdataclass.h>
     35   1.1  christos #include <dns/rdatalist.h>
     36   1.1  christos #include <dns/rdataset.h>
     37   1.1  christos #include <dns/rdatastruct.h>
     38   1.1  christos #include <dns/rdatatype.h>
     39   1.1  christos #include <dns/time.h>
     40   1.1  christos 
     41   1.4  christos #define CHECK(op)                            \
     42   1.4  christos 	do {                                 \
     43   1.4  christos 		result = (op);               \
     44   1.4  christos 		if (result != ISC_R_SUCCESS) \
     45   1.4  christos 			goto failure;        \
     46   1.6    rillig 	} while (0)
     47   1.1  christos 
     48   1.1  christos #define DIFF_COMMON_LOGARGS \
     49   1.1  christos 	dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_DIFF
     50   1.1  christos 
     51   1.1  christos static dns_rdatatype_t
     52   1.1  christos rdata_covers(dns_rdata_t *rdata) {
     53  1.10  christos 	return rdata->type == dns_rdatatype_rrsig ? dns_rdata_covers(rdata) : 0;
     54   1.1  christos }
     55   1.1  christos 
     56   1.1  christos isc_result_t
     57   1.4  christos dns_difftuple_create(isc_mem_t *mctx, dns_diffop_t op, const dns_name_t *name,
     58   1.4  christos 		     dns_ttl_t ttl, dns_rdata_t *rdata, dns_difftuple_t **tp) {
     59   1.1  christos 	dns_difftuple_t *t;
     60   1.1  christos 	unsigned int size;
     61   1.1  christos 	unsigned char *datap;
     62   1.1  christos 
     63   1.1  christos 	REQUIRE(tp != NULL && *tp == NULL);
     64   1.1  christos 
     65   1.1  christos 	/*
     66   1.1  christos 	 * Create a new tuple.  The variable-size wire-format name data and
     67   1.1  christos 	 * rdata immediately follow the dns_difftuple_t structure
     68   1.1  christos 	 * in memory.
     69   1.1  christos 	 */
     70   1.1  christos 	size = sizeof(*t) + name->length + rdata->length;
     71   1.1  christos 	t = isc_mem_allocate(mctx, size);
     72   1.1  christos 	t->mctx = NULL;
     73   1.1  christos 	isc_mem_attach(mctx, &t->mctx);
     74   1.1  christos 	t->op = op;
     75   1.1  christos 
     76   1.1  christos 	datap = (unsigned char *)(t + 1);
     77   1.1  christos 
     78   1.1  christos 	memmove(datap, name->ndata, name->length);
     79   1.1  christos 	dns_name_init(&t->name, NULL);
     80   1.1  christos 	dns_name_clone(name, &t->name);
     81   1.1  christos 	t->name.ndata = datap;
     82   1.1  christos 	datap += name->length;
     83   1.1  christos 
     84   1.1  christos 	t->ttl = ttl;
     85   1.1  christos 
     86   1.1  christos 	dns_rdata_init(&t->rdata);
     87   1.1  christos 	dns_rdata_clone(rdata, &t->rdata);
     88   1.1  christos 	if (rdata->data != NULL) {
     89   1.1  christos 		memmove(datap, rdata->data, rdata->length);
     90   1.1  christos 		t->rdata.data = datap;
     91   1.1  christos 		datap += rdata->length;
     92   1.1  christos 	} else {
     93   1.1  christos 		t->rdata.data = NULL;
     94   1.1  christos 		INSIST(rdata->length == 0);
     95   1.1  christos 	}
     96   1.1  christos 
     97   1.1  christos 	ISC_LINK_INIT(&t->rdata, link);
     98   1.1  christos 	ISC_LINK_INIT(t, link);
     99   1.1  christos 	t->magic = DNS_DIFFTUPLE_MAGIC;
    100   1.1  christos 
    101   1.1  christos 	INSIST(datap == (unsigned char *)t + size);
    102   1.1  christos 
    103   1.1  christos 	*tp = t;
    104  1.10  christos 	return ISC_R_SUCCESS;
    105   1.1  christos }
    106   1.1  christos 
    107   1.1  christos void
    108   1.1  christos dns_difftuple_free(dns_difftuple_t **tp) {
    109   1.1  christos 	dns_difftuple_t *t = *tp;
    110   1.4  christos 	*tp = NULL;
    111   1.1  christos 	isc_mem_t *mctx;
    112   1.1  christos 
    113   1.1  christos 	REQUIRE(DNS_DIFFTUPLE_VALID(t));
    114   1.1  christos 
    115   1.1  christos 	dns_name_invalidate(&t->name);
    116   1.1  christos 	t->magic = 0;
    117   1.1  christos 	mctx = t->mctx;
    118   1.1  christos 	isc_mem_free(mctx, t);
    119   1.1  christos 	isc_mem_detach(&mctx);
    120   1.1  christos }
    121   1.1  christos 
    122   1.1  christos isc_result_t
    123   1.1  christos dns_difftuple_copy(dns_difftuple_t *orig, dns_difftuple_t **copyp) {
    124  1.10  christos 	return dns_difftuple_create(orig->mctx, orig->op, &orig->name,
    125  1.10  christos 				    orig->ttl, &orig->rdata, copyp);
    126   1.1  christos }
    127   1.1  christos 
    128   1.1  christos void
    129   1.1  christos dns_diff_init(isc_mem_t *mctx, dns_diff_t *diff) {
    130   1.1  christos 	diff->mctx = mctx;
    131   1.1  christos 	ISC_LIST_INIT(diff->tuples);
    132   1.1  christos 	diff->magic = DNS_DIFF_MAGIC;
    133  1.10  christos 	diff->size = 0;
    134   1.1  christos }
    135   1.1  christos 
    136   1.1  christos void
    137   1.1  christos dns_diff_clear(dns_diff_t *diff) {
    138   1.1  christos 	dns_difftuple_t *t;
    139   1.1  christos 	REQUIRE(DNS_DIFF_VALID(diff));
    140   1.1  christos 	while ((t = ISC_LIST_HEAD(diff->tuples)) != NULL) {
    141   1.1  christos 		ISC_LIST_UNLINK(diff->tuples, t, link);
    142   1.1  christos 		dns_difftuple_free(&t);
    143   1.1  christos 	}
    144  1.10  christos 	diff->size = 0;
    145   1.1  christos 	ENSURE(ISC_LIST_EMPTY(diff->tuples));
    146   1.1  christos }
    147   1.1  christos 
    148   1.1  christos void
    149   1.4  christos dns_diff_append(dns_diff_t *diff, dns_difftuple_t **tuplep) {
    150  1.10  christos 	REQUIRE(DNS_DIFF_VALID(diff));
    151   1.1  christos 	ISC_LIST_APPEND(diff->tuples, *tuplep, link);
    152  1.10  christos 	diff->size += 1;
    153   1.1  christos 	*tuplep = NULL;
    154   1.1  christos }
    155   1.1  christos 
    156  1.10  christos bool
    157  1.10  christos dns_diff_is_boundary(const dns_diff_t *diff, dns_name_t *new_name) {
    158  1.10  christos 	REQUIRE(DNS_DIFF_VALID(diff));
    159  1.10  christos 	REQUIRE(DNS_NAME_VALID(new_name));
    160  1.10  christos 
    161  1.10  christos 	if (ISC_LIST_EMPTY(diff->tuples)) {
    162  1.10  christos 		return false;
    163  1.10  christos 	}
    164  1.10  christos 
    165  1.10  christos 	dns_difftuple_t *tail = ISC_LIST_TAIL(diff->tuples);
    166  1.10  christos 	return !dns_name_caseequal(&tail->name, new_name);
    167  1.10  christos }
    168  1.10  christos 
    169  1.10  christos size_t
    170  1.10  christos dns_diff_size(const dns_diff_t *diff) {
    171  1.10  christos 	REQUIRE(DNS_DIFF_VALID(diff));
    172  1.10  christos 	return diff->size;
    173  1.10  christos }
    174  1.10  christos 
    175   1.1  christos /* XXX this is O(N) */
    176   1.1  christos 
    177   1.1  christos void
    178   1.4  christos dns_diff_appendminimal(dns_diff_t *diff, dns_difftuple_t **tuplep) {
    179   1.1  christos 	dns_difftuple_t *ot, *next_ot;
    180   1.1  christos 
    181   1.1  christos 	REQUIRE(DNS_DIFF_VALID(diff));
    182   1.1  christos 	REQUIRE(DNS_DIFFTUPLE_VALID(*tuplep));
    183   1.1  christos 
    184   1.1  christos 	/*
    185   1.1  christos 	 * Look for an existing tuple with the same owner name,
    186   1.1  christos 	 * rdata, and TTL.   If we are doing an addition and find a
    187   1.1  christos 	 * deletion or vice versa, remove both the old and the
    188   1.1  christos 	 * new tuple since they cancel each other out (assuming
    189   1.1  christos 	 * that we never delete nonexistent data or add existing
    190   1.1  christos 	 * data).
    191   1.1  christos 	 *
    192   1.1  christos 	 * If we find an old update of the same kind as
    193   1.1  christos 	 * the one we are doing, there must be a programming
    194   1.1  christos 	 * error.  We report it but try to continue anyway.
    195   1.1  christos 	 */
    196   1.4  christos 	for (ot = ISC_LIST_HEAD(diff->tuples); ot != NULL; ot = next_ot) {
    197   1.1  christos 		next_ot = ISC_LIST_NEXT(ot, link);
    198   1.1  christos 		if (dns_name_caseequal(&ot->name, &(*tuplep)->name) &&
    199   1.1  christos 		    dns_rdata_compare(&ot->rdata, &(*tuplep)->rdata) == 0 &&
    200   1.1  christos 		    ot->ttl == (*tuplep)->ttl)
    201   1.1  christos 		{
    202   1.1  christos 			ISC_LIST_UNLINK(diff->tuples, ot, link);
    203  1.10  christos 			INSIST(diff->size > 0);
    204  1.10  christos 			diff->size -= 1;
    205  1.10  christos 
    206   1.1  christos 			if ((*tuplep)->op == ot->op) {
    207   1.9  christos 				UNEXPECTED_ERROR("unexpected non-minimal diff");
    208   1.1  christos 			} else {
    209   1.1  christos 				dns_difftuple_free(tuplep);
    210   1.1  christos 			}
    211   1.1  christos 			dns_difftuple_free(&ot);
    212   1.1  christos 			break;
    213   1.1  christos 		}
    214   1.1  christos 	}
    215   1.1  christos 
    216   1.1  christos 	if (*tuplep != NULL) {
    217   1.1  christos 		ISC_LIST_APPEND(diff->tuples, *tuplep, link);
    218  1.10  christos 		diff->size += 1;
    219   1.1  christos 		*tuplep = NULL;
    220   1.1  christos 	}
    221   1.1  christos }
    222   1.1  christos 
    223   1.1  christos static isc_stdtime_t
    224   1.1  christos setresign(dns_rdataset_t *modified) {
    225   1.1  christos 	dns_rdata_t rdata = DNS_RDATA_INIT;
    226   1.1  christos 	dns_rdata_rrsig_t sig;
    227   1.3  christos 	int64_t when;
    228   1.1  christos 	isc_result_t result;
    229   1.1  christos 
    230   1.1  christos 	result = dns_rdataset_first(modified);
    231   1.1  christos 	INSIST(result == ISC_R_SUCCESS);
    232   1.1  christos 	dns_rdataset_current(modified, &rdata);
    233   1.1  christos 	(void)dns_rdata_tostruct(&rdata, &sig, NULL);
    234   1.4  christos 	if ((rdata.flags & DNS_RDATA_OFFLINE) != 0) {
    235   1.1  christos 		when = 0;
    236   1.4  christos 	} else {
    237   1.1  christos 		when = dns_time64_from32(sig.timeexpire);
    238   1.4  christos 	}
    239   1.1  christos 	dns_rdata_reset(&rdata);
    240   1.1  christos 
    241   1.1  christos 	result = dns_rdataset_next(modified);
    242   1.1  christos 	while (result == ISC_R_SUCCESS) {
    243   1.1  christos 		dns_rdataset_current(modified, &rdata);
    244   1.1  christos 		(void)dns_rdata_tostruct(&rdata, &sig, NULL);
    245   1.1  christos 		if ((rdata.flags & DNS_RDATA_OFFLINE) != 0) {
    246   1.1  christos 			goto next_rr;
    247   1.1  christos 		}
    248   1.4  christos 		if (when == 0 || dns_time64_from32(sig.timeexpire) < when) {
    249   1.1  christos 			when = dns_time64_from32(sig.timeexpire);
    250   1.4  christos 		}
    251   1.4  christos 	next_rr:
    252   1.1  christos 		dns_rdata_reset(&rdata);
    253   1.1  christos 		result = dns_rdataset_next(modified);
    254   1.1  christos 	}
    255   1.1  christos 	INSIST(result == ISC_R_NOMORE);
    256  1.10  christos 	return (isc_stdtime_t)when;
    257   1.1  christos }
    258   1.1  christos 
    259   1.1  christos static void
    260   1.1  christos getownercase(dns_rdataset_t *rdataset, dns_name_t *name) {
    261   1.4  christos 	if (dns_rdataset_isassociated(rdataset)) {
    262   1.1  christos 		dns_rdataset_getownercase(rdataset, name);
    263   1.4  christos 	}
    264   1.1  christos }
    265   1.1  christos 
    266   1.1  christos static void
    267   1.1  christos setownercase(dns_rdataset_t *rdataset, const dns_name_t *name) {
    268   1.4  christos 	if (dns_rdataset_isassociated(rdataset)) {
    269   1.1  christos 		dns_rdataset_setownercase(rdataset, name);
    270   1.4  christos 	}
    271   1.1  christos }
    272   1.1  christos 
    273   1.9  christos static const char *
    274   1.9  christos optotext(dns_diffop_t op) {
    275   1.9  christos 	switch (op) {
    276   1.9  christos 	case DNS_DIFFOP_ADD:
    277  1.10  christos 		return "add";
    278   1.9  christos 	case DNS_DIFFOP_ADDRESIGN:
    279  1.10  christos 		return "add-resign";
    280   1.9  christos 	case DNS_DIFFOP_DEL:
    281  1.10  christos 		return "del";
    282   1.9  christos 	case DNS_DIFFOP_DELRESIGN:
    283  1.10  christos 		return "del-resign";
    284   1.9  christos 	default:
    285  1.10  christos 		return "unknown";
    286   1.9  christos 	}
    287   1.9  christos }
    288   1.9  christos 
    289   1.1  christos static isc_result_t
    290  1.10  christos diff_apply(const dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver,
    291  1.10  christos 	   bool warn) {
    292   1.1  christos 	dns_difftuple_t *t;
    293   1.1  christos 	dns_dbnode_t *node = NULL;
    294   1.1  christos 	isc_result_t result;
    295   1.1  christos 	char namebuf[DNS_NAME_FORMATSIZE];
    296   1.1  christos 	char typebuf[DNS_RDATATYPE_FORMATSIZE];
    297   1.1  christos 	char classbuf[DNS_RDATACLASS_FORMATSIZE];
    298   1.1  christos 
    299   1.1  christos 	REQUIRE(DNS_DIFF_VALID(diff));
    300   1.1  christos 	REQUIRE(DNS_DB_VALID(db));
    301   1.1  christos 
    302   1.1  christos 	t = ISC_LIST_HEAD(diff->tuples);
    303   1.1  christos 	while (t != NULL) {
    304   1.1  christos 		dns_name_t *name;
    305   1.1  christos 
    306   1.1  christos 		INSIST(node == NULL);
    307   1.1  christos 		name = &t->name;
    308   1.1  christos 		/*
    309   1.1  christos 		 * Find the node.
    310   1.1  christos 		 * We create the node if it does not exist.
    311   1.1  christos 		 * This will cause an empty node to be created if the diff
    312   1.1  christos 		 * contains a deletion of an RR at a nonexistent name,
    313   1.1  christos 		 * but such diffs should never be created in the first
    314   1.1  christos 		 * place.
    315   1.1  christos 		 */
    316   1.1  christos 
    317   1.1  christos 		while (t != NULL && dns_name_equal(&t->name, name)) {
    318   1.1  christos 			dns_rdatatype_t type, covers;
    319   1.9  christos 			dns_rdataclass_t rdclass;
    320   1.1  christos 			dns_diffop_t op;
    321   1.1  christos 			dns_rdatalist_t rdl;
    322   1.1  christos 			dns_rdataset_t rds;
    323   1.1  christos 			dns_rdataset_t ardataset;
    324   1.1  christos 			unsigned int options;
    325   1.1  christos 
    326   1.1  christos 			op = t->op;
    327   1.1  christos 			type = t->rdata.type;
    328   1.9  christos 			rdclass = t->rdata.rdclass;
    329   1.1  christos 			covers = rdata_covers(&t->rdata);
    330   1.1  christos 
    331   1.1  christos 			/*
    332   1.1  christos 			 * Collect a contiguous set of updates with
    333   1.1  christos 			 * the same operation (add/delete) and RR type
    334   1.1  christos 			 * into a single rdatalist so that the
    335   1.1  christos 			 * database rrset merging/subtraction code
    336   1.1  christos 			 * can work more efficiently than if each
    337   1.1  christos 			 * RR were merged into / subtracted from
    338   1.1  christos 			 * the database separately.
    339   1.1  christos 			 *
    340   1.1  christos 			 * This is done by linking rdata structures from the
    341   1.1  christos 			 * diff into "rdatalist".  This uses the rdata link
    342   1.1  christos 			 * field, not the diff link field, so the structure
    343   1.1  christos 			 * of the diff itself is not affected.
    344   1.1  christos 			 */
    345   1.1  christos 
    346   1.1  christos 			dns_rdatalist_init(&rdl);
    347   1.1  christos 			rdl.type = type;
    348   1.1  christos 			rdl.covers = covers;
    349   1.1  christos 			rdl.rdclass = t->rdata.rdclass;
    350   1.1  christos 			rdl.ttl = t->ttl;
    351   1.1  christos 
    352   1.1  christos 			node = NULL;
    353   1.1  christos 			if (type != dns_rdatatype_nsec3 &&
    354   1.8  christos 			    covers != dns_rdatatype_nsec3)
    355   1.8  christos 			{
    356   1.4  christos 				CHECK(dns_db_findnode(db, name, true, &node));
    357   1.4  christos 			} else {
    358   1.3  christos 				CHECK(dns_db_findnsec3node(db, name, true,
    359   1.1  christos 							   &node));
    360   1.4  christos 			}
    361   1.1  christos 
    362   1.4  christos 			while (t != NULL && dns_name_equal(&t->name, name) &&
    363   1.4  christos 			       t->op == op && t->rdata.type == type &&
    364   1.1  christos 			       rdata_covers(&t->rdata) == covers)
    365   1.1  christos 			{
    366   1.1  christos 				/*
    367   1.1  christos 				 * Remember the add name for
    368   1.1  christos 				 * dns_rdataset_setownercase.
    369   1.1  christos 				 */
    370   1.1  christos 				name = &t->name;
    371   1.1  christos 				if (t->ttl != rdl.ttl && warn) {
    372   1.1  christos 					dns_name_format(name, namebuf,
    373   1.1  christos 							sizeof(namebuf));
    374   1.1  christos 					dns_rdatatype_format(t->rdata.type,
    375   1.1  christos 							     typebuf,
    376   1.1  christos 							     sizeof(typebuf));
    377   1.1  christos 					dns_rdataclass_format(t->rdata.rdclass,
    378   1.1  christos 							      classbuf,
    379   1.1  christos 							      sizeof(classbuf));
    380   1.1  christos 					isc_log_write(DIFF_COMMON_LOGARGS,
    381   1.4  christos 						      ISC_LOG_WARNING,
    382   1.4  christos 						      "'%s/%s/%s': TTL differs "
    383   1.4  christos 						      "in "
    384   1.4  christos 						      "rdataset, adjusting "
    385   1.4  christos 						      "%lu -> %lu",
    386   1.4  christos 						      namebuf, typebuf,
    387   1.4  christos 						      classbuf,
    388   1.4  christos 						      (unsigned long)t->ttl,
    389   1.4  christos 						      (unsigned long)rdl.ttl);
    390   1.1  christos 				}
    391   1.1  christos 				ISC_LIST_APPEND(rdl.rdata, &t->rdata, link);
    392   1.1  christos 				t = ISC_LIST_NEXT(t, link);
    393   1.1  christos 			}
    394   1.1  christos 
    395   1.1  christos 			/*
    396   1.1  christos 			 * Convert the rdatalist into a rdataset.
    397   1.1  christos 			 */
    398   1.1  christos 			dns_rdataset_init(&rds);
    399   1.1  christos 			dns_rdataset_init(&ardataset);
    400  1.10  christos 			dns_rdatalist_tordataset(&rdl, &rds);
    401   1.1  christos 			rds.trust = dns_trust_ultimate;
    402   1.1  christos 
    403   1.1  christos 			/*
    404   1.1  christos 			 * Merge the rdataset into the database.
    405   1.1  christos 			 */
    406   1.1  christos 			switch (op) {
    407   1.1  christos 			case DNS_DIFFOP_ADD:
    408   1.1  christos 			case DNS_DIFFOP_ADDRESIGN:
    409   1.1  christos 				options = DNS_DBADD_MERGE | DNS_DBADD_EXACT |
    410   1.1  christos 					  DNS_DBADD_EXACTTTL;
    411   1.4  christos 				result = dns_db_addrdataset(db, node, ver, 0,
    412   1.4  christos 							    &rds, options,
    413   1.1  christos 							    &ardataset);
    414   1.1  christos 				break;
    415   1.1  christos 			case DNS_DIFFOP_DEL:
    416   1.1  christos 			case DNS_DIFFOP_DELRESIGN:
    417   1.1  christos 				options = DNS_DBSUB_EXACT | DNS_DBSUB_WANTOLD;
    418   1.1  christos 				result = dns_db_subtractrdataset(db, node, ver,
    419   1.1  christos 								 &rds, options,
    420   1.1  christos 								 &ardataset);
    421   1.1  christos 				break;
    422   1.1  christos 			default:
    423   1.7  christos 				UNREACHABLE();
    424   1.1  christos 			}
    425   1.1  christos 
    426   1.1  christos 			if (result == ISC_R_SUCCESS) {
    427   1.1  christos 				if (rds.type == dns_rdatatype_rrsig &&
    428   1.1  christos 				    (op == DNS_DIFFOP_DELRESIGN ||
    429   1.4  christos 				     op == DNS_DIFFOP_ADDRESIGN))
    430   1.4  christos 				{
    431   1.1  christos 					isc_stdtime_t resign;
    432   1.1  christos 					resign = setresign(&ardataset);
    433   1.1  christos 					dns_db_setsigningtime(db, &ardataset,
    434   1.1  christos 							      resign);
    435   1.1  christos 				}
    436   1.1  christos 				if (op == DNS_DIFFOP_ADD ||
    437   1.8  christos 				    op == DNS_DIFFOP_ADDRESIGN)
    438   1.8  christos 				{
    439   1.1  christos 					setownercase(&ardataset, name);
    440   1.4  christos 				}
    441   1.1  christos 				if (op == DNS_DIFFOP_DEL ||
    442   1.8  christos 				    op == DNS_DIFFOP_DELRESIGN)
    443   1.8  christos 				{
    444   1.1  christos 					getownercase(&ardataset, name);
    445   1.4  christos 				}
    446   1.1  christos 			} else if (result == DNS_R_UNCHANGED) {
    447   1.1  christos 				/*
    448   1.1  christos 				 * This will not happen when executing a
    449   1.1  christos 				 * dynamic update, because that code will
    450   1.1  christos 				 * generate strictly minimal diffs.
    451   1.1  christos 				 * It may happen when receiving an IXFR
    452   1.1  christos 				 * from a server that is not as careful.
    453   1.1  christos 				 * Issue a warning and continue.
    454   1.1  christos 				 */
    455   1.1  christos 				if (warn) {
    456   1.1  christos 					dns_name_format(dns_db_origin(db),
    457   1.1  christos 							namebuf,
    458   1.1  christos 							sizeof(namebuf));
    459   1.1  christos 					dns_rdataclass_format(dns_db_class(db),
    460   1.1  christos 							      classbuf,
    461   1.1  christos 							      sizeof(classbuf));
    462   1.1  christos 					isc_log_write(DIFF_COMMON_LOGARGS,
    463   1.1  christos 						      ISC_LOG_WARNING,
    464   1.1  christos 						      "%s/%s: dns_diff_apply: "
    465   1.1  christos 						      "update with no effect",
    466   1.1  christos 						      namebuf, classbuf);
    467   1.1  christos 				}
    468   1.1  christos 				if (op == DNS_DIFFOP_ADD ||
    469   1.8  christos 				    op == DNS_DIFFOP_ADDRESIGN)
    470   1.8  christos 				{
    471   1.1  christos 					setownercase(&ardataset, name);
    472   1.4  christos 				}
    473   1.1  christos 				if (op == DNS_DIFFOP_DEL ||
    474   1.8  christos 				    op == DNS_DIFFOP_DELRESIGN)
    475   1.8  christos 				{
    476   1.1  christos 					getownercase(&ardataset, name);
    477   1.4  christos 				}
    478   1.1  christos 			} else if (result == DNS_R_NXRRSET) {
    479   1.1  christos 				/*
    480   1.1  christos 				 * OK.
    481   1.1  christos 				 */
    482   1.1  christos 				if (op == DNS_DIFFOP_DEL ||
    483   1.8  christos 				    op == DNS_DIFFOP_DELRESIGN)
    484   1.8  christos 				{
    485   1.1  christos 					getownercase(&ardataset, name);
    486   1.4  christos 				}
    487   1.4  christos 				if (dns_rdataset_isassociated(&ardataset)) {
    488   1.1  christos 					dns_rdataset_disassociate(&ardataset);
    489   1.4  christos 				}
    490   1.1  christos 			} else {
    491   1.9  christos 				if (result == DNS_R_NOTEXACT) {
    492   1.9  christos 					dns_name_format(name, namebuf,
    493   1.9  christos 							sizeof(namebuf));
    494   1.9  christos 					dns_rdatatype_format(type, typebuf,
    495   1.9  christos 							     sizeof(typebuf));
    496   1.9  christos 					dns_rdataclass_format(rdclass, classbuf,
    497   1.9  christos 							      sizeof(classbuf));
    498   1.9  christos 					isc_log_write(
    499   1.9  christos 						DIFF_COMMON_LOGARGS,
    500   1.9  christos 						ISC_LOG_ERROR,
    501   1.9  christos 						"dns_diff_apply: %s/%s/%s: %s "
    502   1.9  christos 						"%s",
    503   1.9  christos 						namebuf, typebuf, classbuf,
    504   1.9  christos 						optotext(op),
    505   1.9  christos 						isc_result_totext(result));
    506   1.9  christos 				}
    507   1.4  christos 				if (dns_rdataset_isassociated(&ardataset)) {
    508   1.1  christos 					dns_rdataset_disassociate(&ardataset);
    509   1.4  christos 				}
    510   1.1  christos 				CHECK(result);
    511   1.1  christos 			}
    512   1.1  christos 			dns_db_detachnode(db, &node);
    513   1.4  christos 			if (dns_rdataset_isassociated(&ardataset)) {
    514   1.1  christos 				dns_rdataset_disassociate(&ardataset);
    515   1.4  christos 			}
    516   1.1  christos 		}
    517   1.1  christos 	}
    518  1.10  christos 	return ISC_R_SUCCESS;
    519   1.1  christos 
    520   1.4  christos failure:
    521   1.4  christos 	if (node != NULL) {
    522   1.1  christos 		dns_db_detachnode(db, &node);
    523   1.4  christos 	}
    524  1.10  christos 	return result;
    525   1.1  christos }
    526   1.1  christos 
    527   1.1  christos isc_result_t
    528  1.10  christos dns_diff_apply(const dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *ver) {
    529  1.10  christos 	return diff_apply(diff, db, ver, true);
    530   1.1  christos }
    531   1.1  christos 
    532   1.1  christos isc_result_t
    533  1.10  christos dns_diff_applysilently(const dns_diff_t *diff, dns_db_t *db,
    534  1.10  christos 		       dns_dbversion_t *ver) {
    535  1.10  christos 	return diff_apply(diff, db, ver, false);
    536   1.1  christos }
    537   1.1  christos 
    538   1.1  christos /* XXX this duplicates lots of code in diff_apply(). */
    539   1.1  christos 
    540   1.1  christos isc_result_t
    541  1.10  christos dns_diff_load(const dns_diff_t *diff, dns_rdatacallbacks_t *callbacks) {
    542   1.1  christos 	dns_difftuple_t *t;
    543   1.1  christos 	isc_result_t result;
    544   1.1  christos 
    545   1.1  christos 	REQUIRE(DNS_DIFF_VALID(diff));
    546   1.1  christos 
    547  1.10  christos 	if (callbacks->setup != NULL) {
    548  1.10  christos 		callbacks->setup(callbacks->add_private);
    549  1.10  christos 	}
    550  1.10  christos 
    551   1.1  christos 	t = ISC_LIST_HEAD(diff->tuples);
    552   1.1  christos 	while (t != NULL) {
    553   1.1  christos 		dns_name_t *name;
    554   1.1  christos 
    555   1.1  christos 		name = &t->name;
    556   1.3  christos 		while (t != NULL && dns_name_caseequal(&t->name, name)) {
    557   1.1  christos 			dns_rdatatype_t type, covers;
    558   1.1  christos 			dns_diffop_t op;
    559   1.1  christos 			dns_rdatalist_t rdl;
    560   1.1  christos 			dns_rdataset_t rds;
    561   1.1  christos 
    562   1.1  christos 			op = t->op;
    563   1.1  christos 			type = t->rdata.type;
    564   1.1  christos 			covers = rdata_covers(&t->rdata);
    565   1.1  christos 
    566   1.1  christos 			dns_rdatalist_init(&rdl);
    567   1.1  christos 			rdl.type = type;
    568   1.1  christos 			rdl.covers = covers;
    569   1.1  christos 			rdl.rdclass = t->rdata.rdclass;
    570   1.1  christos 			rdl.ttl = t->ttl;
    571   1.1  christos 
    572   1.4  christos 			while (t != NULL &&
    573   1.4  christos 			       dns_name_caseequal(&t->name, name) &&
    574   1.1  christos 			       t->op == op && t->rdata.type == type &&
    575   1.1  christos 			       rdata_covers(&t->rdata) == covers)
    576   1.1  christos 			{
    577   1.1  christos 				ISC_LIST_APPEND(rdl.rdata, &t->rdata, link);
    578   1.1  christos 				t = ISC_LIST_NEXT(t, link);
    579   1.1  christos 			}
    580   1.1  christos 
    581   1.1  christos 			/*
    582   1.1  christos 			 * Convert the rdatalist into a rdataset.
    583   1.1  christos 			 */
    584   1.1  christos 			dns_rdataset_init(&rds);
    585  1.10  christos 			dns_rdatalist_tordataset(&rdl, &rds);
    586   1.1  christos 			rds.trust = dns_trust_ultimate;
    587   1.1  christos 
    588   1.1  christos 			INSIST(op == DNS_DIFFOP_ADD);
    589  1.10  christos 			result = callbacks->add(callbacks->add_private, name,
    590  1.10  christos 						&rds DNS__DB_FILELINE);
    591   1.1  christos 			if (result == DNS_R_UNCHANGED) {
    592   1.1  christos 				isc_log_write(DIFF_COMMON_LOGARGS,
    593   1.1  christos 					      ISC_LOG_WARNING,
    594   1.1  christos 					      "dns_diff_load: "
    595   1.1  christos 					      "update with no effect");
    596   1.1  christos 			} else if (result == ISC_R_SUCCESS ||
    597   1.8  christos 				   result == DNS_R_NXRRSET)
    598   1.8  christos 			{
    599   1.1  christos 				/*
    600   1.1  christos 				 * OK.
    601   1.1  christos 				 */
    602   1.1  christos 			} else {
    603   1.1  christos 				CHECK(result);
    604   1.1  christos 			}
    605   1.1  christos 		}
    606   1.1  christos 	}
    607   1.1  christos 	result = ISC_R_SUCCESS;
    608  1.10  christos 
    609   1.4  christos failure:
    610  1.10  christos 	if (callbacks->commit != NULL) {
    611  1.10  christos 		callbacks->commit(callbacks->add_private);
    612  1.10  christos 	}
    613  1.10  christos 	return result;
    614   1.1  christos }
    615   1.1  christos 
    616   1.1  christos /*
    617   1.1  christos  * XXX uses qsort(); a merge sort would be more natural for lists,
    618   1.1  christos  * and perhaps safer wrt thread stack overflow.
    619   1.1  christos  */
    620   1.1  christos isc_result_t
    621   1.1  christos dns_diff_sort(dns_diff_t *diff, dns_diff_compare_func *compare) {
    622   1.1  christos 	unsigned int length = 0;
    623   1.1  christos 	unsigned int i;
    624   1.1  christos 	dns_difftuple_t **v;
    625   1.1  christos 	dns_difftuple_t *p;
    626   1.1  christos 	REQUIRE(DNS_DIFF_VALID(diff));
    627   1.1  christos 
    628   1.4  christos 	for (p = ISC_LIST_HEAD(diff->tuples); p != NULL;
    629   1.8  christos 	     p = ISC_LIST_NEXT(p, link))
    630   1.8  christos 	{
    631   1.1  christos 		length++;
    632   1.4  christos 	}
    633   1.4  christos 	if (length == 0) {
    634  1.10  christos 		return ISC_R_SUCCESS;
    635   1.4  christos 	}
    636  1.10  christos 	v = isc_mem_cget(diff->mctx, length, sizeof(dns_difftuple_t *));
    637   1.1  christos 	for (i = 0; i < length; i++) {
    638   1.1  christos 		p = ISC_LIST_HEAD(diff->tuples);
    639   1.1  christos 		v[i] = p;
    640   1.1  christos 		ISC_LIST_UNLINK(diff->tuples, p, link);
    641   1.1  christos 	}
    642   1.1  christos 	INSIST(ISC_LIST_HEAD(diff->tuples) == NULL);
    643   1.1  christos 	qsort(v, length, sizeof(v[0]), compare);
    644   1.1  christos 	for (i = 0; i < length; i++) {
    645   1.1  christos 		ISC_LIST_APPEND(diff->tuples, v[i], link);
    646   1.1  christos 	}
    647  1.10  christos 	isc_mem_cput(diff->mctx, v, length, sizeof(dns_difftuple_t *));
    648  1.10  christos 	return ISC_R_SUCCESS;
    649   1.1  christos }
    650   1.1  christos 
    651   1.1  christos /*
    652   1.1  christos  * Create an rdataset containing the single RR of the given
    653   1.1  christos  * tuple.  The caller must allocate the rdata, rdataset and
    654   1.1  christos  * an rdatalist structure for it to refer to.
    655   1.1  christos  */
    656   1.1  christos 
    657  1.10  christos static void
    658   1.1  christos diff_tuple_tordataset(dns_difftuple_t *t, dns_rdata_t *rdata,
    659   1.4  christos 		      dns_rdatalist_t *rdl, dns_rdataset_t *rds) {
    660   1.1  christos 	REQUIRE(DNS_DIFFTUPLE_VALID(t));
    661   1.1  christos 	REQUIRE(rdl != NULL);
    662   1.1  christos 	REQUIRE(rds != NULL);
    663   1.1  christos 
    664   1.1  christos 	dns_rdatalist_init(rdl);
    665   1.1  christos 	rdl->type = t->rdata.type;
    666   1.1  christos 	rdl->rdclass = t->rdata.rdclass;
    667   1.1  christos 	rdl->ttl = t->ttl;
    668   1.1  christos 	dns_rdataset_init(rds);
    669   1.1  christos 	ISC_LINK_INIT(rdata, link);
    670   1.1  christos 	dns_rdata_clone(&t->rdata, rdata);
    671   1.1  christos 	ISC_LIST_APPEND(rdl->rdata, rdata, link);
    672  1.10  christos 	dns_rdatalist_tordataset(rdl, rds);
    673   1.1  christos }
    674   1.1  christos 
    675   1.1  christos isc_result_t
    676  1.10  christos dns_diff_print(const dns_diff_t *diff, FILE *file) {
    677   1.1  christos 	isc_result_t result;
    678   1.1  christos 	dns_difftuple_t *t;
    679   1.1  christos 	char *mem = NULL;
    680   1.1  christos 	unsigned int size = 2048;
    681   1.1  christos 	const char *op = NULL;
    682   1.1  christos 
    683   1.1  christos 	REQUIRE(DNS_DIFF_VALID(diff));
    684   1.1  christos 
    685   1.1  christos 	mem = isc_mem_get(diff->mctx, size);
    686   1.1  christos 
    687   1.1  christos 	for (t = ISC_LIST_HEAD(diff->tuples); t != NULL;
    688   1.8  christos 	     t = ISC_LIST_NEXT(t, link))
    689   1.8  christos 	{
    690   1.1  christos 		isc_buffer_t buf;
    691   1.1  christos 		isc_region_t r;
    692   1.1  christos 
    693   1.1  christos 		dns_rdatalist_t rdl;
    694   1.1  christos 		dns_rdataset_t rds;
    695   1.1  christos 		dns_rdata_t rd = DNS_RDATA_INIT;
    696   1.1  christos 
    697  1.10  christos 		diff_tuple_tordataset(t, &rd, &rdl, &rds);
    698   1.4  christos 	again:
    699   1.1  christos 		isc_buffer_init(&buf, mem, size);
    700   1.4  christos 		result = dns_rdataset_totext(&rds, &t->name, false, false,
    701   1.4  christos 					     &buf);
    702   1.1  christos 
    703   1.1  christos 		if (result == ISC_R_NOSPACE) {
    704   1.1  christos 			isc_mem_put(diff->mctx, mem, size);
    705   1.1  christos 			size += 1024;
    706   1.1  christos 			mem = isc_mem_get(diff->mctx, size);
    707   1.1  christos 			goto again;
    708   1.1  christos 		}
    709   1.1  christos 
    710   1.4  christos 		if (result != ISC_R_SUCCESS) {
    711   1.1  christos 			goto cleanup;
    712   1.4  christos 		}
    713   1.1  christos 		/*
    714   1.1  christos 		 * Get rid of final newline.
    715   1.1  christos 		 */
    716   1.1  christos 		INSIST(buf.used >= 1 &&
    717   1.4  christos 		       ((char *)buf.base)[buf.used - 1] == '\n');
    718   1.1  christos 		buf.used--;
    719   1.1  christos 
    720   1.1  christos 		isc_buffer_usedregion(&buf, &r);
    721   1.1  christos 		switch (t->op) {
    722   1.4  christos 		case DNS_DIFFOP_EXISTS:
    723   1.4  christos 			op = "exists";
    724   1.4  christos 			break;
    725   1.4  christos 		case DNS_DIFFOP_ADD:
    726   1.4  christos 			op = "add";
    727   1.4  christos 			break;
    728   1.4  christos 		case DNS_DIFFOP_DEL:
    729   1.4  christos 			op = "del";
    730   1.4  christos 			break;
    731   1.4  christos 		case DNS_DIFFOP_ADDRESIGN:
    732   1.4  christos 			op = "add re-sign";
    733   1.4  christos 			break;
    734   1.4  christos 		case DNS_DIFFOP_DELRESIGN:
    735   1.4  christos 			op = "del re-sign";
    736   1.4  christos 			break;
    737   1.1  christos 		}
    738   1.4  christos 		if (file != NULL) {
    739   1.4  christos 			fprintf(file, "%s %.*s\n", op, (int)r.length,
    740   1.4  christos 				(char *)r.base);
    741   1.4  christos 		} else {
    742   1.1  christos 			isc_log_write(DIFF_COMMON_LOGARGS, ISC_LOG_DEBUG(7),
    743   1.4  christos 				      "%s %.*s", op, (int)r.length,
    744   1.4  christos 				      (char *)r.base);
    745   1.4  christos 		}
    746   1.1  christos 	}
    747   1.1  christos 	result = ISC_R_SUCCESS;
    748   1.4  christos cleanup:
    749   1.4  christos 	if (mem != NULL) {
    750   1.1  christos 		isc_mem_put(diff->mctx, mem, size);
    751   1.4  christos 	}
    752  1.10  christos 	return result;
    753   1.1  christos }
    754