/*
 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
 *
 * SPDX-License-Identifier: MPL-2.0
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
 *
 * See the COPYRIGHT file distributed with this work for additional
 * information regarding copyright ownership.
 */

/*! \file
 * \brief
 * The built-in "version", "hostname", "id", "authors" and "empty" databases.
 */

#include <stdio.h>
#include <string.h>

#include <isc/lex.h>
#include <isc/mem.h>
#include <isc/result.h>
#include <isc/util.h>

#include <dns/callbacks.h>
#include <dns/dbiterator.h>
#include <dns/rdatalist.h>
#include <dns/rdatasetiter.h>
#include <dns/types.h>

#include <named/builtin.h>
#include <named/globals.h>
#include <named/os.h>
#include <named/server.h>

#define BDBNODE_MAGIC	    ISC_MAGIC('B', 'D', 'B', 'N')
#define VALID_BDBNODE(bdbl) ISC_MAGIC_VALID(bdbl, BDBNODE_MAGIC)

/*%
 * Note that "impmagic" is not the first four bytes of the struct, so
 * ISC_MAGIC_VALID cannot be used here.
 */
#define BDB_MAGIC      ISC_MAGIC('B', 'D', 'B', '-')
#define VALID_BDB(bdb) ((bdb) != NULL && (bdb)->common.impmagic == BDB_MAGIC)

#define BDB_DNS64 0x00000001U

typedef struct bdbimplementation {
	unsigned int flags;
	dns_dbimplementation_t *dbimp;
} bdbimplementation_t;

typedef struct bdbnode bdbnode_t;
typedef struct bdb {
	dns_db_t common;
	bdbimplementation_t *implementation;
	isc_result_t (*lookup)(bdbnode_t *node);
	char *server;
	char *contact;
} bdb_t;

struct bdbnode {
	DBNODE_FIELDS;

	isc_refcount_t references;
	bdb_t *bdb;
	ISC_LIST(dns_rdatalist_t) lists;
	ISC_LIST(isc_buffer_t) buffers;
	ISC_LINK(bdbnode_t) link;
	dns_rdatacallbacks_t callbacks;
};

typedef struct bdb_rdatasetiter {
	dns_rdatasetiter_t common;
	dns_rdatalist_t *current;
} bdb_rdatasetiter_t;

static isc_result_t
findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
	     dns_rdatatype_t type, dns_rdatatype_t covers, isc_stdtime_t now,
	     dns_rdataset_t *rdataset,
	     dns_rdataset_t *sigrdataset DNS__DB_FLARG);

/*
 * Node methods forward declarations
 */
static void
bdbnode_attachnode(dns_dbnode_t *source, dns_dbnode_t **targetp DNS__DB_FLARG);
static void
bdbnode_detachnode(dns_dbnode_t **nodep DNS__DB_FLARG);

/*
 * Node methods structure
 */
static dns_dbnode_methods_t bdbnode_methods = (dns_dbnode_methods_t){
	.attachnode = bdbnode_attachnode,
	.detachnode = bdbnode_detachnode,
};

/*
 * Helper functions to convert text to wire forma.
 */
static isc_result_t
putrdata(bdbnode_t *node, dns_rdatatype_t typeval, dns_ttl_t ttl,
	 const unsigned char *rdatap, unsigned int rdlen) {
	dns_rdatalist_t *rdatalist = NULL;
	dns_rdata_t *rdata = NULL;
	isc_buffer_t *rdatabuf = NULL;
	isc_mem_t *mctx = NULL;
	isc_region_t region;

	mctx = node->bdb->common.mctx;

	rdatalist = ISC_LIST_HEAD(node->lists);
	while (rdatalist != NULL) {
		if (rdatalist->type == typeval) {
			break;
		}
		rdatalist = ISC_LIST_NEXT(rdatalist, link);
	}

	if (rdatalist == NULL) {
		rdatalist = isc_mem_get(mctx, sizeof(dns_rdatalist_t));
		dns_rdatalist_init(rdatalist);
		rdatalist->rdclass = node->bdb->common.rdclass;
		rdatalist->type = typeval;
		rdatalist->ttl = ttl;
		ISC_LIST_APPEND(node->lists, rdatalist, link);
	} else if (rdatalist->ttl != ttl) {
		return DNS_R_BADTTL;
	}

	rdata = isc_mem_get(mctx, sizeof(dns_rdata_t));

	isc_buffer_allocate(mctx, &rdatabuf, rdlen);
	region.base = UNCONST(rdatap);
	region.length = rdlen;
	isc_buffer_copyregion(rdatabuf, &region);
	isc_buffer_usedregion(rdatabuf, &region);
	dns_rdata_init(rdata);
	dns_rdata_fromregion(rdata, rdatalist->rdclass, rdatalist->type,
			     &region);
	ISC_LIST_APPEND(rdatalist->rdata, rdata, link);
	ISC_LIST_APPEND(node->buffers, rdatabuf, link);

	return ISC_R_SUCCESS;
}

