/*
 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
 *
 * SPDX-License-Identifier: MPL-2.0 AND ISC
 *
 * 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.
 */

/*
 * Copyright (C) 2009-2015 Red Hat
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND AUTHORS 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.
 */

/*
 * Database API implementation. The interface is defined in lib/dns/db.h.
 *
 * dns_db_*() calls on database instances backed by this driver use
 * struct sampledb_methods to find appropriate function implementation.
 *
 * This example re-uses DB implementation from original BIND and blindly
 * proxies most of dns_db_*() calls to this underlying DB.
 * See struct sampledb below.
 */

#include "db.h"
#include <inttypes.h>
#include <stdbool.h>

#include <isc/string.h>
#include <isc/util.h>

#include <dns/db.h>
#include <dns/diff.h>
#include <dns/enumclass.h>
#include <dns/rdatalist.h>
#include <dns/rdatastruct.h>
#include <dns/soa.h>
#include <dns/types.h>

#include "instance.h"
#include "syncptr.h"
#include "util.h"

#define SAMPLEDB_MAGIC ISC_MAGIC('S', 'M', 'D', 'B')
#define VALID_SAMPLEDB(sampledb) \
	((sampledb) != NULL && (sampledb)->common.impmagic == SAMPLEDB_MAGIC)

struct sampledb {
	dns_db_t common;
	sample_instance_t *inst;

	/*
	 * Internal database implementation provided by BIND.
	 * Most dns_db_* calls (find(), createiterator(), etc.)
	 * are blindly forwarded to this RBT DB.
	 */
	dns_db_t *db;
};

typedef struct sampledb sampledb_t;

static void
destroy(dns_db_t *db) {
	sampledb_t *sampledb = (sampledb_t *)db;

	REQUIRE(VALID_SAMPLEDB(sampledb));

	dns_db_detach(&sampledb->db);
	dns_name_free(&sampledb->common.origin, sampledb->common.mctx);
	isc_mem_putanddetach(&sampledb->common.mctx, sampledb,
			     sizeof(*sampledb));
}

static void
currentversion(dns_db_t *db, dns_dbversion_t **versionp) {
	sampledb_t *sampledb = (sampledb_t *)db;

	REQUIRE(VALID_SAMPLEDB(sampledb));

	dns_db_currentversion(sampledb->db, versionp);
}

static isc_result_t
newversion(dns_db_t *db, dns_dbversion_t **versionp) {
	sampledb_t *sampledb = (sampledb_t *)db;

	REQUIRE(VALID_SAMPLEDB(sampledb));

	return dns_db_newversion(sampledb->db, versionp);
}

static void
attachversion(dns_db_t *db, dns_dbversion_t *source,
	      dns_dbversion_t **targetp) {
	sampledb_t *sampledb = (sampledb_t *)db;

	REQUIRE(VALID_SAMPLEDB(sampledb));

	dns_db_attachversion(sampledb->db, source, targetp);
}

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

	REQUIRE(VALID_SAMPLEDB(sampledb));

	dns__db_closeversion(sampledb->db, versionp, commit DNS__DB_FLARG_PASS);
}

static isc_result_t
findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options,
	    isc_stdtime_t now, dns_dbnode_t **nodep, dns_name_t *foundname,
	    dns_name_t *dcname, dns_rdataset_t *rdataset,
	    dns_rdataset_t *sigrdataset DNS__DB_FLARG) {
	sampledb_t *sampledb = (sampledb_t *)db;

	REQUIRE(VALID_SAMPLEDB(sampledb));

	return dns__db_findzonecut(sampledb->db, name, options, now, nodep,
				   foundname, dcname, rdataset,
				   sigrdataset DNS__DB_FLARG_PASS);
}