static isc_result_t
putrr(bdbnode_t *node, const char *type, dns_ttl_t ttl, const char *data) {
	isc_result_t result;
	dns_rdatatype_t typeval;
	isc_lex_t *lex = NULL;
	isc_mem_t *mctx = NULL;
	const dns_name_t *origin = NULL;
	isc_buffer_t *rb = NULL;
	isc_buffer_t b;

	REQUIRE(VALID_BDBNODE(node));
	REQUIRE(type != NULL);
	REQUIRE(data != NULL);

	mctx = node->bdb->common.mctx;
	origin = &node->bdb->common.origin;

	isc_constregion_t r = { .base = type, .length = strlen(type) };
	RETERR(dns_rdatatype_fromtext(&typeval, (isc_textregion_t *)&r));

	isc_lex_create(mctx, 64, &lex);

	size_t datalen = strlen(data);
	isc_buffer_constinit(&b, data, datalen);
	isc_buffer_add(&b, datalen);

	RETERR(isc_lex_openbuffer(lex, &b));

	isc_buffer_allocate(mctx, &rb, DNS_RDATA_MAXLENGTH);
	result = dns_rdata_fromtext(NULL, node->bdb->common.rdclass, typeval,
				    lex, origin, 0, mctx, rb, &node->callbacks);
	isc_lex_destroy(&lex);

	if (result == ISC_R_SUCCESS) {
		result = putrdata(node, typeval, ttl, isc_buffer_base(rb),
				  isc_buffer_usedlength(rb));
	}

	isc_buffer_free(&rb);

	return result;
}

/* Reasonable default SOA values */
#define DEFAULT_REFRESH 28800U	/* 8 hours */
#define DEFAULT_RETRY	7200U	/* 2 hours */
#define DEFAULT_EXPIRE	604800U /* 7 days */
#define DEFAULT_MINIMUM 86400U	/* 1 day */
#define DEFAULT_TTL	(60 * 60 * 24)

static isc_result_t
putsoa(bdbnode_t *node, const char *mname, const char *rname, uint32_t serial) {
	char str[2 * DNS_NAME_MAXTEXT + 5 * (sizeof("2147483647")) + 7];
	int n;

	REQUIRE(mname != NULL);
	REQUIRE(rname != NULL);

	n = snprintf(str, sizeof(str), "%s %s %u %u %u %u %u", mname, rname,
		     serial, DEFAULT_REFRESH, DEFAULT_RETRY, DEFAULT_EXPIRE,
		     DEFAULT_MINIMUM);
	if (n >= (int)sizeof(str) || n < 0) {
		return ISC_R_NOSPACE;
	}
	return putrr(node, "SOA", DEFAULT_TTL, str);
}

static isc_result_t
puttxt(bdbnode_t *node, const char *text) {
	unsigned char buf[256];
	unsigned int len = strlen(text);

	if (len > 255) {
		len = 255; /* Silently truncate */
	}
	buf[0] = len;
	memmove(&buf[1], text, len);
	return putrdata(node, dns_rdatatype_txt, 0, buf, len + 1);
}

/*
 * Builtin database implementation functions.
 */

/* Precomputed HEX * 16 or 1 table. */
static const unsigned char hex16[256] = {
	1, 1,	1,   1,	  1,   1,   1,	 1,   1,   1,	1, 1, 1, 1, 1, 1, /*00*/
	1, 1,	1,   1,	  1,   1,   1,	 1,   1,   1,	1, 1, 1, 1, 1, 1, /*10*/
	1, 1,	1,   1,	  1,   1,   1,	 1,   1,   1,	1, 1, 1, 1, 1, 1, /*20*/
	0, 16,	32,  48,  64,  80,  96,	 112, 128, 144, 1, 1, 1, 1, 1, 1, /*30*/
	1, 160, 176, 192, 208, 224, 240, 1,   1,   1,	1, 1, 1, 1, 1, 1, /*40*/
	1, 1,	1,   1,	  1,   1,   1,	 1,   1,   1,	1, 1, 1, 1, 1, 1, /*50*/
	1, 160, 176, 192, 208, 224, 240, 1,   1,   1,	1, 1, 1, 1, 1, 1, /*60*/
	1, 1,	1,   1,	  1,   1,   1,	 1,   1,   1,	1, 1, 1, 1, 1, 1, /*70*/
	1, 1,	1,   1,	  1,   1,   1,	 1,   1,   1,	1, 1, 1, 1, 1, 1, /*80*/
	1, 1,	1,   1,	  1,   1,   1,	 1,   1,   1,	1, 1, 1, 1, 1, 1, /*90*/
	1, 1,	1,   1,	  1,   1,   1,	 1,   1,   1,	1, 1, 1, 1, 1, 1, /*A0*/
	1, 1,	1,   1,	  1,   1,   1,	 1,   1,   1,	1, 1, 1, 1, 1, 1, /*B0*/
	1, 1,	1,   1,	  1,   1,   1,	 1,   1,   1,	1, 1, 1, 1, 1, 1, /*C0*/
	1, 1,	1,   1,	  1,   1,   1,	 1,   1,   1,	1, 1, 1, 1, 1, 1, /*D0*/
	1, 1,	1,   1,	  1,   1,   1,	 1,   1,   1,	1, 1, 1, 1, 1, 1, /*E0*/
	1, 1,	1,   1,	  1,   1,   1,	 1,   1,   1,	1, 1, 1, 1, 1, 1  /*F0*/
};

static const unsigned char decimal[] = "0123456789";
static const unsigned char ipv4only[] = "\010ipv4only\004arpa";

static size_t
dns64_rdata(unsigned char *v, size_t start, unsigned char *rdata) {
	size_t i, j = 0;

	for (i = 0; i < 4U; i++) {
		unsigned char c = v[start++];
		if (start == 7U) {
			start++;
		}
		if (c > 99) {
			rdata[j++] = 3;
			rdata[j++] = decimal[c / 100];
			c = c % 100;
			rdata[j++] = decimal[c / 10];
			c = c % 10;
			rdata[j++] = decimal[c];
		} else if (c > 9) {
			rdata[j++] = 2;
			rdata[j++] = decimal[c / 10];
			c = c % 10;
			rdata[j++] = decimal[c];
		} else {
			rdata[j++] = 1;
			rdata[j++] = decimal[c];
		}
	}
	memmove(&rdata[j], "\07in-addr\04arpa", 14);
	return j + 14;
}

static isc_result_t
dns64_cname(const dns_name_t *zone, const dns_name_t *name, bdbnode_t *node) {
	size_t zlen, nlen, j, len;
	unsigned char v[16], n;
	unsigned int i;
	unsigned char rdata[sizeof("123.123.123.123.in-addr.arpa.")];
	unsigned char *ndata;

	/*
	 * The combined length of the zone and name is 74.
	 *
	 * The minimum zone length is 10 ((3)ip6(4)arpa(0)).
	 *
	 * The length of name should always be even as we are expecting
	 * a series of nibbles.
	 */
	zlen = zone->length;
	nlen = name->length;
	if ((zlen + nlen) > 74U || zlen < 10U || (nlen % 2) != 0U) {
		return ISC_R_NOTFOUND;
	}

	/*
	 * Check that name is a series of nibbles.
	 * Compute the byte values that correspond to the nibbles as we go.
	 *
	 * Shift the final result 4 bits, by setting 'i' to 1, if we if we
	 * have a odd number of nibbles so that "must be zero" tests below
	 * are byte aligned and we correctly return ISC_R_NOTFOUND or
	 * ISC_R_SUCCESS.  We will not generate a CNAME in this case.
	 */
	ndata = name->ndata;
	i = (nlen % 4) == 2U ? 1 : 0;
	j = nlen;
	memset(v, 0, sizeof(v));
	while (j != 0U) {
		INSIST((i / 2) < sizeof(v));
		if (ndata[0] != 1) {
			return ISC_R_NOTFOUND;
		}
		n = hex16[ndata[1] & 0xff];
		if (n == 1) {
			return ISC_R_NOTFOUND;
		}
		v[i / 2] = n | (v[i / 2] >> 4);
		j -= 2;
		ndata += 2;
		i++;
	}

	/*
	 * If we get here then we know name only consisted of nibbles.
	 * Now we need to determine if the name exists or not and whether
	 * it corresponds to a empty node in the zone or there should be
	 * a CNAME.
	 */
#define ZLEN(x) (10 + (x) / 2)
	switch (zlen) {
	case ZLEN(32): /* prefix len 32 */
		/*
		 * The nibbles that map to this byte must be zero for 'name'
		 * to exist in the zone.
		 */
		if (nlen > 16U && v[(nlen - 1) / 4 - 4] != 0) {
			return ISC_R_NOTFOUND;
		}
		/*
		 * If the total length is not 74 then this is a empty node
		 * so return success.
		 */
		if (nlen + zlen != 74U) {
			return ISC_R_SUCCESS;
		}
		len = dns64_rdata(v, 8, rdata);
		break;
	case ZLEN(40): /* prefix len 40 */
		/*
		 * The nibbles that map to this byte must be zero for 'name'
		 * to exist in the zone.
		 */
		if (nlen > 12U && v[(nlen - 1) / 4 - 3] != 0) {
			return ISC_R_NOTFOUND;
		}
		/*
		 * If the total length is not 74 then this is a empty node
		 * so return success.
		 */
		if (nlen + zlen != 74U) {
			return ISC_R_SUCCESS;
		}
		len = dns64_rdata(v, 6, rdata);
		break;
	case ZLEN(48): /* prefix len 48 */
		/*
		 * The nibbles that map to this byte must be zero for 'name'
		 * to exist in the zone.
		 */
		if (nlen > 8U && v[(nlen - 1) / 4 - 2] != 0) {
			return ISC_R_NOTFOUND;
		}
		/*
		 * If the total length is not 74 then this is a empty node
		 * so return success.
		 */
		if (nlen + zlen != 74U) {
			return ISC_R_SUCCESS;
		}
		len = dns64_rdata(v, 5, rdata);
		break;
	case ZLEN(56): /* prefix len 56 */
		/*
		 * The nibbles that map to this byte must be zero for 'name'
		 * to exist in the zone.
		 */
		if (nlen > 4U && v[(nlen - 1) / 4 - 1] != 0) {
			return ISC_R_NOTFOUND;
		}
		/*
		 * If the total length is not 74 then this is a empty node
		 * so return success.
		 */
		if (nlen + zlen != 74U) {
			return ISC_R_SUCCESS;
		}
		len = dns64_rdata(v, 4, rdata);
		break;
	case ZLEN(64): /* prefix len 64 */
		/*
		 * The nibbles that map to this byte must be zero for 'name'
		 * to exist in the zone.
		 */
		if (v[(nlen - 1) / 4] != 0) {
			return ISC_R_NOTFOUND;
		}
		/*
		 * If the total length is not 74 then this is a empty node
		 * so return success.
		 */
		if (nlen + zlen != 74U) {
			return ISC_R_SUCCESS;
		}
		len = dns64_rdata(v, 3, rdata);
		break;
	case ZLEN(96): /* prefix len 96 */
		/*
		 * If the total length is not 74 then this is a empty node
		 * so return success.
		 */
		if (nlen + zlen != 74U) {
			return ISC_R_SUCCESS;
		}
		len = dns64_rdata(v, 0, rdata);
		break;
	default:
		/*
		 * This should never be reached unless someone adds a
		 * zone declaration with this internal type to named.conf.
		 */
		return ISC_R_NOTFOUND;
	}

	/*
	 * Reverse of 192.0.0.170 or 192.0.0.171 maps to ipv4only.arpa.
	 */
	if ((v[0] == 170 || v[0] == 171) && v[1] == 0 && v[2] == 0 &&
	    v[3] == 192)
	{
		return putrdata(node, dns_rdatatype_ptr, 3600, ipv4only,
				sizeof(ipv4only));
	}

	return putrdata(node, dns_rdatatype_cname, 600, rdata,
			(unsigned int)len);
}