static isc_result_t
createiterator(dns_db_t *db, unsigned int options,
	       dns_dbiterator_t **iteratorp) {
	sampledb_t *sampledb = (sampledb_t *)db;

	REQUIRE(VALID_SAMPLEDB(sampledb));

	return dns_db_createiterator(sampledb->db, options, iteratorp);
}

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) {
	sampledb_t *sampledb = (sampledb_t *)db;

	REQUIRE(VALID_SAMPLEDB(sampledb));

	return dns__db_findrdataset(sampledb->db, node, version, type, covers,
				    now, rdataset,
				    sigrdataset DNS__DB_FLARG_PASS);
}

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) {
	sampledb_t *sampledb = (sampledb_t *)db;

	REQUIRE(VALID_SAMPLEDB(sampledb));

	return dns__db_allrdatasets(sampledb->db, node, version, options, now,
				    iteratorp DNS__DB_FLARG_PASS);
}

static isc_result_t
addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
	    isc_stdtime_t now, dns_rdataset_t *rdataset, unsigned int options,
	    dns_rdataset_t *addedrdataset DNS__DB_FLARG) {
	sampledb_t *sampledb = (sampledb_t *)db;
	isc_result_t result;
	dns_fixedname_t name;

	REQUIRE(VALID_SAMPLEDB(sampledb));

	dns_fixedname_init(&name);
	CHECK(dns__db_addrdataset(sampledb->db, node, version, now, rdataset,
				  options, addedrdataset DNS__DB_FLARG_PASS));
	if (dns_rdatatype_isaddr(rdataset->type)) {
		CHECK(syncptrs(sampledb->inst, &node->name, rdataset,
			       DNS_DIFFOP_ADD));
	}

cleanup:
	return result;
}

static isc_result_t
subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
		 dns_rdataset_t *rdataset, unsigned int options,
		 dns_rdataset_t *newrdataset DNS__DB_FLARG) {
	sampledb_t *sampledb = (sampledb_t *)db;
	isc_result_t result;
	dns_fixedname_t name;

	REQUIRE(VALID_SAMPLEDB(sampledb));

	dns_fixedname_init(&name);
	result = dns__db_subtractrdataset(sampledb->db, node, version, rdataset,
					  options,
					  newrdataset DNS__DB_FLARG_PASS);
	if (result != ISC_R_SUCCESS && result != DNS_R_NXRRSET) {
		goto cleanup;
	}

	if (dns_rdatatype_isaddr(rdataset->type)) {
		CHECK(syncptrs(sampledb->inst, &node->name, rdataset,
			       DNS_DIFFOP_DEL));
	}

cleanup:
	return result;
}

/*
 * deleterdataset() function is not used during DNS update processing so syncptr
 * implementation is left as an exercise to the reader.
 */
static isc_result_t
deleterdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
	       dns_rdatatype_t type, dns_rdatatype_t covers DNS__DB_FLARG) {
	sampledb_t *sampledb = (sampledb_t *)db;

	REQUIRE(VALID_SAMPLEDB(sampledb));

	return dns__db_deleterdataset(sampledb->db, node, version, type,
				      covers DNS__DB_FLARG_PASS);
}

static bool
issecure(dns_db_t *db) {
	sampledb_t *sampledb = (sampledb_t *)db;

	REQUIRE(VALID_SAMPLEDB(sampledb));

	return dns_db_issecure(sampledb->db);
}

static unsigned int
nodecount(dns_db_t *db) {
	sampledb_t *sampledb = (sampledb_t *)db;

	REQUIRE(VALID_SAMPLEDB(sampledb));

	return dns_db_nodecount(sampledb->db);
}

static isc_result_t
getoriginnode(dns_db_t *db, dns_dbnode_t **nodep DNS__DB_FLARG) {
	sampledb_t *sampledb = (sampledb_t *)db;

	REQUIRE(VALID_SAMPLEDB(sampledb));

	return dns__db_getoriginnode(sampledb->db, nodep DNS__DB_FLARG_PASS);
}