static isc_result_t
builtin_lookup(bdb_t *bdb, const dns_name_t *name, bdbnode_t *node) {
	if (name->length == 0) {
		return bdb->lookup(node);
	} else if ((node->bdb->implementation->flags & BDB_DNS64) != 0) {
		return dns64_cname(&bdb->common.origin, name, node);
	} else {
		return ISC_R_NOTFOUND;
	}
}

static isc_result_t
builtin_authority(bdb_t *bdb, bdbnode_t *node) {
	isc_result_t result;
	const char *contact = "hostmaster";
	const char *server = "@";

	if (bdb->server != NULL) {
		server = bdb->server;
	}
	if (bdb->contact != NULL) {
		contact = bdb->contact;
	}

	result = putsoa(node, server, contact, 0);
	if (result != ISC_R_SUCCESS) {
		return ISC_R_FAILURE;
	}

	result = putrr(node, "NS", 0, server);
	if (result != ISC_R_SUCCESS) {
		return ISC_R_FAILURE;
	}

	return ISC_R_SUCCESS;
}

static isc_result_t
version_lookup(bdbnode_t *node) {
	if (named_g_server->version_set) {
		if (named_g_server->version == NULL) {
			return ISC_R_SUCCESS;
		} else {
			return puttxt(node, named_g_server->version);
		}
	} else {
		return puttxt(node, PACKAGE_VERSION);
	}
}

static isc_result_t
hostname_lookup(bdbnode_t *node) {
	if (named_g_server->hostname_set) {
		if (named_g_server->hostname == NULL) {
			return ISC_R_SUCCESS;
		} else {
			return puttxt(node, named_g_server->hostname);
		}
	} else {
		char buf[256];
		if (gethostname(buf, sizeof(buf)) != 0) {
			return ISC_R_FAILURE;
		}
		return puttxt(node, buf);
	}
}

static isc_result_t
authors_lookup(bdbnode_t *node) {
	const char **p = NULL;
	static const char *authors[] = {
		"Mark Andrews",	      "Curtis Blackburn",
		"James Brister",      "Ben Cottrell",
		"John H. DuBois III", "Francis Dupont",
		"Michael Graff",      "Andreas Gustafsson",
		"Bob Halley",	      "Evan Hunt",
		"JINMEI Tatuya",      "Witold Krecicki",
		"David Lawrence",     "Scott Mann",
		"Danny Mayer",	      "Aydin Mercan",
		"Damien Neil",	      "Matt Nelson",
		"Jeremy C. Reed",     "Michael Sawyer",
		"Brian Wellington",   NULL
	};

	/*
	 * If a version string is specified, disable the authors.bind zone.
	 */
	if (named_g_server->version_set) {
		return ISC_R_SUCCESS;
	}

	for (p = authors; *p != NULL; p++) {
		RETERR(puttxt(node, *p));
	}
	return ISC_R_SUCCESS;
}

static isc_result_t
id_lookup(bdbnode_t *node) {
	if (named_g_server->sctx->usehostname) {
		char buf[256];
		if (gethostname(buf, sizeof(buf)) != 0) {
			return ISC_R_FAILURE;
		}
		return puttxt(node, buf);
	} else if (named_g_server->sctx->server_id != NULL) {
		return puttxt(node, named_g_server->sctx->server_id);
	} else {
		return ISC_R_SUCCESS;
	}
}

static isc_result_t
empty_lookup(bdbnode_t *node) {
	UNUSED(node);

	return ISC_R_SUCCESS;
}

static isc_result_t
ipv4only_lookup(bdbnode_t *node) {
	unsigned char data[2][4] = { { 192, 0, 0, 170 }, { 192, 0, 0, 171 } };

	for (int i = 0; i < 2; i++) {
		RETERR(putrdata(node, dns_rdatatype_a, 3600, data[i], 4));
	}
	return ISC_R_SUCCESS;
}

static isc_result_t
ipv4reverse_lookup(bdbnode_t *node) {
	isc_result_t result;

	result = putrdata(node, dns_rdatatype_ptr, 3600, ipv4only,
			  sizeof(ipv4only));
	return result;
}

/*
 * Rdataset implementation methods. An rdataset in the builtin databases is
 * implemented as an rdatalist which holds a reference to the dbnode,
 * to prevent the node being freed while the rdataset is still in use, so
 * we need local implementations of clone and disassociate but the rest of
 * the implementation can be the same as dns_rdatalist..
 */
static void
disassociate(dns_rdataset_t *rdataset DNS__DB_FLARG) {
	dns_dbnode_t *node = rdataset->rdlist.node;

	bdbnode_detachnode(&node DNS__DB_FLARG_PASS);
	dns_rdatalist_disassociate(rdataset DNS__DB_FLARG_PASS);
}

static void
rdataset_clone(const dns_rdataset_t *source,
	       dns_rdataset_t *target DNS__DB_FLARG) {
	dns_dbnode_t *node = source->rdlist.node;

	dns__rdatalist_clone(source, target DNS__DB_FLARG_PASS);
	bdbnode_attachnode(node, &target->rdlist.node DNS__DB_FLARG_PASS);
}

static dns_rdatasetmethods_t bdb_rdataset_methods = {
	.disassociate = disassociate,
	.first = dns__rdatalist_first,
	.next = dns__rdatalist_next,
	.current = dns__rdatalist_current,
	.clone = rdataset_clone,
	.count = dns__rdatalist_count,
	.addnoqname = dns__rdatalist_addnoqname,
	.getnoqname = dns__rdatalist_getnoqname,
};

static void
new_rdataset(dns_rdatalist_t *rdatalist, dns_db_t *db ISC_ATTR_UNUSED,
	     dns_dbnode_t *node, dns_rdataset_t *rdataset) {
	dns_rdatalist_tordataset(rdatalist, rdataset);

	rdataset->methods = &bdb_rdataset_methods;
	dns_db_attachnode(node, &rdataset->rdlist.node);
}

/*
 * Rdataset iterator methods
 */

static void
rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp DNS__DB_FLARG) {
	bdb_rdatasetiter_t *bdbiterator = (bdb_rdatasetiter_t *)(*iteratorp);
	bdbnode_detachnode(&bdbiterator->common.node DNS__DB_FLARG_PASS);
	isc_mem_put(bdbiterator->common.db->mctx, bdbiterator,
		    sizeof(bdb_rdatasetiter_t));
	*iteratorp = NULL;
}

static isc_result_t
rdatasetiter_first(dns_rdatasetiter_t *iterator DNS__DB_FLARG) {
	bdb_rdatasetiter_t *bdbiterator = (bdb_rdatasetiter_t *)iterator;
	bdbnode_t *bdbnode = (bdbnode_t *)iterator->node;

	if (ISC_LIST_EMPTY(bdbnode->lists)) {
		return ISC_R_NOMORE;
	}
	bdbiterator->current = ISC_LIST_HEAD(bdbnode->lists);
	return ISC_R_SUCCESS;
}

static isc_result_t
rdatasetiter_next(dns_rdatasetiter_t *iterator DNS__DB_FLARG) {
	bdb_rdatasetiter_t *bdbiterator = (bdb_rdatasetiter_t *)iterator;

	bdbiterator->current = ISC_LIST_NEXT(bdbiterator->current, link);
	if (bdbiterator->current == NULL) {
		return ISC_R_NOMORE;
	} else {
		return ISC_R_SUCCESS;
	}
}

static void
rdatasetiter_current(dns_rdatasetiter_t *iterator,
		     dns_rdataset_t *rdataset DNS__DB_FLARG) {
	bdb_rdatasetiter_t *bdbiterator = (bdb_rdatasetiter_t *)iterator;

	new_rdataset(bdbiterator->current, iterator->db, iterator->node,
		     rdataset);
}

static dns_rdatasetitermethods_t rdatasetiter_methods = {
	rdatasetiter_destroy, rdatasetiter_first, rdatasetiter_next,
	rdatasetiter_current
};

/*
 * Database implementation methods
 */
static void
destroy(dns_db_t *db) {
	bdb_t *bdb = (bdb_t *)db;
	isc_refcount_destroy(&bdb->common.references);

	if (bdb->server != NULL) {
		isc_mem_free(isc_g_mctx, bdb->server);
	}
	if (bdb->contact != NULL) {
		isc_mem_free(isc_g_mctx, bdb->contact);
	}

	bdb->common.magic = 0;
	bdb->common.impmagic = 0;

	dns_name_free(&bdb->common.origin, bdb->common.mctx);

	isc_mem_putanddetach(&bdb->common.mctx, bdb, sizeof(bdb_t));
}

/*
 * A dummy 'version' value is used so that dns_db_createversion()
 * can return a non-NULL version to the caller, but there can only be
 * one version of these databases, so the version value is never used.
 */
static int dummy;