static isc_result_t
getnsec3parameters(dns_db_t *db, dns_dbversion_t *version, dns_hash_t *hash,
		   uint8_t *flags, uint16_t *iterations, unsigned char *salt,
		   size_t *salt_length) {
	sampledb_t *sampledb = (sampledb_t *)db;

	REQUIRE(VALID_SAMPLEDB(sampledb));

	return dns_db_getnsec3parameters(sampledb->db, version, hash, flags,
					 iterations, salt, salt_length);
}

static isc_result_t
findnsec3node(dns_db_t *db, const dns_name_t *name, bool create,
	      dns_dbnode_t **nodep DNS__DB_FLARG) {
	sampledb_t *sampledb = (sampledb_t *)db;

	REQUIRE(VALID_SAMPLEDB(sampledb));

	return dns__db_findnsec3node(sampledb->db, name, create,
				     nodep DNS__DB_FLARG_PASS);
}

static isc_result_t
setsigningtime(dns_db_t *db, dns_rdataset_t *rdataset, isc_stdtime_t resign) {
	sampledb_t *sampledb = (sampledb_t *)db;

	REQUIRE(VALID_SAMPLEDB(sampledb));

	return dns_db_setsigningtime(sampledb->db, rdataset, resign);
}

static isc_result_t
getsigningtime(dns_db_t *db, isc_stdtime_t *resign, dns_name_t *name,
	       dns_typepair_t *type) {
	sampledb_t *sampledb = (sampledb_t *)db;

	REQUIRE(VALID_SAMPLEDB(sampledb));

	return dns_db_getsigningtime(sampledb->db, resign, name, type);
}

static dns_stats_t *
getrrsetstats(dns_db_t *db) {
	sampledb_t *sampledb = (sampledb_t *)db;

	REQUIRE(VALID_SAMPLEDB(sampledb));

	return dns_db_getrrsetstats(sampledb->db);
}

static isc_result_t
findnode(dns_db_t *db, const dns_name_t *name, bool create,
	 dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo,
	 dns_dbnode_t **nodep DNS__DB_FLARG) {
	sampledb_t *sampledb = (sampledb_t *)db;

	REQUIRE(VALID_SAMPLEDB(sampledb));

	return dns__db_findnode(sampledb->db, name, create, methods, clientinfo,
				nodep DNS__DB_FLARG_PASS);
}

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, dns_clientinfo_t *clientinfo,
     dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset DNS__DB_FLARG) {
	sampledb_t *sampledb = (sampledb_t *)db;

	REQUIRE(VALID_SAMPLEDB(sampledb));

	return dns__db_find(sampledb->db, name, version, type, options, now,
			    nodep, foundname, methods, clientinfo, rdataset,
			    sigrdataset DNS__DB_FLARG_PASS);
}

static isc_result_t
setcachestats(dns_db_t *db, isc_stats_t *stats) {
	sampledb_t *sampledb = (sampledb_t *)db;

	REQUIRE(VALID_SAMPLEDB(sampledb));

	return dns_db_setcachestats(sampledb->db, stats);
}

/*
 * DB interface definition. Database driver uses this structure to
 * determine which implementation of dns_db_*() function to call.
 */
static dns_dbmethods_t sampledb_methods = {
	.destroy = destroy,
	.currentversion = currentversion,
	.newversion = newversion,
	.attachversion = attachversion,
	.closeversion = closeversion,
	.findzonecut = findzonecut,
	.createiterator = createiterator,
	.findrdataset = findrdataset,
	.allrdatasets = allrdatasets,
	.addrdataset = addrdataset,
	.subtractrdataset = subtractrdataset,
	.deleterdataset = deleterdataset,
	.issecure = issecure,
	.nodecount = nodecount,
	.getoriginnode = getoriginnode,
	.getnsec3parameters = getnsec3parameters,
	.findnsec3node = findnsec3node,
	.setsigningtime = setsigningtime,
	.getsigningtime = getsigningtime,
	.getrrsetstats = getrrsetstats,
	.findnode = findnode,
	.find = find,
	.setcachestats = setcachestats,
};