static void
currentversion(dns_db_t *db, dns_dbversion_t **versionp) {
	bdb_t *bdb = (bdb_t *)db;

	REQUIRE(VALID_BDB(bdb));

	*versionp = (void *)&dummy;
	return;
}

static void
attachversion(dns_db_t *db, dns_dbversion_t *source,
	      dns_dbversion_t **targetp) {
	bdb_t *bdb = (bdb_t *)db;

	REQUIRE(VALID_BDB(bdb));
	REQUIRE(source != NULL && source == (void *)&dummy);
	REQUIRE(targetp != NULL && *targetp == NULL);

	*targetp = source;
	return;
}

static void
closeversion(dns_db_t *db, dns_dbversion_t **versionp,
	     bool commit DNS__DB_FLARG) {
	bdb_t *bdb = (bdb_t *)db;

	REQUIRE(VALID_BDB(bdb));
	REQUIRE(versionp != NULL && *versionp == (void *)&dummy);
	REQUIRE(!commit);

	*versionp = NULL;
}

static isc_result_t
createnode(bdb_t *bdb, bdbnode_t **nodep) {
	bdbnode_t *node = NULL;

	REQUIRE(VALID_BDB(bdb));

	node = isc_mem_get(bdb->common.mctx, sizeof(bdbnode_t));
	*node = (bdbnode_t){
		.lists = ISC_LIST_INITIALIZER,
		.buffers = ISC_LIST_INITIALIZER,
		.link = ISC_LINK_INITIALIZER,
	};

	dns_db_attach((dns_db_t *)bdb, (dns_db_t **)&node->bdb);
	dns_rdatacallbacks_init(&node->callbacks);

	isc_refcount_init(&node->references, 1);
	node->magic = BDBNODE_MAGIC;
	node->methods = &bdbnode_methods;

	*nodep = node;
	return ISC_R_SUCCESS;
}

static void
destroynode(bdbnode_t *node) {
	bdb_t *bdb = NULL;
	isc_mem_t *mctx = NULL;

	bdb = node->bdb;
	mctx = bdb->common.mctx;

	ISC_LIST_FOREACH(node->lists, list, link) {
		ISC_LIST_FOREACH(list->rdata, rdata, link) {
			ISC_LIST_UNLINK(list->rdata, rdata, link);
			isc_mem_put(mctx, rdata, sizeof(dns_rdata_t));
		}
		ISC_LIST_UNLINK(node->lists, list, link);
		isc_mem_put(mctx, list, sizeof(dns_rdatalist_t));
	}

	ISC_LIST_FOREACH(node->buffers, b, link) {
		ISC_LIST_UNLINK(node->buffers, b, link);
		isc_buffer_free(&b);
	}

	node->magic = 0;
	isc_mem_put(mctx, node, sizeof(bdbnode_t));
	dns_db_detach((dns_db_t **)(void *)&bdb);
}

static isc_result_t
findnode(dns_db_t *db, const dns_name_t *name, bool create,
	 dns_clientinfomethods_t *methods ISC_ATTR_UNUSED,
	 dns_clientinfo_t *clientinfo ISC_ATTR_UNUSED,
	 dns_dbnode_t **nodep DNS__DB_FLARG) {
	bdb_t *bdb = (bdb_t *)db;
	bdbnode_t *node = NULL;
	isc_result_t result;
	bool isorigin;
	dns_name_t relname;
	unsigned int labels;

	REQUIRE(VALID_BDB(bdb));
	REQUIRE(nodep != NULL && *nodep == NULL);

	UNUSED(create);

	isorigin = dns_name_equal(name, &bdb->common.origin);

	labels = dns_name_countlabels(name) - dns_name_countlabels(&db->origin);
	dns_name_init(&relname);
	dns_name_getlabelsequence(name, 0, labels, &relname);
	name = &relname;

	RETERR(createnode(bdb, &node));

	result = builtin_lookup(bdb, name, node);
	if (result != ISC_R_SUCCESS && (!isorigin || result != ISC_R_NOTFOUND))
	{
		destroynode(node);
		return result;
	}

	if (isorigin) {
		result = builtin_authority(bdb, node);
		if (result != ISC_R_SUCCESS) {
			destroynode(node);
			return result;
		}
	}

	*nodep = (dns_dbnode_t *)node;
	return ISC_R_SUCCESS;
}