/* Auxiliary driver functions. */

/*
 * Auxiliary functions add_*() create minimal database which can be loaded.
 * This is necessary because this driver create empty 'fake' zone which
 * is not loaded from disk so there is no way for user to supply SOA, NS and A
 * records.
 *
 * Following functions were copied from BIND 9.10.2rc1 named/server.c,
 * credit goes to ISC.
 */
static isc_result_t
add_soa(dns_db_t *db, dns_dbversion_t *version, const dns_name_t *name,
	const dns_name_t *origin, const dns_name_t *contact) {
	dns_dbnode_t *node = NULL;
	dns_rdata_t rdata = DNS_RDATA_INIT;
	dns_rdatalist_t rdatalist;
	dns_rdataset_t rdataset;
	isc_result_t result;
	unsigned char buf[DNS_SOA_BUFFERSIZE];

	dns_rdataset_init(&rdataset);
	dns_rdatalist_init(&rdatalist);
	CHECK(dns_soa_buildrdata(origin, contact, dns_db_class(db), 0, 28800,
				 7200, 604800, 86400, buf, &rdata));
	rdatalist.type = rdata.type;
	rdatalist.covers = dns_rdatatype_none;
	rdatalist.rdclass = rdata.rdclass;
	rdatalist.ttl = 86400;
	ISC_LIST_APPEND(rdatalist.rdata, &rdata, link);
	dns_rdatalist_tordataset(&rdatalist, &rdataset);
	CHECK(dns_db_findnode(db, name, true, &node));
	CHECK(dns_db_addrdataset(db, node, version, 0, &rdataset, 0, NULL));
cleanup:
	if (node != NULL) {
		dns_db_detachnode(&node);
	}
	return result;
}

static isc_result_t
add_ns(dns_db_t *db, dns_dbversion_t *version, const dns_name_t *name,
       const dns_name_t *nsname) {
	dns_dbnode_t *node = NULL;
	dns_rdata_ns_t ns;
	dns_rdata_t rdata = DNS_RDATA_INIT;
	dns_rdatalist_t rdatalist;
	dns_rdataset_t rdataset;
	isc_result_t result;
	isc_buffer_t b;
	unsigned char buf[DNS_NAME_MAXWIRE];

	isc_buffer_init(&b, buf, sizeof(buf));

	dns_rdataset_init(&rdataset);
	dns_rdatalist_init(&rdatalist);
	ns.common.rdtype = dns_rdatatype_ns;
	ns.common.rdclass = dns_db_class(db);
	ns.mctx = NULL;
	dns_name_init(&ns.name);
	dns_name_clone(nsname, &ns.name);
	CHECK(dns_rdata_fromstruct(&rdata, dns_db_class(db), dns_rdatatype_ns,
				   &ns, &b));
	rdatalist.type = rdata.type;
	rdatalist.covers = dns_rdatatype_none;
	rdatalist.rdclass = rdata.rdclass;
	rdatalist.ttl = 86400;
	ISC_LIST_APPEND(rdatalist.rdata, &rdata, link);
	dns_rdatalist_tordataset(&rdatalist, &rdataset);
	CHECK(dns_db_findnode(db, name, true, &node));
	CHECK(dns_db_addrdataset(db, node, version, 0, &rdataset, 0, NULL));
cleanup:
	if (node != NULL) {
		dns_db_detachnode(&node);
	}
	return result;
}