static isc_result_t
find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
     dns_rdatatype_t type, unsigned int options, isc_stdtime_t now,
     dns_dbnode_t **nodep, dns_name_t *foundname,
     dns_clientinfomethods_t *methods ISC_ATTR_UNUSED,
     dns_clientinfo_t *clientinfo ISC_ATTR_UNUSED, dns_rdataset_t *rdataset,
     dns_rdataset_t *sigrdataset DNS__DB_FLARG) {
	bdb_t *bdb = (bdb_t *)db;
	isc_result_t result;
	dns_dbnode_t *node = NULL;
	dns_fixedname_t fname;
	dns_rdataset_t xrdataset;
	dns_name_t *xname = NULL;
	unsigned int nlabels, olabels, i;
	bool dns64;

	REQUIRE(VALID_BDB(bdb));
	REQUIRE(nodep == NULL || *nodep == NULL);
	REQUIRE(version == NULL || version == (dns_dbversion_t *)&dummy);

	if (!dns_name_issubdomain(name, &db->origin)) {
		return DNS_R_NXDOMAIN;
	}

	olabels = dns_name_countlabels(&db->origin);
	nlabels = dns_name_countlabels(name);

	xname = dns_fixedname_initname(&fname);

	if (rdataset == NULL) {
		dns_rdataset_init(&xrdataset);
		rdataset = &xrdataset;
	}

	result = DNS_R_NXDOMAIN;
	dns64 = ((bdb->implementation->flags & BDB_DNS64) != 0);
	for (i = (dns64 ? nlabels : olabels); i <= nlabels; i++) {
		/*
		 * Look up the next label.
		 */
		dns_name_getlabelsequence(name, nlabels - i, i, xname);
		result = findnode(db, xname, false, NULL, NULL,
				  &node DNS__DB_FLARG_PASS);
		if (result == ISC_R_NOTFOUND) {
			/*
			 * No data at zone apex?
			 */
			if (i == olabels) {
				return DNS_R_BADDB;
			}
			result = DNS_R_NXDOMAIN;
			continue;
		}
		if (result != ISC_R_SUCCESS) {
			return result;
		}

		/*
		 * DNS64 zones don't have DNAME or NS records.
		 */
		if (dns64) {
			goto skip;
		}

		/*
		 * Look for a DNAME at the current label, unless this is
		 * the qname.
		 */
		if (i < nlabels) {
			result = findrdataset(
				db, node, version, dns_rdatatype_dname, 0, now,
				rdataset, sigrdataset DNS__DB_FLARG_PASS);
			if (result == ISC_R_SUCCESS) {
				result = DNS_R_DNAME;
				break;
			}
		}

		/*
		 * Look for an NS at the current label, unless this is the
		 * origin or glue is ok.
		 */
		if (i != olabels && (options & DNS_DBFIND_GLUEOK) == 0) {
			result = findrdataset(
				db, node, version, dns_rdatatype_ns, 0, now,
				rdataset, sigrdataset DNS__DB_FLARG_PASS);
			if (result == ISC_R_SUCCESS) {
				if (i == nlabels && type == dns_rdatatype_any) {
					result = DNS_R_ZONECUT;
					dns_rdataset_cleanup(rdataset);
					dns_rdataset_cleanup(sigrdataset);
				} else {
					result = DNS_R_DELEGATION;
				}
				break;
			}
		}

		/*
		 * If the current name is not the qname, add another label
		 * and try again.
		 */
		if (i < nlabels) {
			destroynode((bdbnode_t *)node);
			node = NULL;
			continue;
		}

	skip:
		/*
		 * If we're looking for ANY, we're done.
		 */
		if (type == dns_rdatatype_any) {
			result = ISC_R_SUCCESS;
			break;
		}

		/*
		 * Look for the qtype.
		 */
		result = findrdataset(db, node, version, type, 0, now, rdataset,
				      sigrdataset DNS__DB_FLARG_PASS);
		if (result == ISC_R_SUCCESS) {
			break;
		}

		/*
		 * Look for a CNAME.
		 */
		if (type != dns_rdatatype_cname) {
			result = findrdataset(
				db, node, version, dns_rdatatype_cname, 0, now,
				rdataset, sigrdataset DNS__DB_FLARG_PASS);
			if (result == ISC_R_SUCCESS) {
				result = DNS_R_CNAME;
				break;
			}
		}

		result = DNS_R_NXRRSET;
		break;
	}

	if (rdataset == &xrdataset) {
		dns_rdataset_cleanup(rdataset);
	}

	if (foundname != NULL) {
		dns_name_copy(xname, foundname);
	}

	if (nodep != NULL) {
		*nodep = node;
	} else if (node != NULL) {
		bdbnode_detachnode(&node DNS__DB_FLARG_PASS);
	}

	return result;
}

static void
bdbnode_attachnode(dns_dbnode_t *source, dns_dbnode_t **targetp DNS__DB_FLARG) {
	bdbnode_t *node = (bdbnode_t *)source;

	isc_refcount_increment(&node->references);

	*targetp = source;
}

static void
bdbnode_detachnode(dns_dbnode_t **nodep DNS__DB_FLARG) {
	bdbnode_t *node = NULL;

	REQUIRE(nodep != NULL && *nodep != NULL);

	node = (bdbnode_t *)(*nodep);
	*nodep = NULL;

	if (isc_refcount_decrement(&node->references) == 1) {
		destroynode(node);
	}
}

static isc_result_t
findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
	     dns_rdatatype_t type, dns_rdatatype_t covers, isc_stdtime_t now,
	     dns_rdataset_t *rdataset,
	     dns_rdataset_t *sigrdataset DNS__DB_FLARG) {
	bdbnode_t *bdbnode = (bdbnode_t *)node;

	REQUIRE(VALID_BDBNODE(bdbnode));

	UNUSED(version);
	UNUSED(covers);
	UNUSED(now);
	UNUSED(sigrdataset);

	if (type == dns_rdatatype_rrsig) {
		return ISC_R_NOTIMPLEMENTED;
	}

	ISC_LIST_FOREACH(bdbnode->lists, list, link) {
		if (list->type == type) {
			new_rdataset(list, db, node, rdataset);
			return ISC_R_SUCCESS;
		}
	}

	return ISC_R_NOTFOUND;
}