static isc_result_t
add_a(dns_db_t *db, dns_dbversion_t *version, const dns_name_t *name,
      struct in_addr addr) {
	dns_dbnode_t *node = NULL;
	dns_rdata_in_a_t a;
	dns_rdata_t rdata = DNS_RDATA_INIT;
	dns_rdatalist_t rdatalist;
	dns_rdataset_t rdataset;
	isc_result_t result;
	isc_buffer_t b;
	unsigned char buf[DNS_NAME_MAXWIRE];

	isc_buffer_init(&b, buf, sizeof(buf));

	dns_rdataset_init(&rdataset);
	dns_rdatalist_init(&rdatalist);
	a.common.rdtype = dns_rdatatype_a;
	a.common.rdclass = dns_db_class(db);
	a.in_addr = addr;
	CHECK(dns_rdata_fromstruct(&rdata, dns_db_class(db), dns_rdatatype_a,
				   &a, &b));
	rdatalist.type = rdata.type;
	rdatalist.covers = dns_rdatatype_none;
	rdatalist.rdclass = rdata.rdclass;
	rdatalist.ttl = 86400;
	ISC_LIST_APPEND(rdatalist.rdata, &rdata, link);
	dns_rdatalist_tordataset(&rdatalist, &rdataset);
	CHECK(dns_db_findnode(db, name, true, &node));
	CHECK(dns_db_addrdataset(db, node, version, 0, &rdataset, 0, NULL));
cleanup:
	if (node != NULL) {
		dns_db_detachnode(&node);
	}
	return result;
}

/*
 * Driver-specific implementation of dns_db_create().
 *
 * @param[in] argv      Database-specific parameters from dns_db_create().
 * @param[in] driverarg Driver-specific parameter from dns_db_register().
 */
isc_result_t
create_db(isc_mem_t *mctx, const dns_name_t *origin, dns_dbtype_t type,
	  dns_rdataclass_t rdclass, unsigned int argc, char *argv[],
	  void *driverarg, dns_db_t **dbp) {
	sampledb_t *sampledb = NULL;
	isc_result_t result;
	dns_dbversion_t *version = NULL;
	struct in_addr a_addr;

	REQUIRE(type == dns_dbtype_zone);
	REQUIRE(rdclass == dns_rdataclass_in);
	REQUIRE(argc == 0);
	REQUIRE(argv != NULL);
	REQUIRE(driverarg != NULL); /* pointer to driver instance */
	REQUIRE(dbp != NULL && *dbp == NULL);

	UNUSED(driverarg); /* no driver-specific configuration */

	a_addr.s_addr = 0x0100007fU;

	sampledb = isc_mem_get(mctx, sizeof(*sampledb));
	*sampledb = (sampledb_t){
		.common.magic = DNS_DB_MAGIC,
		.common.impmagic = SAMPLEDB_MAGIC,
		.common.methods = &sampledb_methods,
		.common.rdclass = rdclass,
	};

	isc_mem_attach(mctx, &sampledb->common.mctx);
	dns_name_init(&sampledb->common.origin);

	dns_name_dup(origin, mctx, &sampledb->common.origin);

	isc_refcount_init(&sampledb->common.references, 1);

	/* Translate instance name to instance pointer. */
	sampledb->inst = driverarg;

	/* Create internal instance of DB implementation from BIND. */
	CHECK(dns_db_create(mctx, ZONEDB_DEFAULT, origin, dns_dbtype_zone,
			    dns_rdataclass_in, 0, NULL, &sampledb->db));

	/* Create fake SOA, NS, and A records to make database loadable. */
	CHECK(dns_db_newversion(sampledb->db, &version));
	CHECK(add_soa(sampledb->db, version, origin, origin, origin));
	CHECK(add_ns(sampledb->db, version, origin, origin));
	CHECK(add_a(sampledb->db, version, origin, a_addr));
	dns_db_closeversion(sampledb->db, &version, true);

	*dbp = (dns_db_t *)sampledb;

	return ISC_R_SUCCESS;

cleanup:
	if (dns_name_dynamic(&sampledb->common.origin)) {
		dns_name_free(&sampledb->common.origin, mctx);
	}

	isc_mem_putanddetach(&sampledb->common.mctx, sampledb,
			     sizeof(*sampledb));

	return result;
}