static isc_result_t
allrdatasets(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
	     unsigned int options, isc_stdtime_t now,
	     dns_rdatasetiter_t **iteratorp DNS__DB_FLARG) {
	bdb_rdatasetiter_t *iterator = NULL;

	REQUIRE(version == NULL || version == (dns_dbversion_t *)&dummy);

	iterator = isc_mem_get(db->mctx, sizeof(bdb_rdatasetiter_t));
	*iterator = (bdb_rdatasetiter_t){
		.common.methods = &rdatasetiter_methods,
		.common.db = db,
		.common.version = version,
		.common.options = options,
		.common.now = now,
		.common.magic = DNS_RDATASETITER_MAGIC,
	};

	bdbnode_attachnode(node, &iterator->common.node DNS__DB_FLARG_PASS);

	*iteratorp = (dns_rdatasetiter_t *)iterator;

	return ISC_R_SUCCESS;
}

static dns_dbmethods_t bdb_methods = {
	.destroy = destroy,
	.currentversion = currentversion,
	.attachversion = attachversion,
	.closeversion = closeversion,
	.findrdataset = findrdataset,
	.allrdatasets = allrdatasets,
	.findnode = findnode,
	.find = find,
};

static isc_result_t
create(isc_mem_t *mctx, const dns_name_t *origin, dns_dbtype_t type,
       dns_rdataclass_t rdclass, unsigned int argc, char *argv[],
       void *implementation, dns_db_t **dbp) {
	isc_result_t result;
	bool needargs = false;
	bdb_t *bdb = NULL;

	REQUIRE(implementation != NULL);

	if (type != dns_dbtype_zone) {
		return ISC_R_NOTIMPLEMENTED;
	}

	bdb = isc_mem_get(mctx, sizeof(*bdb));
	*bdb = (bdb_t){
		.common = { .methods = &bdb_methods, .rdclass = rdclass },
		.implementation = implementation,
	};

	isc_refcount_init(&bdb->common.references, 1);
	isc_mem_attach(mctx, &bdb->common.mctx);
	dns_name_init(&bdb->common.origin);
	dns_name_dup(origin, mctx, &bdb->common.origin);

	INSIST(argc >= 1);
	if (strcmp(argv[0], "authors") == 0) {
		bdb->lookup = authors_lookup;
	} else if (strcmp(argv[0], "hostname") == 0) {
		bdb->lookup = hostname_lookup;
	} else if (strcmp(argv[0], "id") == 0) {
		bdb->lookup = id_lookup;
	} else if (strcmp(argv[0], "version") == 0) {
		bdb->lookup = version_lookup;
	} else if (strcmp(argv[0], "dns64") == 0) {
		needargs = true;
		bdb->lookup = empty_lookup;
	} else if (strcmp(argv[0], "empty") == 0) {
		needargs = true;
		bdb->lookup = empty_lookup;
	} else if (strcmp(argv[0], "ipv4only") == 0) {
		needargs = true;
		bdb->lookup = ipv4only_lookup;
	} else {
		needargs = true;
		bdb->lookup = ipv4reverse_lookup;
	}

	if (needargs) {
		if (argc != 3) {
			CLEANUP(DNS_R_SYNTAX);
		}

		bdb->server = isc_mem_strdup(isc_g_mctx, argv[1]);
		bdb->contact = isc_mem_strdup(isc_g_mctx, argv[2]);
	} else if (argc != 1) {
		CLEANUP(DNS_R_SYNTAX);
	}

	bdb->common.magic = DNS_DB_MAGIC;
	bdb->common.impmagic = BDB_MAGIC;

	*dbp = (dns_db_t *)bdb;

	return ISC_R_SUCCESS;

cleanup:
	dns_name_free(&bdb->common.origin, mctx);
	if (bdb->server != NULL) {
		isc_mem_free(isc_g_mctx, bdb->server);
	}
	if (bdb->contact != NULL) {
		isc_mem_free(isc_g_mctx, bdb->contact);
	}

	isc_mem_putanddetach(&bdb->common.mctx, bdb, sizeof(bdb_t));
	return result;
}

/*
 * Builtin database registration functions
 */
static bdbimplementation_t builtin = { .flags = 0 };
static bdbimplementation_t dns64 = { .flags = BDB_DNS64 };

isc_result_t
named_builtin_init(void) {
	isc_result_t result;

	RETERR(dns_db_register("_builtin", create, &builtin, isc_g_mctx,
			       &builtin.dbimp));

	result = dns_db_register("_dns64", create, &dns64, isc_g_mctx,
				 &dns64.dbimp);
	if (result != ISC_R_SUCCESS) {
		dns_db_unregister(&builtin.dbimp);
		return result;
	}

	return ISC_R_SUCCESS;
}

void
named_builtin_deinit(void) {
	if (builtin.dbimp != NULL) {
		dns_db_unregister(&builtin.dbimp);
	}
	if (dns64.dbimp != NULL) {
		dns_db_unregister(&dns64.dbimp);
	}
}
