summaryrefslogtreecommitdiffstats
path: root/contrib/dlz/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/dlz/drivers')
-rw-r--r--contrib/dlz/drivers/dlz_bdb_driver.c780
-rw-r--r--contrib/dlz/drivers/dlz_bdbhpt_driver.c849
-rw-r--r--contrib/dlz/drivers/dlz_dlopen_driver.c0
-rw-r--r--contrib/dlz/drivers/dlz_drivers.c156
-rw-r--r--contrib/dlz/drivers/dlz_filesystem_driver.c1000
-rw-r--r--contrib/dlz/drivers/dlz_ldap_driver.c1216
-rw-r--r--contrib/dlz/drivers/dlz_mysql_driver.c1037
-rw-r--r--contrib/dlz/drivers/dlz_odbc_driver.c1406
-rw-r--r--contrib/dlz/drivers/dlz_postgres_driver.c1240
-rw-r--r--contrib/dlz/drivers/dlz_stub_driver.c295
l---------contrib/dlz/drivers/include/.clang-format1
-rw-r--r--contrib/dlz/drivers/include/dlz/dlz_bdb_driver.h24
-rw-r--r--contrib/dlz/drivers/include/dlz/dlz_bdbhpt_driver.h24
-rw-r--r--contrib/dlz/drivers/include/dlz/dlz_dlopen_driver.h0
-rw-r--r--contrib/dlz/drivers/include/dlz/dlz_drivers.h26
-rw-r--r--contrib/dlz/drivers/include/dlz/dlz_filesystem_driver.h24
-rw-r--r--contrib/dlz/drivers/include/dlz/dlz_ldap_driver.h24
-rw-r--r--contrib/dlz/drivers/include/dlz/dlz_mysql_driver.h24
-rw-r--r--contrib/dlz/drivers/include/dlz/dlz_odbc_driver.h24
-rw-r--r--contrib/dlz/drivers/include/dlz/dlz_postgres_driver.h24
-rw-r--r--contrib/dlz/drivers/include/dlz/dlz_stub_driver.h24
-rw-r--r--contrib/dlz/drivers/include/dlz/sdlz_helper.h99
-rw-r--r--contrib/dlz/drivers/rules.in50
-rw-r--r--contrib/dlz/drivers/sdlz_helper.c468
24 files changed, 8815 insertions, 0 deletions
diff --git a/contrib/dlz/drivers/dlz_bdb_driver.c b/contrib/dlz/drivers/dlz_bdb_driver.c
new file mode 100644
index 0000000..5cce7ce
--- /dev/null
+++ b/contrib/dlz/drivers/dlz_bdb_driver.c
@@ -0,0 +1,780 @@
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ * 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.
+ */
+
+/*
+ * Copyright (C) 1999-2001, 2016 Internet Systems Consortium, Inc. ("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 http://mozilla.org/MPL/2.0/.
+ */
+
+#ifdef DLZ_BDB
+#include <db.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+#include <dns/log.h>
+#include <dns/result.h>
+#include <dns/sdlz.h>
+
+#include <dlz/dlz_bdb_driver.h>
+#include <named/globals.h>
+
+static dns_sdlzimplementation_t *dlz_bdb = NULL;
+
+/* should the bdb driver use threads. */
+#define bdb_threads DB_THREAD
+
+/* BDB database names */
+#define dlz_data "dns_data"
+#define dlz_zone "dns_zone"
+#define dlz_host "dns_host"
+#define dlz_client "dns_client"
+
+/*%
+ * This structure contains all the Berkeley DB handles
+ * for this instance of the BDB driver.
+ */
+
+typedef struct bdb_instance {
+ DB_ENV *dbenv; /*%< BDB environment */
+ DB *data; /*%< dns_data database handle */
+ DB *zone; /*%< zone database handle */
+ DB *host; /*%< host database handle */
+ DB *client; /*%< client database handle */
+ isc_mem_t *mctx; /*%< memory context */
+} bdb_instance_t;
+
+typedef struct parsed_data {
+ char *zone;
+ char *host;
+ char *type;
+ int ttl;
+ char *data;
+} parsed_data_t;
+
+/* forward reference */
+
+static isc_result_t
+bdb_findzone(void *driverarg, void *dbdata, const char *name,
+ dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo);
+
+/*%
+ * Parses the DBT from the Berkeley DB into a parsed_data record
+ * The parsed_data record should be allocated before and passed into the
+ * bdb_parse_data function. The char (type & data) fields should not
+ * be "free"d as that memory is part of the DBT data field. It will be
+ * "free"d when the DBT is freed.
+ */
+
+static isc_result_t
+bdb_parse_data(char *in, parsed_data_t *pd) {
+ char *endp, *ttlStr;
+ char *tmp = in;
+ char *lastchar = (char *)&tmp[strlen(tmp) + 1];
+
+ /*%
+ * String should be formatted as:
+ * zone(a space)host(a space)ttl(a space)type(a space)remaining data
+ * examples:
+ * example.com www 10 A 127.0.0.1
+ * example.com mail 10 A 127.0.0.2
+ * example.com @ 10 MX 20 mail.example.com
+ */
+
+ /* save pointer to zone */
+ pd->zone = tmp;
+
+ /* find space after zone and change it to a '\0' */
+ tmp = strchr(tmp, ' ');
+ /* verify we found a space */
+ if (tmp == NULL) {
+ return (ISC_R_FAILURE);
+ }
+ /* change the space to a null (string terminator) */
+ tmp[0] = '\0';
+ /* make sure it is safe to increment pointer */
+ if (++tmp > lastchar) {
+ return (ISC_R_FAILURE);
+ }
+
+ /* save pointer to host */
+ pd->host = tmp;
+
+ /* find space after type and change it to a '\0' */
+ tmp = strchr(tmp, ' ');
+ /* verify we found a space */
+ if (tmp == NULL) {
+ return (ISC_R_FAILURE);
+ }
+ /* change the space to a null (string terminator) */
+ tmp[0] = '\0';
+ /* make sure it is safe to increment pointer */
+ if (++tmp > lastchar) {
+ return (ISC_R_FAILURE);
+ }
+
+ /* save pointer to dns type */
+ pd->type = tmp;
+
+ /* find space after type and change it to a '\0' */
+ tmp = strchr(tmp, ' ');
+ /* verify we found a space */
+ if (tmp == NULL) {
+ return (ISC_R_FAILURE);
+ }
+ /* change the space to a null (string terminator) */
+ tmp[0] = '\0';
+ /* make sure it is safe to increment pointer */
+ if (++tmp > lastchar) {
+ return (ISC_R_FAILURE);
+ }
+
+ /* save pointer to dns ttl */
+ ttlStr = tmp;
+
+ /* find space after ttl and change it to a '\0' */
+ tmp = strchr(tmp, ' ');
+ /* verify we found a space */
+ if (tmp == NULL) {
+ return (ISC_R_FAILURE);
+ }
+ /* change the space to a null (string terminator) */
+ tmp[0] = '\0';
+ /* make sure it is safe to increment pointer */
+ if (++tmp > lastchar) {
+ return (ISC_R_FAILURE);
+ }
+
+ /* save pointer to remainder of DNS data */
+ pd->data = tmp;
+
+ /* convert ttl string to integer */
+ pd->ttl = strtol(ttlStr, &endp, 10);
+ if (*endp != '\0' || pd->ttl < 0) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "BDB driver ttl must be a positive number");
+ return (ISC_R_FAILURE);
+ }
+
+ /* if we get this far everything should have worked. */
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * DLZ methods
+ */
+
+static isc_result_t
+bdb_allowzonexfr(void *driverarg, void *dbdata, const char *name,
+ const char *client) {
+ isc_result_t result;
+ bdb_instance_t *db = (bdb_instance_t *)dbdata;
+ DBC *client_cursor = NULL;
+ DBT key, data;
+
+ /* check to see if we are authoritative for the zone first. */
+ result = bdb_findzone(driverarg, dbdata, name, NULL, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ memset(&key, 0, sizeof(DBT));
+ key.flags = DB_DBT_MALLOC;
+ key.data = strdup(name);
+ if (key.data == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto xfr_cleanup;
+ }
+ key.size = strlen(key.data);
+
+ memset(&data, 0, sizeof(DBT));
+ data.flags = DB_DBT_MALLOC;
+ data.data = strdup(client);
+ if (data.data == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto xfr_cleanup;
+ }
+ data.size = strlen(data.data);
+
+ /* get a cursor to loop through zone data */
+ if (db->client->cursor(db->client, NULL, &client_cursor, 0) != 0) {
+ result = ISC_R_FAILURE;
+ goto xfr_cleanup;
+ }
+
+ switch (client_cursor->c_get(client_cursor, &key, &data, DB_GET_BOTH)) {
+ case DB_NOTFOUND:
+ case DB_SECONDARY_BAD:
+ result = ISC_R_NOTFOUND;
+ break;
+ case 0:
+ result = ISC_R_SUCCESS;
+ break;
+ default:
+ result = ISC_R_FAILURE;
+ }
+
+xfr_cleanup:
+
+ /* free any memory duplicate string in the key field */
+ if (key.data != NULL) {
+ free(key.data);
+ }
+
+ /* free any memory allocated to the data field. */
+ if (data.data != NULL) {
+ free(data.data);
+ }
+
+ /* get rid of zone_cursor */
+ if (client_cursor != NULL) {
+ client_cursor->c_close(client_cursor);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+bdb_allnodes(const char *zone, void *driverarg, void *dbdata,
+ dns_sdlzallnodes_t *allnodes) {
+ isc_result_t result = ISC_R_NOTFOUND;
+ bdb_instance_t *db = (bdb_instance_t *)dbdata;
+ DBC *zone_cursor = NULL;
+ DBT key, data;
+ int flags;
+ int bdbres;
+ parsed_data_t pd;
+ char *tmp = NULL, *tmp_zone;
+
+ UNUSED(driverarg);
+
+ memset(&key, 0, sizeof(DBT));
+ memset(&data, 0, sizeof(DBT));
+
+ key.data = tmp_zone = strdup(zone);
+
+ if (key.data == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ key.size = strlen(key.data);
+
+ /* get a cursor to loop through zone data */
+ if (db->zone->cursor(db->zone, NULL, &zone_cursor, 0) != 0) {
+ result = ISC_R_FAILURE;
+ goto allnodes_cleanup;
+ }
+
+ flags = DB_SET;
+
+ while ((bdbres = zone_cursor->c_get(zone_cursor, &key, &data, flags)) ==
+ 0)
+ {
+ flags = DB_NEXT_DUP;
+
+ tmp = realloc(tmp, data.size + 1);
+ if (tmp == NULL) {
+ goto allnodes_cleanup;
+ }
+
+ strncpy(tmp, data.data, data.size);
+ tmp[data.size] = '\0';
+
+ if (bdb_parse_data(tmp, &pd) != ISC_R_SUCCESS) {
+ goto allnodes_cleanup;
+ }
+
+ result = dns_sdlz_putnamedrr(allnodes, pd.host, pd.type, pd.ttl,
+ pd.data);
+ if (result != ISC_R_SUCCESS) {
+ goto allnodes_cleanup;
+ }
+ } /* end while loop */
+
+allnodes_cleanup:
+
+ if (tmp != NULL) {
+ free(tmp);
+ }
+
+ /* free any memory duplicate string in the key field */
+ if (tmp_zone != NULL) {
+ free(tmp_zone);
+ }
+
+ /* get rid of zone_cursor */
+ if (zone_cursor != NULL) {
+ zone_cursor->c_close(zone_cursor);
+ }
+
+ return (result);
+}
+
+/*%
+ * Performs BDB cleanup.
+ * Used by bdb_create if there is an error starting up.
+ * Used by bdb_destroy when the driver is shutting down.
+ */
+
+static void
+bdb_cleanup(bdb_instance_t *db) {
+ isc_mem_t *mctx;
+
+ /* close databases */
+ if (db->data != NULL) {
+ db->data->close(db->data, 0);
+ }
+ if (db->host != NULL) {
+ db->host->close(db->host, 0);
+ }
+ if (db->zone != NULL) {
+ db->zone->close(db->zone, 0);
+ }
+ if (db->client != NULL) {
+ db->client->close(db->client, 0);
+ }
+
+ /* close environment */
+ if (db->dbenv != NULL) {
+ db->dbenv->close(db->dbenv, 0);
+ }
+
+ /* cleanup memory */
+ if (db->mctx != NULL) {
+ /* save mctx for later */
+ mctx = db->mctx;
+ /* return, and detach the memory */
+ isc_mem_putanddetach(&mctx, db, sizeof(bdb_instance_t));
+ }
+}
+
+static isc_result_t
+bdb_findzone(void *driverarg, void *dbdata, const char *name,
+ dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo) {
+ isc_result_t result;
+ bdb_instance_t *db = (bdb_instance_t *)dbdata;
+ DBC *zone_cursor = NULL;
+ DBT key, data;
+
+ UNUSED(driverarg);
+ UNUSED(methods);
+ UNUSED(clientinfo);
+
+ memset(&key, 0, sizeof(DBT));
+ memset(&data, 0, sizeof(DBT));
+ data.flags = DB_DBT_MALLOC;
+
+ key.data = strdup(name);
+
+ if (key.data == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ key.size = strlen(key.data);
+
+ /* get a cursor to loop through zone data */
+ if (db->zone->cursor(db->zone, NULL, &zone_cursor, 0) != 0) {
+ result = ISC_R_NOTFOUND;
+ goto findzone_cleanup;
+ }
+
+ switch (zone_cursor->c_get(zone_cursor, &key, &data, DB_SET)) {
+ case DB_NOTFOUND:
+ case DB_SECONDARY_BAD:
+ result = ISC_R_NOTFOUND;
+ break;
+ case 0:
+ result = ISC_R_SUCCESS;
+ break;
+ default:
+ result = ISC_R_FAILURE;
+ }
+
+findzone_cleanup:
+
+ /* free any memory duplicate string in the key field */
+ if (key.data != NULL) {
+ free(key.data);
+ }
+
+ /* free any memory allocated to the data field. */
+ if (data.data != NULL) {
+ free(data.data);
+ }
+
+ /* get rid of zone_cursor */
+ if (zone_cursor != NULL) {
+ zone_cursor->c_close(zone_cursor);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+bdb_lookup(const char *zone, const char *name, void *driverarg, void *dbdata,
+ dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo) {
+ isc_result_t result = ISC_R_NOTFOUND;
+ bdb_instance_t *db = (bdb_instance_t *)dbdata;
+ DBC *zone_cursor = NULL;
+ DBC *host_cursor = NULL;
+ DBC *join_cursor = NULL;
+ DBT key, data;
+ DBC *cur_arr[3];
+ int bdbres;
+ parsed_data_t pd;
+ char *tmp_zone, *tmp_host = NULL;
+ char *tmp = NULL;
+
+ UNUSED(driverarg);
+ UNUSED(methods);
+ UNUSED(clientinfo);
+
+ memset(&key, 0, sizeof(DBT));
+ memset(&data, 0, sizeof(DBT));
+
+ /* set zone key */
+ key.data = tmp_zone = strdup(zone);
+ if (key.data == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto lookup_cleanup;
+ }
+ key.size = strlen(key.data);
+
+ /* get a cursor to loop through zone data */
+ if (db->zone->cursor(db->zone, NULL, &zone_cursor, 0) != 0) {
+ result = ISC_R_FAILURE;
+ goto lookup_cleanup;
+ }
+
+ /* initialize zone_cursor with zone_key */
+ if (zone_cursor->c_get(zone_cursor, &key, &data, DB_SET) != 0) {
+ result = ISC_R_NOTFOUND;
+ goto lookup_cleanup;
+ }
+
+ /* set host key */
+ key.data = tmp_host = strdup(name);
+ if (key.data == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto lookup_cleanup;
+ }
+ key.size = strlen(key.data);
+
+ /* get a cursor to loop through host data */
+ if (db->host->cursor(db->host, NULL, &host_cursor, 0) != 0) {
+ result = ISC_R_FAILURE;
+ goto lookup_cleanup;
+ }
+
+ /* initialize host_cursor with host_key */
+ if (host_cursor->c_get(host_cursor, &key, &data, DB_SET) != 0) {
+ result = ISC_R_NOTFOUND;
+ goto lookup_cleanup;
+ }
+
+ cur_arr[0] = zone_cursor;
+ cur_arr[1] = host_cursor;
+ cur_arr[2] = NULL;
+
+ db->data->join(db->data, cur_arr, &join_cursor, 0);
+
+ while ((bdbres = join_cursor->c_get(join_cursor, &key, &data, 0)) == 0)
+ {
+ tmp = realloc(tmp, data.size + 1);
+ if (tmp == NULL) {
+ goto lookup_cleanup;
+ }
+
+ strncpy(tmp, data.data, data.size);
+ tmp[data.size] = '\0';
+
+ if (bdb_parse_data(tmp, &pd) != ISC_R_SUCCESS) {
+ goto lookup_cleanup;
+ }
+
+ result = dns_sdlz_putrr(lookup, pd.type, pd.ttl, pd.data);
+
+ if (result != ISC_R_SUCCESS) {
+ goto lookup_cleanup;
+ }
+ } /* end while loop */
+
+lookup_cleanup:
+
+ if (tmp != NULL) {
+ free(tmp);
+ }
+ if (tmp_zone != NULL) {
+ free(tmp_zone);
+ }
+ if (tmp_host != NULL) {
+ free(tmp_host);
+ }
+
+ /* get rid of the joined cusor */
+ if (join_cursor != NULL) {
+ join_cursor->c_close(join_cursor);
+ }
+
+ /* get rid of zone_cursor */
+ if (zone_cursor != NULL) {
+ zone_cursor->c_close(zone_cursor);
+ }
+
+ /* get rid of host_cursor */
+ if (host_cursor != NULL) {
+ host_cursor->c_close(host_cursor);
+ }
+
+ return (result);
+}
+
+/*% Initializes, sets flags and then opens Berkeley databases. */
+
+static isc_result_t
+bdb_opendb(DB_ENV *db_env, DBTYPE db_type, DB **db, const char *db_name,
+ char *db_file, int flags) {
+ int result;
+
+ /* Initialize the database. */
+ if ((result = db_create(db, db_env, 0)) != 0) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "BDB could not initialize %s database. "
+ "BDB error: %s",
+ db_name, db_strerror(result));
+ return (ISC_R_FAILURE);
+ }
+
+ /* set database flags. */
+ if ((result = (*db)->set_flags(*db, flags)) != 0) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "BDB could not set flags for %s database. "
+ "BDB error: %s",
+ db_name, db_strerror(result));
+ return (ISC_R_FAILURE);
+ }
+
+ /* open the database. */
+ if ((result = (*db)->open(*db, NULL, db_file, db_name, db_type,
+ DB_RDONLY | bdb_threads, 0)) != 0)
+ {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "BDB could not open %s database in %s. "
+ "BDB error: %s",
+ db_name, db_file, db_strerror(result));
+ return (ISC_R_FAILURE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+bdb_create(const char *dlzname, unsigned int argc, char *argv[],
+ void *driverarg, void **dbdata) {
+ isc_result_t result;
+ int bdbres;
+ bdb_instance_t *db = NULL;
+
+ UNUSED(dlzname);
+ UNUSED(driverarg);
+
+ /* verify we have 3 arg's passed to the driver */
+ if (argc != 3) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Berkeley DB driver requires at least "
+ "2 command line args.");
+ return (ISC_R_FAILURE);
+ }
+
+ /* allocate and zero memory for driver structure */
+ db = isc_mem_get(named_g_mctx, sizeof(bdb_instance_t));
+ memset(db, 0, sizeof(bdb_instance_t));
+
+ /* attach to the memory context */
+ isc_mem_attach(named_g_mctx, &db->mctx);
+
+ /* create BDB environment
+ * Basically BDB allocates and assigns memory to db->dbenv
+ */
+ bdbres = db_env_create(&db->dbenv, 0);
+ if (bdbres != 0) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "BDB environment could not be created. "
+ "BDB error: %s",
+ db_strerror(bdbres));
+ result = ISC_R_FAILURE;
+ goto init_cleanup;
+ }
+
+ /* open BDB environment */
+ bdbres = db->dbenv->open(
+ db->dbenv, argv[1],
+ DB_INIT_CDB | DB_INIT_MPOOL | bdb_threads | DB_CREATE, 0);
+ if (bdbres != 0) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "BDB environment at '%s' could not be opened. "
+ "BDB error: %s",
+ argv[1], db_strerror(bdbres));
+ result = ISC_R_FAILURE;
+ goto init_cleanup;
+ }
+
+ /* open dlz_data database. */
+ result = bdb_opendb(db->dbenv, DB_UNKNOWN, &db->data, dlz_data, argv[2],
+ 0);
+ if (result != ISC_R_SUCCESS) {
+ goto init_cleanup;
+ }
+
+ /* open dlz_host database. */
+ result = bdb_opendb(db->dbenv, DB_UNKNOWN, &db->host, dlz_host, argv[2],
+ DB_DUP | DB_DUPSORT);
+ if (result != ISC_R_SUCCESS) {
+ goto init_cleanup;
+ }
+
+ /* open dlz_zone database. */
+ result = bdb_opendb(db->dbenv, DB_UNKNOWN, &db->zone, dlz_zone, argv[2],
+ DB_DUP | DB_DUPSORT);
+ if (result != ISC_R_SUCCESS) {
+ goto init_cleanup;
+ }
+
+ /* open dlz_client database. */
+ result = bdb_opendb(db->dbenv, DB_UNKNOWN, &db->client, dlz_client,
+ argv[2], DB_DUP | DB_DUPSORT);
+ if (result != ISC_R_SUCCESS) {
+ goto init_cleanup;
+ }
+
+ /* associate the host secondary database with the primary database */
+ bdbres = db->data->associate(db->data, NULL, db->host, NULL, 0);
+ if (bdbres != 0) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "BDB could not associate %s database with %s. "
+ "BDB error: %s",
+ dlz_host, dlz_data, db_strerror(bdbres));
+ result = ISC_R_FAILURE;
+ goto init_cleanup;
+ }
+
+ /* associate the zone secondary database with the primary database */
+ bdbres = db->data->associate(db->data, NULL, db->zone, NULL, 0);
+ if (bdbres != 0) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "BDB could not associate %s database with %s. "
+ "BDB error: %s",
+ dlz_zone, dlz_data, db_strerror(bdbres));
+ result = ISC_R_FAILURE;
+ goto init_cleanup;
+ }
+
+ *dbdata = db;
+
+ return (ISC_R_SUCCESS);
+
+init_cleanup:
+
+ bdb_cleanup(db);
+ return (result);
+}
+
+static void
+bdb_destroy(void *driverarg, void *dbdata) {
+ UNUSED(driverarg);
+
+ bdb_cleanup((bdb_instance_t *)dbdata);
+}
+
+/* bdb_authority not needed as authority data is returned by lookup */
+static dns_sdlzmethods_t dlz_bdb_methods = {
+ bdb_create,
+ bdb_destroy,
+ bdb_findzone,
+ bdb_lookup,
+ NULL,
+ bdb_allnodes,
+ bdb_allowzonexfr,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+
+/*%
+ * Wrapper around dns_sdlzregister().
+ */
+isc_result_t
+dlz_bdb_init(void) {
+ isc_result_t result;
+
+ /*
+ * Write debugging message to log
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(2), "Registering DLZ bdb driver.");
+
+ result = dns_sdlzregister("bdb", &dlz_bdb_methods, NULL,
+ DNS_SDLZFLAG_RELATIVEOWNER |
+ DNS_SDLZFLAG_RELATIVERDATA |
+ DNS_SDLZFLAG_THREADSAFE,
+ named_g_mctx, &dlz_bdb);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "dns_sdlzregister() failed: %s",
+ isc_result_totext(result));
+ result = ISC_R_UNEXPECTED;
+ }
+
+ return (result);
+}
+
+/*%
+ * Wrapper around dns_sdlzunregister().
+ */
+void
+dlz_bdb_clear(void) {
+ /*
+ * Write debugging message to log
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(2), "Unregistering DLZ bdb driver.");
+
+ if (dlz_bdb != NULL) {
+ dns_sdlzunregister(&dlz_bdb);
+ }
+}
+
+#endif /* ifdef DLZ_BDB */
diff --git a/contrib/dlz/drivers/dlz_bdbhpt_driver.c b/contrib/dlz/drivers/dlz_bdbhpt_driver.c
new file mode 100644
index 0000000..fefe99d
--- /dev/null
+++ b/contrib/dlz/drivers/dlz_bdbhpt_driver.c
@@ -0,0 +1,849 @@
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ * 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.
+ */
+
+/*
+ * Copyright (C) 1999-2001, 2016 Internet Systems Consortium, Inc. ("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 http://mozilla.org/MPL/2.0/.
+ */
+
+#ifdef DLZ_BDB
+#include <db.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+#include <dns/log.h>
+#include <dns/result.h>
+#include <dns/sdlz.h>
+
+#include <dlz/dlz_bdbhpt_driver.h>
+#include <named/globals.h>
+
+static dns_sdlzimplementation_t *dlz_bdbhpt = NULL;
+
+/* should the bdb driver use threads. */
+#define bdbhpt_threads DB_THREAD
+
+/* bdbhpt database names */
+#define dlz_data "dns_data"
+#define dlz_zone "dns_zone"
+#define dlz_xfr "dns_xfr"
+#define dlz_client "dns_client"
+
+/* This structure contains all the Berkeley DB handles
+ * for this instance of the bdbhpt driver.
+ */
+
+typedef struct bdbhpt_instance {
+ DB_ENV *dbenv; /*%< bdbhpt environment */
+ DB *data; /*%< dns_data database handle */
+ DB *zone; /*%< zone database handle */
+ DB *xfr; /*%< zone xfr database handle */
+ DB *client; /*%< client database handle */
+ isc_mem_t *mctx; /*%< memory context */
+} bdbhpt_instance_t;
+
+typedef struct bdbhpt_parsed_data {
+ char *host;
+ char *type;
+ int ttl;
+ char *data;
+} bdbhpt_parsed_data_t;
+
+/* forward reference */
+
+static isc_result_t
+bdbhpt_findzone(void *driverarg, void *dbdata, const char *name,
+ dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo);
+
+/*%
+ * Reverses a string in place.
+ */
+
+static char *
+bdbhpt_strrev(char *str) {
+ char *p1, *p2;
+
+ if (!str || !*str) {
+ return (str);
+ }
+ for (p1 = str, p2 = str + strlen(str) - 1; p2 > p1; ++p1, --p2) {
+ *p1 ^= *p2;
+ *p2 ^= *p1;
+ *p1 ^= *p2;
+ }
+ return (str);
+}
+
+/*%
+ * Parses the DBT from the Berkeley DB into a parsed_data record
+ * The parsed_data record should be allocated before and passed into the
+ * bdbhpt_parse_data function. The char (type & data) fields should not
+ * be "free"d as that memory is part of the DBT data field. It will be
+ * "free"d when the DBT is freed.
+ */
+
+static isc_result_t
+bdbhpt_parse_data(char *in, bdbhpt_parsed_data_t *pd) {
+ char *endp, *ttlStr;
+ char *tmp = in;
+ char *lastchar = (char *)&tmp[strlen(tmp)];
+
+ /*%
+ * String should be formatted as:
+ * replication_id
+ * (a space)
+ * host_name
+ * (a space)
+ * ttl
+ * (a space)
+ * type
+ * (a space)
+ * remaining data
+ *
+ * examples:
+ *
+ * 9191 host 10 A 127.0.0.1
+ * server1_212 host 10 A 127.0.0.2
+ * {xxxx-xxxx-xxxx-xxxx-xxxx} host 10 MX 20 mail.example.com
+ */
+
+ /*
+ * we don't need the replication id, so don't
+ * bother saving a pointer to it.
+ */
+
+ /* find space after replication id */
+ tmp = strchr(tmp, ' ');
+ /* verify we found a space */
+ if (tmp == NULL) {
+ return (ISC_R_FAILURE);
+ }
+ /* make sure it is safe to increment pointer */
+ if (++tmp > lastchar) {
+ return (ISC_R_FAILURE);
+ }
+
+ /* save pointer to host */
+ pd->host = tmp;
+
+ /* find space after host and change it to a '\0' */
+ tmp = strchr(tmp, ' ');
+ /* verify we found a space */
+ if (tmp == NULL) {
+ return (ISC_R_FAILURE);
+ }
+ /* change the space to a null (string terminator) */
+ tmp[0] = '\0';
+ /* make sure it is safe to increment pointer */
+ if (++tmp > lastchar) {
+ return (ISC_R_FAILURE);
+ }
+
+ /* save pointer to ttl string */
+ ttlStr = tmp;
+
+ /* find space after ttl and change it to a '\0' */
+ tmp = strchr(tmp, ' ');
+ /* verify we found a space */
+ if (tmp == NULL) {
+ return (ISC_R_FAILURE);
+ }
+ /* change the space to a null (string terminator) */
+ tmp[0] = '\0';
+ /* make sure it is safe to increment pointer */
+ if (++tmp > lastchar) {
+ return (ISC_R_FAILURE);
+ }
+
+ /* save pointer to dns type */
+ pd->type = tmp;
+
+ /* find space after type and change it to a '\0' */
+ tmp = strchr(tmp, ' ');
+ /* verify we found a space */
+ if (tmp == NULL) {
+ return (ISC_R_FAILURE);
+ }
+ /* change the space to a null (string terminator) */
+ tmp[0] = '\0';
+ /* make sure it is safe to increment pointer */
+ if (++tmp > lastchar) {
+ return (ISC_R_FAILURE);
+ }
+
+ /* save pointer to remainder of DNS data */
+ pd->data = tmp;
+
+ /* convert ttl string to integer */
+ pd->ttl = strtol(ttlStr, &endp, 10);
+ if (*endp != '\0' || pd->ttl < 0) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "bdbhpt driver ttl must be a positive number");
+ return (ISC_R_FAILURE);
+ }
+
+ /* if we get this far everything should have worked. */
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * DLZ methods
+ */
+
+static isc_result_t
+bdbhpt_allowzonexfr(void *driverarg, void *dbdata, const char *name,
+ const char *client) {
+ isc_result_t result;
+ bdbhpt_instance_t *db = (bdbhpt_instance_t *)dbdata;
+ DBT key, data;
+
+ /* check to see if we are authoritative for the zone first. */
+ result = bdbhpt_findzone(driverarg, dbdata, name, NULL, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ memset(&key, 0, sizeof(DBT));
+ key.flags = DB_DBT_MALLOC;
+ key.data = strdup(name);
+ if (key.data == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto xfr_cleanup;
+ }
+ key.size = strlen(key.data);
+
+ memset(&data, 0, sizeof(DBT));
+ data.flags = DB_DBT_MALLOC;
+ data.data = strdup(client);
+ if (data.data == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto xfr_cleanup;
+ }
+ data.size = strlen(data.data);
+
+ switch (db->client->get(db->client, NULL, &key, &data, DB_GET_BOTH)) {
+ case DB_NOTFOUND:
+ result = ISC_R_NOTFOUND;
+ break;
+ case 0:
+ result = ISC_R_SUCCESS;
+ break;
+ default:
+ result = ISC_R_FAILURE;
+ }
+
+xfr_cleanup:
+
+ /* free any memory duplicate string in the key field */
+ if (key.data != NULL) {
+ free(key.data);
+ }
+
+ /* free any memory allocated to the data field. */
+ if (data.data != NULL) {
+ free(data.data);
+ }
+
+ return (result);
+}
+
+/*%
+ * BDB does not allow a secondary index on a database that allows
+ * duplicates. We have a few options:
+ *
+ * 1) kill speed by having lookup method use a secondary db which
+ * is associated to the primary DB with the DNS data. Then have
+ * another secondary db for zone transfer which also points to
+ * the dns_data primary. NO - The point of this driver is
+ * lookup performance.
+ *
+ * 2) Blow up database size by storing DNS data twice. Once for
+ * the lookup (dns_data) database, and a second time for the zone
+ * transfer (dns_xfr) database. NO - That would probably require
+ * a larger cache to provide good performance. Also, that would
+ * make the DB larger on disk potentially slowing it as well.
+ *
+ * 3) Loop through the dns_xfr database with a cursor to get
+ * all the different hosts in a zone. Then use the zone & host
+ * together to lookup the data in the dns_data database. YES -
+ * This may slow down zone xfr's a little, but that's ok they
+ * don't happen as often and don't need to be as fast. We can
+ * also use this table when deleting a zone (The BDB driver
+ * is read only - the delete would be used during replication
+ * updates by a separate process).
+ */
+
+static isc_result_t
+bdbhpt_allnodes(const char *zone, void *driverarg, void *dbdata,
+ dns_sdlzallnodes_t *allnodes) {
+ isc_result_t result = ISC_R_NOTFOUND;
+ bdbhpt_instance_t *db = (bdbhpt_instance_t *)dbdata;
+ DBC *xfr_cursor = NULL;
+ DBC *dns_cursor = NULL;
+ DBT xfr_key, xfr_data, dns_key, dns_data;
+ int xfr_flags;
+ int dns_flags;
+ int bdbhptres;
+ bdbhpt_parsed_data_t pd;
+ char *tmp = NULL, *tmp_zone, *tmp_zone_host = NULL;
+
+ UNUSED(driverarg);
+
+ memset(&xfr_key, 0, sizeof(DBT));
+ memset(&xfr_data, 0, sizeof(DBT));
+ memset(&dns_key, 0, sizeof(DBT));
+ memset(&dns_data, 0, sizeof(DBT));
+
+ xfr_key.data = tmp_zone = strdup(zone);
+ if (xfr_key.data == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ xfr_key.size = strlen(xfr_key.data);
+
+ /* get a cursor to loop through dns_xfr table */
+ if (db->xfr->cursor(db->xfr, NULL, &xfr_cursor, 0) != 0) {
+ result = ISC_R_FAILURE;
+ goto allnodes_cleanup;
+ }
+
+ /* get a cursor to loop through dns_data table */
+ if (db->data->cursor(db->data, NULL, &dns_cursor, 0) != 0) {
+ result = ISC_R_FAILURE;
+ goto allnodes_cleanup;
+ }
+
+ xfr_flags = DB_SET;
+
+ /* loop through xfr table for specified zone. */
+ while ((bdbhptres = xfr_cursor->c_get(xfr_cursor, &xfr_key, &xfr_data,
+ xfr_flags)) == 0)
+ {
+ xfr_flags = DB_NEXT_DUP;
+
+ /* +1 to allow for space between zone and host names */
+ dns_key.size = xfr_data.size + xfr_key.size + 1;
+
+ /* +1 to allow for null term at end of string. */
+ dns_key.data = tmp_zone_host = malloc(dns_key.size + 1);
+ if (dns_key.data == NULL) {
+ goto allnodes_cleanup;
+ }
+
+ /*
+ * construct search key for dns_data.
+ * zone_name(a space)host_name
+ */
+ strcpy(dns_key.data, zone);
+ strcat(dns_key.data, " ");
+ strncat(dns_key.data, xfr_data.data, xfr_data.size);
+
+ dns_flags = DB_SET;
+
+ while ((bdbhptres = dns_cursor->c_get(dns_cursor, &dns_key,
+ &dns_data, dns_flags)) ==
+ 0)
+ {
+ dns_flags = DB_NEXT_DUP;
+
+ /* +1 to allow for null term at end of string. */
+ tmp = realloc(tmp, dns_data.size + 1);
+ if (tmp == NULL) {
+ goto allnodes_cleanup;
+ }
+
+ /* copy data to tmp string, and append null term. */
+ strncpy(tmp, dns_data.data, dns_data.size);
+ tmp[dns_data.size] = '\0';
+
+ /* split string into dns data parts. */
+ if (bdbhpt_parse_data(tmp, &pd) != ISC_R_SUCCESS) {
+ goto allnodes_cleanup;
+ }
+
+ result = dns_sdlz_putnamedrr(allnodes, pd.host, pd.type,
+ pd.ttl, pd.data);
+ if (result != ISC_R_SUCCESS) {
+ goto allnodes_cleanup;
+ }
+ } /* end inner while loop */
+
+ /* clean up memory */
+ if (tmp_zone_host != NULL) {
+ free(tmp_zone_host);
+ tmp_zone_host = NULL;
+ }
+ } /* end outer while loop */
+
+allnodes_cleanup:
+
+ /* free any memory */
+ if (tmp != NULL) {
+ free(tmp);
+ }
+
+ if (tmp_zone_host != NULL) {
+ free(tmp_zone_host);
+ }
+
+ if (tmp_zone != NULL) {
+ free(tmp_zone);
+ }
+
+ /* get rid of cursors */
+ if (xfr_cursor != NULL) {
+ xfr_cursor->c_close(xfr_cursor);
+ }
+
+ if (dns_cursor != NULL) {
+ dns_cursor->c_close(dns_cursor);
+ }
+
+ return (result);
+}
+
+/*%
+ * Performs bdbhpt cleanup.
+ * Used by bdbhpt_create if there is an error starting up.
+ * Used by bdbhpt_destroy when the driver is shutting down.
+ */
+
+static void
+bdbhpt_cleanup(bdbhpt_instance_t *db) {
+ isc_mem_t *mctx;
+
+ /* close databases */
+ if (db->data != NULL) {
+ db->data->close(db->data, 0);
+ }
+ if (db->xfr != NULL) {
+ db->xfr->close(db->xfr, 0);
+ }
+ if (db->zone != NULL) {
+ db->zone->close(db->zone, 0);
+ }
+ if (db->client != NULL) {
+ db->client->close(db->client, 0);
+ }
+
+ /* close environment */
+ if (db->dbenv != NULL) {
+ db->dbenv->close(db->dbenv, 0);
+ }
+
+ /* cleanup memory */
+ if (db->mctx != NULL) {
+ /* save mctx for later */
+ mctx = db->mctx;
+ /* return, and detach the memory */
+ isc_mem_putanddetach(&mctx, db, sizeof(bdbhpt_instance_t));
+ }
+}
+
+static isc_result_t
+bdbhpt_findzone(void *driverarg, void *dbdata, const char *name,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo) {
+ isc_result_t result;
+ bdbhpt_instance_t *db = (bdbhpt_instance_t *)dbdata;
+ DBT key, data;
+
+ UNUSED(driverarg);
+ UNUSED(methods);
+ UNUSED(clientinfo);
+
+ memset(&key, 0, sizeof(DBT));
+ memset(&data, 0, sizeof(DBT));
+ data.flags = DB_DBT_MALLOC;
+
+ key.data = strdup(name);
+
+ if (key.data == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ /*
+ * reverse string to take advantage of BDB locality of reference
+ * if we need further lookups because the zone doesn't match the
+ * first time.
+ */
+ key.data = bdbhpt_strrev(key.data);
+ key.size = strlen(key.data);
+
+ switch (db->zone->get(db->zone, NULL, &key, &data, 0)) {
+ case DB_NOTFOUND:
+ result = ISC_R_NOTFOUND;
+ break;
+ case 0:
+ result = ISC_R_SUCCESS;
+ break;
+ default:
+ result = ISC_R_FAILURE;
+ }
+
+ /* free any memory duplicate string in the key field */
+ if (key.data != NULL) {
+ free(key.data);
+ }
+
+ /* free any memory allocated to the data field. */
+ if (data.data != NULL) {
+ free(data.data);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+bdbhpt_lookup(const char *zone, const char *name, void *driverarg, void *dbdata,
+ dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo) {
+ isc_result_t result = ISC_R_NOTFOUND;
+ bdbhpt_instance_t *db = (bdbhpt_instance_t *)dbdata;
+ DBC *data_cursor = NULL;
+ DBT key, data;
+ int bdbhptres;
+ int flags;
+
+ bdbhpt_parsed_data_t pd;
+ char *tmp = NULL;
+ char *keyStr = NULL;
+
+ UNUSED(driverarg);
+ UNUSED(methods);
+ UNUSED(clientinfo);
+
+ memset(&key, 0, sizeof(DBT));
+ memset(&data, 0, sizeof(DBT));
+
+ key.size = strlen(zone) + strlen(name) + 1;
+
+ /* allocate mem for key */
+ key.data = keyStr = malloc((key.size + 1) * sizeof(char));
+
+ if (keyStr == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ strcpy(keyStr, zone);
+ strcat(keyStr, " ");
+ strcat(keyStr, name);
+
+ /* get a cursor to loop through data */
+ if (db->data->cursor(db->data, NULL, &data_cursor, 0) != 0) {
+ result = ISC_R_FAILURE;
+ goto lookup_cleanup;
+ }
+
+ result = ISC_R_NOTFOUND;
+
+ flags = DB_SET;
+ while ((bdbhptres = data_cursor->c_get(data_cursor, &key, &data,
+ flags)) == 0)
+ {
+ flags = DB_NEXT_DUP;
+ tmp = realloc(tmp, data.size + 1);
+ if (tmp == NULL) {
+ goto lookup_cleanup;
+ }
+
+ strncpy(tmp, data.data, data.size);
+ tmp[data.size] = '\0';
+
+ if (bdbhpt_parse_data(tmp, &pd) != ISC_R_SUCCESS) {
+ goto lookup_cleanup;
+ }
+
+ result = dns_sdlz_putrr(lookup, pd.type, pd.ttl, pd.data);
+
+ if (result != ISC_R_SUCCESS) {
+ goto lookup_cleanup;
+ }
+ } /* end while loop */
+
+lookup_cleanup:
+
+ /* get rid of cursor */
+ if (data_cursor != NULL) {
+ data_cursor->c_close(data_cursor);
+ }
+
+ free(keyStr);
+ if (tmp != NULL) {
+ free(tmp);
+ }
+
+ return (result);
+}
+
+/*% Initializes, sets flags and then opens Berkeley databases. */
+
+static isc_result_t
+bdbhpt_opendb(DB_ENV *db_env, DBTYPE db_type, DB **db, const char *db_name,
+ char *db_file, int flags) {
+ int result;
+
+ /* Initialize the database. */
+ if ((result = db_create(db, db_env, 0)) != 0) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "bdbhpt could not initialize %s database. "
+ "bdbhpt error: %s",
+ db_name, db_strerror(result));
+ return (ISC_R_FAILURE);
+ }
+
+ /* set database flags. */
+ if ((result = (*db)->set_flags(*db, flags)) != 0) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "bdbhpt could not set flags for %s database. "
+ "bdbhpt error: %s",
+ db_name, db_strerror(result));
+ return (ISC_R_FAILURE);
+ }
+
+ /* open the database. */
+ if ((result = (*db)->open(*db, NULL, db_file, db_name, db_type,
+ DB_RDONLY | bdbhpt_threads, 0)) != 0)
+ {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "bdbhpt could not open %s database in %s. "
+ "bdbhpt error: %s",
+ db_name, db_file, db_strerror(result));
+ return (ISC_R_FAILURE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+bdbhpt_create(const char *dlzname, unsigned int argc, char *argv[],
+ void *driverarg, void **dbdata) {
+ isc_result_t result;
+ int bdbhptres;
+ int bdbFlags = 0;
+ bdbhpt_instance_t *db = NULL;
+
+ UNUSED(dlzname);
+ UNUSED(driverarg);
+
+ /* verify we have 4 arg's passed to the driver */
+ if (argc != 4) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "bdbhpt driver requires at least "
+ "3 command line args.");
+ return (ISC_R_FAILURE);
+ }
+
+ switch ((char)*argv[1]) {
+ /*
+ * Transactional mode. Highest safety - lowest speed.
+ */
+ case 'T':
+ case 't':
+ bdbFlags = DB_INIT_MPOOL | DB_INIT_LOCK | DB_INIT_LOG |
+ DB_INIT_TXN;
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(1),
+ "bdbhpt driver using transactional mode.");
+ break;
+ /*
+ * Concurrent mode. Lower safety (no rollback) -
+ * higher speed.
+ */
+ case 'C':
+ case 'c':
+ bdbFlags = DB_INIT_CDB | DB_INIT_MPOOL;
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(1),
+ "bdbhpt driver using concurrent mode.");
+ break;
+ /*
+ * Private mode. No inter-process communication & no locking.
+ * Lowest safety - highest speed.
+ */
+ case 'P':
+ case 'p':
+ bdbFlags = DB_PRIVATE | DB_INIT_MPOOL;
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(1),
+ "bdbhpt driver using private mode.");
+ break;
+ default:
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "bdbhpt driver requires the operating mode "
+ "be set to P or C or T. You specified '%s'",
+ argv[1]);
+ return (ISC_R_FAILURE);
+ }
+
+ /* allocate and zero memory for driver structure */
+ db = isc_mem_get(named_g_mctx, sizeof(bdbhpt_instance_t));
+ memset(db, 0, sizeof(bdbhpt_instance_t));
+
+ /* attach to the memory context */
+ isc_mem_attach(named_g_mctx, &db->mctx);
+
+ /*
+ * create bdbhpt environment
+ * Basically bdbhpt allocates and assigns memory to db->dbenv
+ */
+ bdbhptres = db_env_create(&db->dbenv, 0);
+ if (bdbhptres != 0) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "bdbhpt environment could not be created. "
+ "bdbhpt error: %s",
+ db_strerror(bdbhptres));
+ result = ISC_R_FAILURE;
+ goto init_cleanup;
+ }
+
+ /* open bdbhpt environment */
+ bdbhptres = db->dbenv->open(db->dbenv, argv[2],
+ bdbFlags | bdbhpt_threads | DB_CREATE, 0);
+ if (bdbhptres != 0) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "bdbhpt environment at '%s' could not be opened."
+ " bdbhpt error: %s",
+ argv[2], db_strerror(bdbhptres));
+ result = ISC_R_FAILURE;
+ goto init_cleanup;
+ }
+
+ /* open dlz_data database. */
+ result = bdbhpt_opendb(db->dbenv, DB_UNKNOWN, &db->data, dlz_data,
+ argv[3], DB_DUP | DB_DUPSORT);
+ if (result != ISC_R_SUCCESS) {
+ goto init_cleanup;
+ }
+
+ /* open dlz_xfr database. */
+ result = bdbhpt_opendb(db->dbenv, DB_UNKNOWN, &db->xfr, dlz_xfr,
+ argv[3], DB_DUP | DB_DUPSORT);
+ if (result != ISC_R_SUCCESS) {
+ goto init_cleanup;
+ }
+
+ /* open dlz_zone database. */
+ result = bdbhpt_opendb(db->dbenv, DB_UNKNOWN, &db->zone, dlz_zone,
+ argv[3], 0);
+ if (result != ISC_R_SUCCESS) {
+ goto init_cleanup;
+ }
+
+ /* open dlz_client database. */
+ result = bdbhpt_opendb(db->dbenv, DB_UNKNOWN, &db->client, dlz_client,
+ argv[3], DB_DUP | DB_DUPSORT);
+ if (result != ISC_R_SUCCESS) {
+ goto init_cleanup;
+ }
+
+ *dbdata = db;
+
+ return (ISC_R_SUCCESS);
+
+init_cleanup:
+
+ bdbhpt_cleanup(db);
+ return (result);
+}
+
+static void
+bdbhpt_destroy(void *driverarg, void *dbdata) {
+ UNUSED(driverarg);
+
+ bdbhpt_cleanup((bdbhpt_instance_t *)dbdata);
+}
+
+/*
+ * bdbhpt_authority not needed as authority data is returned by lookup
+ */
+static dns_sdlzmethods_t dlz_bdbhpt_methods = {
+ bdbhpt_create,
+ bdbhpt_destroy,
+ bdbhpt_findzone,
+ bdbhpt_lookup,
+ NULL,
+ bdbhpt_allnodes,
+ bdbhpt_allowzonexfr,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+
+/*%
+ * Wrapper around dns_sdlzregister().
+ */
+isc_result_t
+dlz_bdbhpt_init(void) {
+ isc_result_t result;
+
+ /*
+ * Write debugging message to log
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(2), "Registering DLZ bdbhpt driver.");
+
+ result = dns_sdlzregister("bdbhpt", &dlz_bdbhpt_methods, NULL,
+ DNS_SDLZFLAG_RELATIVEOWNER |
+ DNS_SDLZFLAG_RELATIVERDATA |
+ DNS_SDLZFLAG_THREADSAFE,
+ named_g_mctx, &dlz_bdbhpt);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "dns_sdlzregister() failed: %s",
+ isc_result_totext(result));
+ result = ISC_R_UNEXPECTED;
+ }
+
+ return (result);
+}
+
+/*%
+ * Wrapper around dns_sdlzunregister().
+ */
+void
+dlz_bdbhpt_clear(void) {
+ /*
+ * Write debugging message to log
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(2), "Unregistering DLZ bdbhpt driver.");
+
+ if (dlz_bdbhpt != NULL) {
+ dns_sdlzunregister(&dlz_bdbhpt);
+ }
+}
+
+#endif /* ifdef DLZ_BDB */
diff --git a/contrib/dlz/drivers/dlz_dlopen_driver.c b/contrib/dlz/drivers/dlz_dlopen_driver.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/dlz/drivers/dlz_dlopen_driver.c
diff --git a/contrib/dlz/drivers/dlz_drivers.c b/contrib/dlz/drivers/dlz_drivers.c
new file mode 100644
index 0000000..8320bf8
--- /dev/null
+++ b/contrib/dlz/drivers/dlz_drivers.c
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2005 Internet Systems Consortium, Inc. ("ISC")
+ * 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 */
+
+#include <isc/result.h>
+
+/*
+ * Pull in declarations for this module's functions.
+ */
+
+#include <dlz/dlz_drivers.h>
+
+/*
+ * Pull in driver-specific stuff.
+ */
+
+#ifdef DLZ_STUB
+#include <dlz/dlz_stub_driver.h>
+#endif /* ifdef DLZ_STUB */
+
+#ifdef DLZ_POSTGRES
+#include <dlz/dlz_postgres_driver.h>
+#endif /* ifdef DLZ_POSTGRES */
+
+#ifdef DLZ_MYSQL
+#include <dlz/dlz_mysql_driver.h>
+#endif /* ifdef DLZ_MYSQL */
+
+#ifdef DLZ_FILESYSTEM
+#include <dlz/dlz_filesystem_driver.h>
+#endif /* ifdef DLZ_FILESYSTEM */
+
+#ifdef DLZ_BDB
+#include <dlz/dlz_bdb_driver.h>
+#include <dlz/dlz_bdbhpt_driver.h>
+#endif /* ifdef DLZ_BDB */
+
+#ifdef DLZ_LDAP
+#include <dlz/dlz_ldap_driver.h>
+#endif /* ifdef DLZ_LDAP */
+
+#ifdef DLZ_ODBC
+#include <dlz/dlz_odbc_driver.h>
+#endif /* ifdef DLZ_ODBC */
+
+/*%
+ * Call init functions for all relevant DLZ drivers.
+ */
+
+isc_result_t
+dlz_drivers_init(void) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+#ifdef DLZ_STUB
+ result = dlz_stub_init();
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+#endif /* ifdef DLZ_STUB */
+
+#ifdef DLZ_POSTGRES
+ result = dlz_postgres_init();
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+#endif /* ifdef DLZ_POSTGRES */
+
+#ifdef DLZ_MYSQL
+ result = dlz_mysql_init();
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+#endif /* ifdef DLZ_MYSQL */
+
+#ifdef DLZ_FILESYSTEM
+ result = dlz_fs_init();
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+#endif /* ifdef DLZ_FILESYSTEM */
+
+#ifdef DLZ_BDB
+ result = dlz_bdb_init();
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ result = dlz_bdbhpt_init();
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+#endif /* ifdef DLZ_BDB */
+
+#ifdef DLZ_LDAP
+ result = dlz_ldap_init();
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+#endif /* ifdef DLZ_LDAP */
+
+#ifdef DLZ_ODBC
+ result = dlz_odbc_init();
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+#endif /* ifdef DLZ_ODBC */
+
+ return (result);
+}
+
+/*%
+ * Call shutdown functions for all relevant DLZ drivers.
+ */
+
+void
+dlz_drivers_clear(void) {
+#ifdef DLZ_STUB
+ dlz_stub_clear();
+#endif /* ifdef DLZ_STUB */
+
+#ifdef DLZ_POSTGRES
+ dlz_postgres_clear();
+#endif /* ifdef DLZ_POSTGRES */
+
+#ifdef DLZ_MYSQL
+ dlz_mysql_clear();
+#endif /* ifdef DLZ_MYSQL */
+
+#ifdef DLZ_FILESYSTEM
+ dlz_fs_clear();
+#endif /* ifdef DLZ_FILESYSTEM */
+
+#ifdef DLZ_BDB
+ dlz_bdb_clear();
+ dlz_bdbhpt_clear();
+#endif /* ifdef DLZ_BDB */
+
+#ifdef DLZ_LDAP
+ dlz_ldap_clear();
+#endif /* ifdef DLZ_LDAP */
+
+#ifdef DLZ_ODBC
+ dlz_odbc_clear();
+#endif /* ifdef DLZ_ODBC */
+}
diff --git a/contrib/dlz/drivers/dlz_filesystem_driver.c b/contrib/dlz/drivers/dlz_filesystem_driver.c
new file mode 100644
index 0000000..c097411
--- /dev/null
+++ b/contrib/dlz/drivers/dlz_filesystem_driver.c
@@ -0,0 +1,1000 @@
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ * 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.
+ */
+
+/*
+ * Copyright (C) 1999-2001, 2016 Internet Systems Consortium, Inc. ("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 http://mozilla.org/MPL/2.0/.
+ */
+
+#ifdef DLZ_FILESYSTEM
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <isc/dir.h>
+#include <isc/mem.h>
+#include <isc/platform.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/log.h>
+#include <dns/result.h>
+#include <dns/sdlz.h>
+
+#include <dlz/dlz_filesystem_driver.h>
+#include <named/globals.h>
+
+static dns_sdlzimplementation_t *dlz_fs = NULL;
+
+typedef struct config_data {
+ char *basedir;
+ int basedirsize;
+ char *datadir;
+ int datadirsize;
+ char *xfrdir;
+ int xfrdirsize;
+ int splitcnt;
+ char separator;
+ char pathsep;
+ isc_mem_t *mctx;
+} config_data_t;
+
+typedef struct dir_entry dir_entry_t;
+
+struct dir_entry {
+ char dirpath[PATH_MAX];
+ ISC_LINK(dir_entry_t) link;
+};
+
+typedef ISC_LIST(dir_entry_t) dlist_t;
+
+/* forward reference */
+
+static void
+fs_destroy(void *driverarg, void *dbdata);
+
+/*
+ * Private methods
+ */
+
+static bool
+is_safe(const char *input) {
+ unsigned int i;
+ unsigned int len = strlen(input);
+
+ /* check that only allowed characters are in the domain name */
+ for (i = 0; i < len; i++) {
+ /* '.' is allowed, but has special requirements */
+ if (input[i] == '.') {
+ /* '.' is not allowed as first char */
+ if (i == 0) {
+ return (false);
+ }
+ /* '..', two dots together is not allowed. */
+ if (input[i - 1] == '.') {
+ return (false);
+ }
+ /* '.' is not allowed as last char */
+ if (i == len - 1) {
+ return (false);
+ }
+ /* only 1 dot in ok location, continue at next char */
+ continue;
+ }
+ /* '-' is allowed, continue at next char */
+ if (input[i] == '-') {
+ continue;
+ }
+ /* 0-9 is allowed, continue at next char */
+ if (input[i] >= '0' && input[i] <= '9') {
+ continue;
+ }
+ /* A-Z uppercase is allowed, continue at next char */
+ if (input[i] >= 'A' && input[i] <= 'Z') {
+ continue;
+ }
+ /* a-z lowercase is allowed, continue at next char */
+ if (input[i] >= 'a' && input[i] <= 'z') {
+ continue;
+ }
+
+ /*
+ * colon needs to be allowed for IPV6 client
+ * addresses. Not dangerous in domain names, as not a
+ * special char.
+ */
+ if (input[i] == ':') {
+ continue;
+ }
+
+ /*
+ * '@' needs to be allowed for in zone data. Not
+ * dangerous in domain names, as not a special char.
+ */
+ if (input[i] == '@') {
+ continue;
+ }
+
+ /*
+ * if we reach this point we have encountered a
+ * disallowed char!
+ */
+ return (false);
+ }
+ /* everything ok. */
+ return (true);
+}
+
+static void
+create_path_helper(char *out, const char *in, config_data_t *cd) {
+ char *tmpString;
+ char *tmpPtr;
+ int i;
+
+ tmpString = isc_mem_strdup(named_g_mctx, in);
+
+ /*
+ * don't forget is_safe guarantees '.' will NOT be the
+ * first/last char
+ */
+ while ((tmpPtr = strrchr(tmpString, '.')) != NULL) {
+ i = 0;
+ while (tmpPtr[i + 1] != '\0') {
+ if (cd->splitcnt < 1) {
+ strcat(out, (char *)&tmpPtr[i + 1]);
+ } else {
+ strncat(out, (char *)&tmpPtr[i + 1],
+ cd->splitcnt);
+ }
+ strncat(out, (char *)&cd->pathsep, 1);
+ if (cd->splitcnt == 0) {
+ break;
+ }
+ if (strlen((char *)&tmpPtr[i + 1]) <=
+ (unsigned int)cd->splitcnt)
+ {
+ break;
+ }
+ i += cd->splitcnt;
+ }
+ tmpPtr[0] = '\0';
+ }
+
+ /* handle the "first" label properly */
+ i = 0;
+ tmpPtr = tmpString;
+ while (tmpPtr[i] != '\0') {
+ if (cd->splitcnt < 1) {
+ strcat(out, (char *)&tmpPtr[i]);
+ } else {
+ strncat(out, (char *)&tmpPtr[i], cd->splitcnt);
+ }
+ strncat(out, (char *)&cd->pathsep, 1);
+ if (cd->splitcnt == 0) {
+ break;
+ }
+ if (strlen((char *)&tmpPtr[i]) <= (unsigned int)cd->splitcnt) {
+ break;
+ }
+ i += cd->splitcnt;
+ }
+
+ isc_mem_free(named_g_mctx, tmpString);
+}
+
+/*%
+ * Checks to make sure zone and host are safe. If safe, then
+ * hashes zone and host strings to build a path. If zone / host
+ * are not safe an error is returned.
+ */
+
+static isc_result_t
+create_path(const char *zone, const char *host, const char *client,
+ config_data_t *cd, char **path) {
+ char *tmpPath;
+ int pathsize;
+ int len;
+ bool isroot = false;
+
+ /* we require a zone & cd parameter */
+ REQUIRE(zone != NULL);
+ REQUIRE(cd != NULL);
+ /* require path to be a pointer to NULL */
+ REQUIRE(path != NULL && *path == NULL);
+ /*
+ * client and host may both be NULL, but they can't both be
+ * NON-NULL
+ */
+ REQUIRE((host == NULL && client == NULL) ||
+ (host != NULL && client == NULL) ||
+ (host == NULL && client != NULL));
+
+ /* special case for root zone */
+ if (strcmp(zone, ".") == 0) {
+ isroot = true;
+ }
+
+ /* if the requested zone is "unsafe", return error */
+ if (!isroot && !is_safe(zone)) {
+ return (ISC_R_FAILURE);
+ }
+
+ /* if host was passed, verify that it is safe */
+ if (host != NULL && !is_safe(host)) {
+ return (ISC_R_FAILURE);
+ }
+
+ /* if client was passed, verify that it is safe */
+ if (client != NULL && !is_safe(client)) {
+ return (ISC_R_FAILURE);
+ }
+
+ /* Determine how much memory the split up string will require */
+ if (host != NULL) {
+ len = strlen(zone) + strlen(host);
+ } else if (client != NULL) {
+ len = strlen(zone) + strlen(client);
+ } else {
+ len = strlen(zone);
+ }
+
+ /*
+ * even though datadir and xfrdir will never be in the same
+ * string we only waste a few bytes by allocating for both,
+ * and then we are safe from buffer overruns.
+ */
+ pathsize = len + cd->basedirsize + cd->datadirsize + cd->xfrdirsize + 4;
+
+ /* if we are splitting names, we will need extra space. */
+ if (cd->splitcnt > 0) {
+ pathsize += len / cd->splitcnt;
+ }
+
+ tmpPath = isc_mem_allocate(named_g_mctx, pathsize * sizeof(char));
+
+ /*
+ * build path string.
+ * start out with base directory.
+ */
+ strcpy(tmpPath, cd->basedir);
+
+ /* add zone name - parsed properly */
+ if (!isroot) {
+ create_path_helper(tmpPath, zone, cd);
+ }
+
+ /*
+ * When neither client or host is passed we are building a
+ * path to see if a zone is supported. We require that a zone
+ * path have the "data dir" directory contained within it so
+ * that we know this zone is really supported. Otherwise,
+ * this zone may not really be supported because we are
+ * supporting a delagated sub zone.
+ *
+ * Example:
+ *
+ * We are supporting long.domain.com and using a splitcnt of
+ * 0. the base dir is "/base-dir/" and the data dir is
+ * "/.datadir" We want to see if we are authoritative for
+ * domain.com. Path /base-dir/com/domain/.datadir since
+ * /base-dir/com/domain/.datadir does not exist, we are not
+ * authoritative for the domain "domain.com". However we are
+ * authoritative for the domain "long.domain.com" because the
+ * path /base-dir/com/domain/long/.datadir does exist!
+ */
+
+ /* if client is passed append xfr dir, otherwise append data dir */
+ if (client != NULL) {
+ strcat(tmpPath, cd->xfrdir);
+ strncat(tmpPath, (char *)&cd->pathsep, 1);
+ strcat(tmpPath, client);
+ } else {
+ strcat(tmpPath, cd->datadir);
+ }
+
+ /* if host not null, add it. */
+ if (host != NULL) {
+ strncat(tmpPath, (char *)&cd->pathsep, 1);
+ create_path_helper(tmpPath, host, cd);
+ }
+
+ /* return the path we built. */
+ *path = tmpPath;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+process_dir(isc_dir_t *dir, void *passback, config_data_t *cd,
+ dlist_t *dir_list, unsigned int basedirlen) {
+ char tmp[PATH_MAX + NAME_MAX];
+ int astPos;
+ struct stat sb;
+ isc_result_t result = ISC_R_FAILURE;
+ char *endp;
+ char *type;
+ char *ttlStr;
+ char *data;
+ char host[NAME_MAX];
+ char *tmpString;
+ char *tmpPtr;
+ int ttl;
+ int i;
+ int len;
+ dir_entry_t *direntry;
+ bool foundHost;
+
+ tmp[0] = '\0'; /* set 1st byte to '\0' so strcpy works right. */
+ host[0] = '\0';
+ foundHost = false;
+
+ /* copy base directory name to tmp. */
+ strcpy(tmp, dir->dirname);
+
+ /* dir->dirname will always have '*' as the last char. */
+ astPos = strlen(dir->dirname) - 1;
+
+ /* if dir_list != NULL, were are performing a zone xfr */
+ if (dir_list != NULL) {
+ /* if splitcnt == 0, determine host from path. */
+ if (cd->splitcnt == 0) {
+ if (strlen(tmp) - 3 > basedirlen) {
+ tmp[astPos - 1] = '\0';
+ tmpString = (char *)&tmp[basedirlen + 1];
+ /* handle filesystem's special wildcard "-" */
+ if (strcmp(tmpString, "-") == 0) {
+ strcpy(host, "*");
+ } else {
+ /*
+ * not special wildcard -- normal name
+ */
+ while ((tmpPtr = strrchr(
+ tmpString,
+ cd->pathsep)) != NULL)
+ {
+ if ((strlen(host) +
+ strlen(tmpPtr + 1) + 2) >
+ NAME_MAX)
+ {
+ continue;
+ }
+ strcat(host, tmpPtr + 1);
+ strcat(host, ".");
+ tmpPtr[0] = '\0';
+ }
+ if ((strlen(host) + strlen(tmpString) +
+ 1) <= NAME_MAX)
+ {
+ strcat(host, tmpString);
+ }
+ }
+
+ foundHost = true;
+ /* set tmp again for use later */
+ strcpy(tmp, dir->dirname);
+ }
+ } else {
+ /*
+ * if splitcnt != 0 determine host from
+ * ".host" directory entry
+ */
+ while (isc_dir_read(dir) == ISC_R_SUCCESS) {
+ if (strncasecmp(".host", dir->entry.name, 5) ==
+ 0)
+ {
+ /*
+ * handle filesystem's special
+ * wildcard "-"
+ */
+ if (strcmp((char *)&dir->entry.name[6],
+ "-") == 0)
+ {
+ strlcpy(host, "*",
+ sizeof(host));
+ } else {
+ strlcpy(host,
+ (char *)&dir->entry
+ .name[6],
+ sizeof(host));
+ }
+ foundHost = true;
+ break;
+ }
+ }
+ /* reset dir list for use later */
+ isc_dir_reset(dir);
+ } /* end of else */
+ }
+
+ while (isc_dir_read(dir) == ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(1),
+ "Filesystem driver Dir name:"
+ " '%s' Dir entry: '%s'\n",
+ dir->dirname, dir->entry.name);
+
+ /* skip any entries starting with "." */
+ if (dir->entry.name[0] == '.') {
+ continue;
+ }
+
+ /*
+ * get rid of '*', set to NULL. Effectively trims
+ * string from previous loop to base directory only
+ * while still leaving memory for concat to be
+ * performed next.
+ */
+
+ tmp[astPos] = '\0';
+
+ /* add name to base directory name. */
+ strcat(tmp, dir->entry.name);
+
+ /* make sure we can stat entry */
+ if (stat(tmp, &sb) == 0) {
+ /* if entry is a directory */
+ if ((sb.st_mode & S_IFDIR) != 0) {
+ /*
+ * if dir list is NOT NULL, add dir to
+ * dir list
+ */
+ if (dir_list != NULL) {
+ direntry = isc_mem_get(
+ named_g_mctx,
+ sizeof(dir_entry_t));
+ strcpy(direntry->dirpath, tmp);
+ ISC_LINK_INIT(direntry, link);
+ ISC_LIST_APPEND(*dir_list, direntry,
+ link);
+ result = ISC_R_SUCCESS;
+ }
+ continue;
+
+ /*
+ * if entry is a file be sure we do
+ * not add entry to DNS results if we
+ * are performing a zone xfr and we
+ * could not find a host entry.
+ */
+ } else if (dir_list != NULL && !foundHost) {
+ continue;
+ }
+ } else { /* if we cannot stat entry, skip it. */
+ continue;
+ }
+
+ type = dir->entry.name;
+ ttlStr = strchr(type, cd->separator);
+ if (ttlStr == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Filesystem driver: "
+ "%s could not be parsed properly",
+ tmp);
+ return (ISC_R_FAILURE);
+ }
+
+ /* replace separator char with NULL to split string */
+ ttlStr[0] = '\0';
+ /* start string after NULL of previous string */
+ ttlStr = (char *)&ttlStr[1];
+
+ data = strchr(ttlStr, cd->separator);
+ if (data == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Filesystem driver: "
+ "%s could not be parsed properly",
+ tmp);
+ return (ISC_R_FAILURE);
+ }
+
+ /* replace separator char with NULL to split string */
+ data[0] = '\0';
+
+ /* start string after NULL of previous string */
+ data = (char *)&data[1];
+
+ /* replace all cd->separator chars with a space. */
+ len = strlen(data);
+
+ for (i = 0; i < len; i++) {
+ if (data[i] == cd->separator) {
+ data[i] = ' ';
+ }
+ }
+
+ /* convert text to int, make sure it worked right */
+ ttl = strtol(ttlStr, &endp, 10);
+ if (*endp != '\0' || ttl < 0) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Filesystem driver "
+ "ttl must be a positive number");
+ }
+
+ /* pass data back to Bind */
+ if (dir_list == NULL) {
+ result = dns_sdlz_putrr((dns_sdlzlookup_t *)passback,
+ type, ttl, data);
+ } else {
+ result = dns_sdlz_putnamedrr(
+ (dns_sdlzallnodes_t *)passback, (char *)host,
+ type, ttl, data);
+ }
+
+ /* if error, return error right away */
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ } /* end of while loop */
+
+ return (result);
+}
+
+/*
+ * SDLZ interface methods
+ */
+
+static isc_result_t
+fs_allowzonexfr(void *driverarg, void *dbdata, const char *name,
+ const char *client) {
+ isc_result_t result;
+ char *path;
+ struct stat sb;
+ config_data_t *cd;
+ path = NULL;
+
+ UNUSED(driverarg);
+
+ cd = (config_data_t *)dbdata;
+
+ if (create_path(name, NULL, client, cd, &path) != ISC_R_SUCCESS) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ if (stat(path, &sb) != 0) {
+ result = ISC_R_NOTFOUND;
+ goto complete_AXFR;
+ }
+
+ if ((sb.st_mode & S_IFREG) != 0) {
+ result = ISC_R_SUCCESS;
+ goto complete_AXFR;
+ }
+
+ result = ISC_R_NOTFOUND;
+
+complete_AXFR:
+ isc_mem_free(named_g_mctx, path);
+ return (result);
+}
+
+static isc_result_t
+fs_allnodes(const char *zone, void *driverarg, void *dbdata,
+ dns_sdlzallnodes_t *allnodes) {
+ isc_result_t result;
+ dlist_t *dir_list;
+ config_data_t *cd;
+ char *basepath;
+ unsigned int basepathlen;
+ struct stat sb;
+ isc_dir_t dir;
+ dir_entry_t *dir_entry;
+ dir_entry_t *next_de;
+
+ basepath = NULL;
+ dir_list = NULL;
+
+ UNUSED(driverarg);
+ UNUSED(allnodes);
+
+ cd = (config_data_t *)dbdata;
+
+ /* allocate memory for list */
+ dir_list = isc_mem_get(named_g_mctx, sizeof(dlist_t));
+
+ /* initialize list */
+ ISC_LIST_INIT(*dir_list);
+
+ if (create_path(zone, NULL, NULL, cd, &basepath) != ISC_R_SUCCESS) {
+ result = ISC_R_NOTFOUND;
+ goto complete_allnds;
+ }
+
+ /* remove path separator at end of path so stat works properly */
+ basepathlen = strlen(basepath);
+
+ if (stat(basepath, &sb) != 0) {
+ result = ISC_R_NOTFOUND;
+ goto complete_allnds;
+ }
+
+ if ((sb.st_mode & S_IFDIR) == 0) {
+ result = ISC_R_NOTFOUND;
+ goto complete_allnds;
+ }
+
+ /* initialize and open directory */
+ isc_dir_init(&dir);
+ result = isc_dir_open(&dir, basepath);
+
+ /* if directory open failed, return error. */
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Unable to open %s directory to read entries.",
+ basepath);
+ result = ISC_R_FAILURE;
+ goto complete_allnds;
+ }
+
+ /* process the directory */
+ result = process_dir(&dir, allnodes, cd, dir_list, basepathlen);
+
+ /* close the directory */
+ isc_dir_close(&dir);
+
+ if (result != ISC_R_SUCCESS) {
+ goto complete_allnds;
+ }
+
+ /* get first dir entry from list. */
+ dir_entry = ISC_LIST_HEAD(*dir_list);
+ while (dir_entry != NULL) {
+ result = isc_dir_open(&dir, dir_entry->dirpath);
+ /* if directory open failed, return error. */
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Unable to open %s "
+ "directory to read entries.",
+ basepath);
+ result = ISC_R_FAILURE;
+ goto complete_allnds;
+ }
+
+ /* process the directory */
+ result = process_dir(&dir, allnodes, cd, dir_list, basepathlen);
+
+ /* close the directory */
+ isc_dir_close(&dir);
+
+ if (result != ISC_R_SUCCESS) {
+ goto complete_allnds;
+ }
+
+ dir_entry = ISC_LIST_NEXT(dir_entry, link);
+ } /* end while */
+
+complete_allnds:
+ /* clean up entries from list. */
+ dir_entry = ISC_LIST_HEAD(*dir_list);
+ while (dir_entry != NULL) {
+ next_de = ISC_LIST_NEXT(dir_entry, link);
+ isc_mem_put(named_g_mctx, dir_entry, sizeof(dir_entry_t));
+ dir_entry = next_de;
+ } /* end while */
+ isc_mem_put(named_g_mctx, dir_list, sizeof(dlist_t));
+
+ if (basepath != NULL) {
+ isc_mem_free(named_g_mctx, basepath);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+fs_findzone(void *driverarg, void *dbdata, const char *name,
+ dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo) {
+ isc_result_t result;
+ char *path;
+ struct stat sb;
+ path = NULL;
+
+ UNUSED(driverarg);
+ UNUSED(methods);
+ UNUSED(clientinfo);
+
+ if (create_path(name, NULL, NULL, (config_data_t *)dbdata, &path) !=
+ ISC_R_SUCCESS)
+ {
+ return (ISC_R_NOTFOUND);
+ }
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(1),
+ "Filesystem driver Findzone() Checking for path: '%s'\n",
+ path);
+
+ if (stat(path, &sb) != 0) {
+ result = ISC_R_NOTFOUND;
+ goto complete_FZ;
+ }
+
+ if ((sb.st_mode & S_IFDIR) != 0) {
+ result = ISC_R_SUCCESS;
+ goto complete_FZ;
+ }
+
+ result = ISC_R_NOTFOUND;
+
+complete_FZ:
+
+ isc_mem_free(named_g_mctx, path);
+ return (result);
+}
+
+static isc_result_t
+fs_lookup(const char *zone, const char *name, void *driverarg, void *dbdata,
+ dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo) {
+ isc_result_t result;
+ char *path;
+ struct stat sb;
+ isc_dir_t dir;
+ path = NULL;
+
+ UNUSED(driverarg);
+ UNUSED(lookup);
+ UNUSED(methods);
+ UNUSED(clientinfo);
+
+ if (strcmp(name, "*") == 0) {
+ /*
+ * handle filesystem's special wildcard "-"
+ */
+ result = create_path(zone, "-", NULL, (config_data_t *)dbdata,
+ &path);
+ } else {
+ result = create_path(zone, name, NULL, (config_data_t *)dbdata,
+ &path);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ /* remove path separator at end of path so stat works properly */
+ path[strlen(path) - 1] = '\0';
+
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(1),
+ "Filesystem driver lookup() Checking for path: '%s'\n",
+ path);
+
+ if (stat(path, &sb) != 0) {
+ result = ISC_R_NOTFOUND;
+ goto complete_lkup;
+ }
+
+ if ((sb.st_mode & S_IFDIR) == 0) {
+ result = ISC_R_NOTFOUND;
+ goto complete_lkup;
+ }
+
+ /* initialize and open directory */
+ isc_dir_init(&dir);
+ result = isc_dir_open(&dir, path);
+
+ /* if directory open failed, return error. */
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Unable to open %s directory to read entries.",
+ path);
+ result = ISC_R_FAILURE;
+ goto complete_lkup;
+ }
+
+ /* process any records in the directory */
+ result = process_dir(&dir, lookup, (config_data_t *)dbdata, NULL, 0);
+
+ /* close the directory */
+ isc_dir_close(&dir);
+
+complete_lkup:
+
+ isc_mem_free(named_g_mctx, path);
+ return (result);
+}
+
+static isc_result_t
+fs_create(const char *dlzname, unsigned int argc, char *argv[], void *driverarg,
+ void **dbdata) {
+ config_data_t *cd;
+ char *endp;
+ int len;
+ char pathsep;
+
+ UNUSED(driverarg);
+ UNUSED(dlzname);
+
+ /* we require 5 command line args. */
+ if (argc != 6) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Filesystem driver requires "
+ "6 command line args.");
+ return (ISC_R_FAILURE);
+ }
+
+ if (strlen(argv[5]) > 1) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Filesystem driver can only "
+ "accept a single character for separator.");
+ return (ISC_R_FAILURE);
+ }
+
+ /* verify base dir ends with '/' or '\' */
+ len = strlen(argv[1]);
+ if (argv[1][len - 1] != '\\' && argv[1][len - 1] != '/') {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Base dir parameter for filesystem driver "
+ "should end with %s",
+ "either '/' or '\\' ");
+ return (ISC_R_FAILURE);
+ }
+
+ /* determine and save path separator for later */
+ if (argv[1][len - 1] == '\\') {
+ pathsep = '\\';
+ } else {
+ pathsep = '/';
+ }
+
+ /* allocate memory for our config data */
+ cd = isc_mem_get(named_g_mctx, sizeof(config_data_t));
+
+ /* zero the memory */
+ memset(cd, 0, sizeof(config_data_t));
+
+ cd->pathsep = pathsep;
+
+ /* get and store our base directory */
+ cd->basedir = isc_mem_strdup(named_g_mctx, argv[1]);
+ cd->basedirsize = strlen(cd->basedir);
+
+ /* get and store our data sub-dir */
+ cd->datadir = isc_mem_strdup(named_g_mctx, argv[2]);
+ cd->datadirsize = strlen(cd->datadir);
+
+ /* get and store our zone xfr sub-dir */
+ cd->xfrdir = isc_mem_strdup(named_g_mctx, argv[3]);
+ cd->xfrdirsize = strlen(cd->xfrdir);
+
+ /* get and store our directory split count */
+ cd->splitcnt = strtol(argv[4], &endp, 10);
+ if (*endp != '\0' || cd->splitcnt < 0) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Directory split count must be zero (0) "
+ "or a positive number");
+ }
+
+ /* get and store our separator character */
+ cd->separator = *argv[5];
+
+ /* attach config data to memory context */
+ isc_mem_attach(named_g_mctx, &cd->mctx);
+
+ /* pass back config data */
+ *dbdata = cd;
+
+ /* return success */
+ return (ISC_R_SUCCESS);
+}
+
+static void
+fs_destroy(void *driverarg, void *dbdata) {
+ isc_mem_t *mctx;
+ config_data_t *cd;
+
+ UNUSED(driverarg);
+
+ cd = (config_data_t *)dbdata;
+
+ /*
+ * free memory for each section of config data that was
+ * allocated
+ */
+ if (cd->basedir != NULL) {
+ isc_mem_free(named_g_mctx, cd->basedir);
+ }
+
+ if (cd->datadir != NULL) {
+ isc_mem_free(named_g_mctx, cd->datadir);
+ }
+
+ if (cd->xfrdir != NULL) {
+ isc_mem_free(named_g_mctx, cd->xfrdir);
+ }
+
+ /* hold memory context to use later */
+ mctx = cd->mctx;
+
+ /* free config data memory */
+ isc_mem_putanddetach(&mctx, cd, sizeof(config_data_t));
+}
+
+static dns_sdlzmethods_t dlz_fs_methods = {
+ fs_create,
+ fs_destroy,
+ fs_findzone,
+ fs_lookup,
+ NULL,
+ fs_allnodes,
+ fs_allowzonexfr,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+
+/*%
+ * Wrapper around dns_sdlzregister().
+ */
+isc_result_t
+dlz_fs_init(void) {
+ isc_result_t result;
+
+ /*
+ * Write debugging message to log
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(2), "Registering DLZ filesystem driver.");
+
+ result = dns_sdlzregister("filesystem", &dlz_fs_methods, NULL,
+ DNS_SDLZFLAG_RELATIVEOWNER |
+ DNS_SDLZFLAG_RELATIVERDATA,
+ named_g_mctx, &dlz_fs);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "dns_sdlzregister() failed: %s",
+ isc_result_totext(result));
+ result = ISC_R_UNEXPECTED;
+ }
+
+ return (result);
+}
+
+/*%
+ * Wrapper around dns_sdlzunregister().
+ */
+void
+dlz_fs_clear(void) {
+ /*
+ * Write debugging message to log
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(2), "Unregistering DLZ filesystem driver.");
+
+ if (dlz_fs != NULL) {
+ dns_sdlzunregister(&dlz_fs);
+ }
+}
+
+#endif /* ifdef DLZ_FILESYSTEM */
diff --git a/contrib/dlz/drivers/dlz_ldap_driver.c b/contrib/dlz/drivers/dlz_ldap_driver.c
new file mode 100644
index 0000000..072d285
--- /dev/null
+++ b/contrib/dlz/drivers/dlz_ldap_driver.c
@@ -0,0 +1,1216 @@
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ * 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.
+ */
+
+/*
+ * Copyright (C) 1999-2001, 2016 Internet Systems Consortium, Inc. ("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 http://mozilla.org/MPL/2.0/.
+ */
+
+#ifdef DLZ_LDAP
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <isc/mem.h>
+#include <isc/platform.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/log.h>
+#include <dns/result.h>
+#include <dns/sdlz.h>
+
+#include <dlz/dlz_ldap_driver.h>
+#include <dlz/sdlz_helper.h>
+#include <named/globals.h>
+
+/*
+ * Need older API functions from ldap.h.
+ */
+#define LDAP_DEPRECATED 1
+
+#include <ldap.h>
+
+#define SIMPLE "simple"
+#define KRB41 "krb41"
+#define KRB42 "krb42"
+#define V2 "v2"
+#define V3 "v3"
+
+static dns_sdlzimplementation_t *dlz_ldap = NULL;
+
+#define dbc_search_limit 30
+#define ALLNODES 1
+#define ALLOWXFR 2
+#define AUTHORITY 3
+#define FINDZONE 4
+#define LOOKUP 5
+
+/*%
+ * Structure to hold everything needed by this "instance" of the LDAP
+ * driver remember, the driver code is only loaded once, but may have
+ * many separate instances.
+ */
+
+typedef struct {
+ db_list_t *db; /*%< handle to a list of DB */
+ int method; /*%< security authentication method */
+ char *user; /*%< who is authenticating */
+ char *cred; /*%< password for simple authentication method */
+ int protocol; /*%< LDAP communication protocol version */
+ char *hosts; /*%< LDAP server hosts */
+} ldap_instance_t;
+
+/* forward references */
+
+static isc_result_t
+dlz_ldap_findzone(void *driverarg, void *dbdata, const char *name,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo);
+
+static void
+dlz_ldap_destroy(void *driverarg, void *dbdata);
+
+/*
+ * Private methods
+ */
+
+/*% checks that the LDAP URL parameters make sense */
+static isc_result_t
+dlz_ldap_checkURL(char *URL, int attrCnt, const char *msg) {
+ isc_result_t result = ISC_R_SUCCESS;
+ int ldap_result;
+ LDAPURLDesc *ldap_url = NULL;
+
+ if (!ldap_is_ldap_url(URL)) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "%s query is not a valid LDAP URL", msg);
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ ldap_result = ldap_url_parse(URL, &ldap_url);
+ if (ldap_result != LDAP_SUCCESS || ldap_url == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "parsing %s query failed", msg);
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ if (ldap_count_values(ldap_url->lud_attrs) < attrCnt) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "%s query must specify at least "
+ "%d attributes to return",
+ msg, attrCnt);
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ if (ldap_url->lud_host != NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "%s query must not specify a host", msg);
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ if (ldap_url->lud_port != 389) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "%s query must not specify a port", msg);
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ if (ldap_url->lud_dn == NULL || strlen(ldap_url->lud_dn) < 1) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "%s query must specify a search base", msg);
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ if (ldap_url->lud_exts != NULL || ldap_url->lud_crit_exts != 0) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "%s uses extensions. "
+ "The driver does not support LDAP extensions.",
+ msg);
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+cleanup:
+ if (ldap_url != NULL) {
+ ldap_free_urldesc(ldap_url);
+ }
+
+ return (result);
+}
+
+/*% Connects / reconnects to LDAP server */
+static isc_result_t
+dlz_ldap_connect(ldap_instance_t *dbi, dbinstance_t *dbc) {
+ isc_result_t result;
+ int ldap_result;
+
+ /* if we have a connection, get ride of it. */
+ if (dbc->dbconn != NULL) {
+ ldap_unbind_s((LDAP *)dbc->dbconn);
+ dbc->dbconn = NULL;
+ }
+
+ /* now connect / reconnect. */
+
+ /* initialize. */
+ dbc->dbconn = ldap_init(dbi->hosts, LDAP_PORT);
+ if (dbc->dbconn == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ /* set protocol version. */
+ ldap_result = ldap_set_option((LDAP *)dbc->dbconn,
+ LDAP_OPT_PROTOCOL_VERSION,
+ &(dbi->protocol));
+ if (ldap_result != LDAP_SUCCESS) {
+ result = ISC_R_NOPERM;
+ goto cleanup;
+ }
+
+ /* "bind" to server. i.e. send username / pass */
+ ldap_result = ldap_bind_s((LDAP *)dbc->dbconn, dbi->user, dbi->cred,
+ dbi->method);
+ if (ldap_result != LDAP_SUCCESS) {
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+
+ /* cleanup if failure. */
+ if (dbc->dbconn != NULL) {
+ ldap_unbind_s((LDAP *)dbc->dbconn);
+ dbc->dbconn = NULL;
+ }
+
+ return (result);
+}
+
+/*%
+ * Properly cleans up a list of database instances.
+ * This function is only used when the driver is compiled for
+ * multithreaded operation.
+ */
+static void
+ldap_destroy_dblist(db_list_t *dblist) {
+ dbinstance_t *ndbi = NULL;
+ dbinstance_t *dbi = NULL;
+
+ /* get the first DBI in the list */
+ ndbi = ISC_LIST_HEAD(*dblist);
+
+ /* loop through the list */
+ while (ndbi != NULL) {
+ dbi = ndbi;
+ /* get the next DBI in the list */
+ ndbi = ISC_LIST_NEXT(dbi, link);
+ /* release DB connection */
+ if (dbi->dbconn != NULL) {
+ ldap_unbind_s((LDAP *)dbi->dbconn);
+ }
+ /* release all memory that comprised a DBI */
+ destroy_sqldbinstance(dbi);
+ }
+ /* release memory for the list structure */
+ isc_mem_put(named_g_mctx, dblist, sizeof(db_list_t));
+}
+
+/*%
+ * Loops through the list of DB instances, attempting to lock
+ * on the mutex. If successful, the DBI is reserved for use
+ * and the thread can perform queries against the database.
+ * If the lock fails, the next one in the list is tried.
+ * looping continues until a lock is obtained, or until
+ * the list has been searched dbc_search_limit times.
+ * This function is only used when the driver is compiled for
+ * multithreaded operation.
+ */
+static dbinstance_t *
+ldap_find_avail_conn(db_list_t *dblist) {
+ dbinstance_t *dbi = NULL;
+ dbinstance_t *head;
+ int count = 0;
+
+ /* get top of list */
+ head = dbi = ISC_LIST_HEAD(*dblist);
+
+ /* loop through list */
+ while (count < dbc_search_limit) {
+ /* try to lock on the mutex */
+ if (isc_mutex_trylock(&dbi->instance_lock) == ISC_R_SUCCESS) {
+ return (dbi); /* success, return the DBI for use. */
+ }
+ /* not successful, keep trying */
+ dbi = ISC_LIST_NEXT(dbi, link);
+
+ /* check to see if we have gone to the top of the list. */
+ if (dbi == NULL) {
+ count++;
+ dbi = head;
+ }
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_INFO,
+ "LDAP driver unable to find available connection "
+ "after searching %d times",
+ count);
+ return (NULL);
+}
+
+static isc_result_t
+ldap_process_results(LDAP *dbc, LDAPMessage *msg, char **attrs, void *ptr,
+ bool allnodes) {
+ isc_result_t result = ISC_R_SUCCESS;
+ int i = 0;
+ int j;
+ int len;
+ char *attribute = NULL;
+ LDAPMessage *entry;
+ char *endp = NULL;
+ char *host = NULL;
+ char *type = NULL;
+ char *data = NULL;
+ char **vals = NULL;
+ int ttl;
+
+ /* make sure there are at least some attributes to process. */
+ REQUIRE(attrs != NULL || attrs[0] != NULL);
+
+ /* get the first entry to process */
+ entry = ldap_first_entry(dbc, msg);
+ if (entry == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_INFO,
+ "LDAP no entries to process.");
+ return (ISC_R_FAILURE);
+ }
+
+ /* loop through all entries returned */
+ while (entry != NULL) {
+ /* reset for this loop */
+ ttl = 0;
+ len = 0;
+ i = 0;
+ attribute = attrs[i];
+
+ /* determine how much space we need for data string */
+ for (j = 0; attrs[j] != NULL; j++) {
+ /* get the list of values for this attribute. */
+ vals = ldap_get_values(dbc, entry, attrs[j]);
+ /* skip empty attributes. */
+ if (vals == NULL || ldap_count_values(vals) < 1) {
+ continue;
+ }
+ /*
+ * we only use the first value. this driver
+ * does not support multi-valued attributes.
+ */
+ len = len + strlen(vals[0]) + 1;
+ /* free vals for next loop */
+ ldap_value_free(vals);
+ } /* end for (j = 0; attrs[j] != NULL, j++) loop */
+
+ /* allocate memory for data string */
+ data = isc_mem_allocate(named_g_mctx, len + 1);
+
+ /*
+ * Make sure data is null termed at the beginning so
+ * we can check if any data was stored to it later.
+ */
+ data[0] = '\0';
+
+ /* reset j to re-use below */
+ j = 0;
+
+ /* loop through the attributes in the order specified. */
+ while (attribute != NULL) {
+ /* get the list of values for this attribute. */
+ vals = ldap_get_values(dbc, entry, attribute);
+
+ /* skip empty attributes. */
+ if (vals == NULL || vals[0] == NULL) {
+ /* increment attribute pointer */
+ attribute = attrs[++i];
+ /* start loop over */
+ continue;
+ }
+
+ /*
+ * j initially = 0. Increment j each time we
+ * set a field that way next loop will set
+ * next field.
+ */
+ switch (j) {
+ case 0:
+ j++;
+ /*
+ * convert text to int, make sure it
+ * worked right
+ */
+ ttl = strtol(vals[0], &endp, 10);
+ if (*endp != '\0' || ttl < 0) {
+ isc_log_write(dns_lctx,
+ DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ,
+ ISC_LOG_ERROR,
+ "LDAP driver ttl must "
+ "be a positive number");
+ goto cleanup;
+ }
+ break;
+ case 1:
+ j++;
+ type = isc_mem_strdup(named_g_mctx, vals[0]);
+ break;
+ case 2:
+ j++;
+ if (allnodes) {
+ host = isc_mem_strdup(named_g_mctx,
+ vals[0]);
+ } else {
+ strcpy(data, vals[0]);
+ }
+ break;
+ case 3:
+ j++;
+ if (allnodes) {
+ strcpy(data, vals[0]);
+ } else {
+ strcat(data, " ");
+ strcat(data, vals[0]);
+ }
+ break;
+ default:
+ strcat(data, " ");
+ strcat(data, vals[0]);
+ break;
+ } /* end switch(j) */
+
+ /* free values */
+ ldap_value_free(vals);
+ vals = NULL;
+
+ /* increment attribute pointer */
+ attribute = attrs[++i];
+ } /* end while (attribute != NULL) */
+
+ if (type == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "LDAP driver unable "
+ "to retrieve DNS type");
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ if (strlen(data) < 1) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "LDAP driver unable "
+ "to retrieve DNS data");
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ if (allnodes && host != NULL) {
+ if (strcasecmp(host, "~") == 0) {
+ result = dns_sdlz_putnamedrr(
+ (dns_sdlzallnodes_t *)ptr, "*", type,
+ ttl, data);
+ } else {
+ result = dns_sdlz_putnamedrr(
+ (dns_sdlzallnodes_t *)ptr, host, type,
+ ttl, data);
+ }
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx,
+ DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "dlz-ldap: putnamedrr failed "
+ "for \"%s %s %u %s\", %s",
+ host, type, ttl, data,
+ isc_result_totext(result));
+ }
+ } else {
+ result = dns_sdlz_putrr((dns_sdlzlookup_t *)ptr, type,
+ ttl, data);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx,
+ DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "dlz-ldap: putrr failed "
+ "for \"%s %u %s\", %s",
+ type, ttl, data,
+ isc_result_totext(result));
+ }
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "LDAP driver failed "
+ "while sending data to BIND.");
+ goto cleanup;
+ }
+
+ /* free memory for type, data and host for next loop */
+ isc_mem_free(named_g_mctx, type);
+ isc_mem_free(named_g_mctx, data);
+ if (host != NULL) {
+ isc_mem_free(named_g_mctx, host);
+ }
+
+ /* get the next entry to process */
+ entry = ldap_next_entry(dbc, entry);
+ } /* end while (entry != NULL) */
+
+cleanup:
+ /* de-allocate memory */
+ if (vals != NULL) {
+ ldap_value_free(vals);
+ }
+ if (host != NULL) {
+ isc_mem_free(named_g_mctx, host);
+ }
+ if (type != NULL) {
+ isc_mem_free(named_g_mctx, type);
+ }
+ if (data != NULL) {
+ isc_mem_free(named_g_mctx, data);
+ }
+
+ return (result);
+}
+
+/*%
+ * This function is the real core of the driver. Zone, record
+ * and client strings are passed in (or NULL is passed if the
+ * string is not available). The type of query we want to run
+ * is indicated by the query flag, and the dbdata object is passed
+ * passed in to. dbdata really holds either:
+ * 1) a list of database instances (in multithreaded mode) OR
+ * 2) a single database instance (in single threaded mode)
+ * The function will construct the query and obtain an available
+ * database instance (DBI). It will then run the query and hopefully
+ * obtain a result set.
+ */
+static isc_result_t
+ldap_get_results(const char *zone, const char *record, const char *client,
+ unsigned int query, void *dbdata, void *ptr) {
+ isc_result_t result;
+ dbinstance_t *dbi = NULL;
+ char *querystring = NULL;
+ LDAPURLDesc *ldap_url = NULL;
+ int ldap_result = 0;
+ LDAPMessage *ldap_msg = NULL;
+ int i;
+ int entries;
+
+ /* get db instance / connection */
+ /* find an available DBI from the list */
+ dbi = ldap_find_avail_conn(
+ (db_list_t *)((ldap_instance_t *)dbdata)->db);
+
+ /* if DBI is null, can't do anything else */
+ if (dbi == NULL) {
+ return (ISC_R_FAILURE);
+ }
+
+ /* set fields */
+ if (zone != NULL) {
+ dbi->zone = isc_mem_strdup(named_g_mctx, zone);
+ } else {
+ dbi->zone = NULL;
+ }
+ if (record != NULL) {
+ dbi->record = isc_mem_strdup(named_g_mctx, record);
+ } else {
+ dbi->record = NULL;
+ }
+ if (client != NULL) {
+ dbi->client = isc_mem_strdup(named_g_mctx, client);
+ } else {
+ dbi->client = NULL;
+ }
+
+ /* what type of query are we going to run? */
+ switch (query) {
+ case ALLNODES:
+ /*
+ * if the query was not passed in from the config file
+ * then we can't run it. return not_implemented, so
+ * it's like the code for that operation was never
+ * built into the driver.... AHHH flexibility!!!
+ */
+ if (dbi->allnodes_q == NULL) {
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup;
+ } else {
+ querystring = build_querystring(named_g_mctx,
+ dbi->allnodes_q);
+ }
+ break;
+ case ALLOWXFR:
+ /* same as comments as ALLNODES */
+ if (dbi->allowxfr_q == NULL) {
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup;
+ } else {
+ querystring = build_querystring(named_g_mctx,
+ dbi->allowxfr_q);
+ }
+ break;
+ case AUTHORITY:
+ /* same as comments as ALLNODES */
+ if (dbi->authority_q == NULL) {
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup;
+ } else {
+ querystring = build_querystring(named_g_mctx,
+ dbi->authority_q);
+ }
+ break;
+ case FINDZONE:
+ /* this is required. It's the whole point of DLZ! */
+ if (dbi->findzone_q == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
+ "No query specified for findzone. "
+ "Findzone requires a query");
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ } else {
+ querystring = build_querystring(named_g_mctx,
+ dbi->findzone_q);
+ }
+ break;
+ case LOOKUP:
+ /* this is required. It's also a major point of DLZ! */
+ if (dbi->lookup_q == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
+ "No query specified for lookup. "
+ "Lookup requires a query");
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ } else {
+ querystring = build_querystring(named_g_mctx,
+ dbi->lookup_q);
+ }
+ break;
+ default:
+ /*
+ * this should never happen. If it does, the code is
+ * screwed up!
+ */
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "Incorrect query flag passed to "
+ "ldap_get_results");
+ result = ISC_R_UNEXPECTED;
+ goto cleanup;
+ }
+
+ /* if the querystring is null, Bummer, outta RAM. UPGRADE TIME!!! */
+ if (querystring == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ /*
+ * output the full query string during debug so we can see
+ * what lame error the query has.
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(1), "\nQuery String: %s\n", querystring);
+
+ /* break URL down into it's component parts, if error cleanup */
+ ldap_result = ldap_url_parse(querystring, &ldap_url);
+ if (ldap_result != LDAP_SUCCESS || ldap_url == NULL) {
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ for (i = 0; i < 3; i++) {
+ /*
+ * dbi->dbconn may be null if trying to reconnect on a
+ * previous query failed.
+ */
+ if (dbi->dbconn == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_INFO,
+ "LDAP driver attempting to re-connect");
+
+ result = dlz_ldap_connect((ldap_instance_t *)dbdata,
+ dbi);
+ if (result != ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ continue;
+ }
+ }
+
+ /* perform ldap search synchronously */
+ ldap_result =
+ ldap_search_s((LDAP *)dbi->dbconn, ldap_url->lud_dn,
+ ldap_url->lud_scope, ldap_url->lud_filter,
+ ldap_url->lud_attrs, 0, &ldap_msg);
+
+ /*
+ * check return code. No such object is ok, just
+ * didn't find what we wanted
+ */
+ switch (ldap_result) {
+ case LDAP_NO_SUCH_OBJECT:
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(1),
+ "No object found matching "
+ "query requirements");
+ result = ISC_R_NOTFOUND;
+ goto cleanup;
+ break;
+ case LDAP_SUCCESS: /* on success do nothing */
+ result = ISC_R_SUCCESS;
+ i = 3;
+ break;
+ case LDAP_SERVER_DOWN:
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_INFO,
+ "LDAP driver attempting to re-connect");
+ result = dlz_ldap_connect((ldap_instance_t *)dbdata,
+ dbi);
+ if (result != ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ break;
+ default:
+ /*
+ * other errors not ok. Log error message and
+ * get out
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "LDAP error: %s",
+ ldap_err2string(ldap_result));
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ break;
+ } /* close switch(ldap_result) */
+ } /* end for (int i = 0 i < 3; i++) */
+
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ switch (query) {
+ case ALLNODES:
+ result = ldap_process_results((LDAP *)dbi->dbconn, ldap_msg,
+ ldap_url->lud_attrs, ptr, true);
+ break;
+ case AUTHORITY:
+ case LOOKUP:
+ result = ldap_process_results((LDAP *)dbi->dbconn, ldap_msg,
+ ldap_url->lud_attrs, ptr, false);
+ break;
+ case ALLOWXFR:
+ entries = ldap_count_entries((LDAP *)dbi->dbconn, ldap_msg);
+ if (entries == 0) {
+ result = ISC_R_NOPERM;
+ } else if (entries > 0) {
+ result = ISC_R_SUCCESS;
+ } else {
+ result = ISC_R_FAILURE;
+ }
+ break;
+ case FINDZONE:
+ entries = ldap_count_entries((LDAP *)dbi->dbconn, ldap_msg);
+ if (entries == 0) {
+ result = ISC_R_NOTFOUND;
+ } else if (entries > 0) {
+ result = ISC_R_SUCCESS;
+ } else {
+ result = ISC_R_FAILURE;
+ }
+ break;
+ default:
+ /*
+ * this should never happen. If it does, the code is
+ * screwed up!
+ */
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "Incorrect query flag passed to "
+ "ldap_get_results");
+ result = ISC_R_UNEXPECTED;
+ }
+
+cleanup:
+ /* it's always good to cleanup after yourself */
+
+ /* if we retrieved results, free them */
+ if (ldap_msg != NULL) {
+ ldap_msgfree(ldap_msg);
+ }
+
+ if (ldap_url != NULL) {
+ ldap_free_urldesc(ldap_url);
+ }
+
+ /* cleanup */
+ if (dbi->zone != NULL) {
+ isc_mem_free(named_g_mctx, dbi->zone);
+ }
+ if (dbi->record != NULL) {
+ isc_mem_free(named_g_mctx, dbi->record);
+ }
+ if (dbi->client != NULL) {
+ isc_mem_free(named_g_mctx, dbi->client);
+ }
+
+ /* release the lock so another thread can use this dbi */
+ isc_mutex_unlock(&dbi->instance_lock);
+
+ /* release query string */
+ if (querystring != NULL) {
+ isc_mem_free(named_g_mctx, querystring);
+ }
+
+ /* return result */
+ return (result);
+}
+
+/*
+ * DLZ methods
+ */
+static isc_result_t
+dlz_ldap_allowzonexfr(void *driverarg, void *dbdata, const char *name,
+ const char *client) {
+ isc_result_t result;
+
+ UNUSED(driverarg);
+
+ /* check to see if we are authoritative for the zone first */
+ result = dlz_ldap_findzone(driverarg, dbdata, name, NULL, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /* get all the zone data */
+ result = ldap_get_results(name, NULL, client, ALLOWXFR, dbdata, NULL);
+ return (result);
+}
+
+static isc_result_t
+dlz_ldap_allnodes(const char *zone, void *driverarg, void *dbdata,
+ dns_sdlzallnodes_t *allnodes) {
+ UNUSED(driverarg);
+ return (ldap_get_results(zone, NULL, NULL, ALLNODES, dbdata, allnodes));
+}
+
+static isc_result_t
+dlz_ldap_authority(const char *zone, void *driverarg, void *dbdata,
+ dns_sdlzlookup_t *lookup) {
+ UNUSED(driverarg);
+ return (ldap_get_results(zone, NULL, NULL, AUTHORITY, dbdata, lookup));
+}
+
+static isc_result_t
+dlz_ldap_findzone(void *driverarg, void *dbdata, const char *name,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo) {
+ UNUSED(driverarg);
+ UNUSED(methods);
+ UNUSED(clientinfo);
+ return (ldap_get_results(name, NULL, NULL, FINDZONE, dbdata, NULL));
+}
+
+static isc_result_t
+dlz_ldap_lookup(const char *zone, const char *name, void *driverarg,
+ void *dbdata, dns_sdlzlookup_t *lookup,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo) {
+ isc_result_t result;
+
+ UNUSED(driverarg);
+ UNUSED(methods);
+ UNUSED(clientinfo);
+
+ if (strcmp(name, "*") == 0) {
+ result = ldap_get_results(zone, "~", NULL, LOOKUP, dbdata,
+ lookup);
+ } else {
+ result = ldap_get_results(zone, name, NULL, LOOKUP, dbdata,
+ lookup);
+ }
+ return (result);
+}
+
+static isc_result_t
+dlz_ldap_create(const char *dlzname, unsigned int argc, char *argv[],
+ void *driverarg, void **dbdata) {
+ isc_result_t result;
+ ldap_instance_t *ldap_inst = NULL;
+ dbinstance_t *dbi = NULL;
+ int protocol;
+ int method;
+ int dbcount;
+ char *endp;
+ /* db_list_t *dblist = NULL; */
+ int i;
+
+ UNUSED(dlzname);
+ UNUSED(driverarg);
+
+ /* if debugging, let user know we are multithreaded. */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(1), "LDAP driver running multithreaded");
+
+ if (argc < 9) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "LDAP driver requires at least "
+ "8 command line args.");
+ return (ISC_R_FAILURE);
+ }
+
+ /* no more than 13 arg's should be passed to the driver */
+ if (argc > 12) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "LDAP driver cannot accept more than "
+ "11 command line args.");
+ return (ISC_R_FAILURE);
+ }
+
+ /* determine protocol version. */
+ if (strncasecmp(argv[2], V2, strlen(V2)) == 0) {
+ protocol = 2;
+ } else if (strncasecmp(argv[2], V3, strlen(V3)) == 0) {
+ protocol = 3;
+ } else {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "LDAP driver protocol must be either %s or %s",
+ V2, V3);
+ return (ISC_R_FAILURE);
+ }
+
+ /* determine connection method. */
+ if (strncasecmp(argv[3], SIMPLE, strlen(SIMPLE)) == 0) {
+ method = LDAP_AUTH_SIMPLE;
+ } else if (strncasecmp(argv[3], KRB41, strlen(KRB41)) == 0) {
+ method = LDAP_AUTH_KRBV41;
+ } else if (strncasecmp(argv[3], KRB42, strlen(KRB42)) == 0) {
+ method = LDAP_AUTH_KRBV42;
+ } else {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "LDAP driver authentication method must be "
+ "one of %s, %s or %s",
+ SIMPLE, KRB41, KRB42);
+ return (ISC_R_FAILURE);
+ }
+
+ /* check how many db connections we should create */
+ dbcount = strtol(argv[1], &endp, 10);
+ if (*endp != '\0' || dbcount < 0) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "LDAP driver database connection count "
+ "must be positive.");
+ return (ISC_R_FAILURE);
+ }
+
+ /* check that LDAP URL parameters make sense */
+ switch (argc) {
+ case 12:
+ result = dlz_ldap_checkURL(argv[11], 0, "allow zone transfer");
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ FALLTHROUGH;
+ case 11:
+ result = dlz_ldap_checkURL(argv[10], 3, "all nodes");
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ FALLTHROUGH;
+ case 10:
+ if (strlen(argv[9]) > 0) {
+ result = dlz_ldap_checkURL(argv[9], 3, "authority");
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+ FALLTHROUGH;
+ case 9:
+ result = dlz_ldap_checkURL(argv[8], 3, "lookup");
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ result = dlz_ldap_checkURL(argv[7], 0, "find zone");
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ break;
+ default:
+ /* not really needed, should shut up compiler. */
+ result = ISC_R_FAILURE;
+ }
+
+ /* allocate memory for LDAP instance */
+ ldap_inst = isc_mem_get(named_g_mctx, sizeof(ldap_instance_t));
+ memset(ldap_inst, 0, sizeof(ldap_instance_t));
+
+ /* store info needed to automatically re-connect. */
+ ldap_inst->protocol = protocol;
+ ldap_inst->method = method;
+ ldap_inst->hosts = isc_mem_strdup(named_g_mctx, argv[6]);
+ ldap_inst->user = isc_mem_strdup(named_g_mctx, argv[4]);
+ ldap_inst->cred = isc_mem_strdup(named_g_mctx, argv[5]);
+
+ /* allocate memory for database connection list */
+ ldap_inst->db = isc_mem_get(named_g_mctx, sizeof(db_list_t));
+
+ /* initialize DB connection list */
+ ISC_LIST_INIT(*(ldap_inst->db));
+
+ /*
+ * create the appropriate number of database instances (DBI)
+ * append each new DBI to the end of the list
+ */
+ for (i = 0; i < dbcount; i++) {
+ /* how many queries were passed in from config file? */
+ switch (argc) {
+ case 9:
+ result = build_sqldbinstance(named_g_mctx, NULL, NULL,
+ NULL, argv[7], argv[8],
+ NULL, &dbi);
+ break;
+ case 10:
+ result = build_sqldbinstance(named_g_mctx, NULL, NULL,
+ argv[9], argv[7], argv[8],
+ NULL, &dbi);
+ break;
+ case 11:
+ result = build_sqldbinstance(named_g_mctx, argv[10],
+ NULL, argv[9], argv[7],
+ argv[8], NULL, &dbi);
+ break;
+ case 12:
+ result = build_sqldbinstance(named_g_mctx, argv[10],
+ argv[11], argv[9], argv[7],
+ argv[8], NULL, &dbi);
+ break;
+ default:
+ /* not really needed, should shut up compiler. */
+ result = ISC_R_FAILURE;
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
+ "LDAP driver created "
+ "database instance object.");
+ } else { /* unsuccessful?, log err msg and cleanup. */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "LDAP driver could not create "
+ "database instance object.");
+ goto cleanup;
+ }
+
+ ISC_LINK_INIT(dbi, link);
+ ISC_LIST_APPEND(*(ldap_inst->db), dbi, link);
+
+ /* attempt to connect */
+ result = dlz_ldap_connect(ldap_inst, dbi);
+
+ /*
+ * if db connection cannot be created, log err msg and
+ * cleanup.
+ */
+ switch (result) {
+ /* success, do nothing */
+ case ISC_R_SUCCESS:
+ break;
+ /*
+ * no memory means ldap_init could not
+ * allocate memory
+ */
+ case ISC_R_NOMEMORY:
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "LDAP driver could not allocate memory "
+ "for connection number %u",
+ i + 1);
+ goto cleanup;
+ break;
+ /*
+ * no perm means ldap_set_option could not set
+ * protocol version
+ */
+ case ISC_R_NOPERM:
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "LDAP driver could not "
+ "set protocol version.");
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ break;
+ /* failure means couldn't connect to ldap server */
+ case ISC_R_FAILURE:
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "LDAP driver could not "
+ "bind connection number %u to server.",
+ i + 1);
+ goto cleanup;
+ break;
+ /*
+ * default should never happen. If it does,
+ * major errors.
+ */
+ default:
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "dlz_ldap_create() failed: %s",
+ isc_result_totext(result));
+ result = ISC_R_UNEXPECTED;
+ goto cleanup;
+ break;
+ } /* end switch(result) */
+
+ /* set DBI = null for next loop through. */
+ dbi = NULL;
+ } /* end for loop */
+
+ /* set dbdata to the ldap_instance we created. */
+ *dbdata = ldap_inst;
+
+ /* hey, we got through all of that ok, return success. */
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ dlz_ldap_destroy(NULL, ldap_inst);
+
+ return (ISC_R_FAILURE);
+}
+
+void
+dlz_ldap_destroy(void *driverarg, void *dbdata) {
+ UNUSED(driverarg);
+
+ if (dbdata != NULL) {
+ /* cleanup the list of DBI's */
+ ldap_destroy_dblist(
+ (db_list_t *)((ldap_instance_t *)dbdata)->db);
+
+ if (((ldap_instance_t *)dbdata)->hosts != NULL) {
+ isc_mem_free(named_g_mctx,
+ ((ldap_instance_t *)dbdata)->hosts);
+ }
+
+ if (((ldap_instance_t *)dbdata)->user != NULL) {
+ isc_mem_free(named_g_mctx,
+ ((ldap_instance_t *)dbdata)->user);
+ }
+
+ if (((ldap_instance_t *)dbdata)->cred != NULL) {
+ isc_mem_free(named_g_mctx,
+ ((ldap_instance_t *)dbdata)->cred);
+ }
+
+ isc_mem_put(named_g_mctx, dbdata, sizeof(ldap_instance_t));
+ }
+}
+
+static dns_sdlzmethods_t dlz_ldap_methods = {
+ dlz_ldap_create,
+ dlz_ldap_destroy,
+ dlz_ldap_findzone,
+ dlz_ldap_lookup,
+ dlz_ldap_authority,
+ dlz_ldap_allnodes,
+ dlz_ldap_allowzonexfr,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+
+/*%
+ * Wrapper around dns_sdlzregister().
+ */
+isc_result_t
+dlz_ldap_init(void) {
+ isc_result_t result;
+
+ /*
+ * Write debugging message to log
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(2), "Registering DLZ ldap driver.");
+
+ result = dns_sdlzregister("ldap", &dlz_ldap_methods, NULL,
+ DNS_SDLZFLAG_RELATIVEOWNER |
+ DNS_SDLZFLAG_RELATIVERDATA,
+ named_g_mctx, &dlz_ldap);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "dns_sdlzregister() failed: %s",
+ isc_result_totext(result));
+ result = ISC_R_UNEXPECTED;
+ }
+
+ return (result);
+}
+
+/*%
+ * Wrapper around dns_sdlzunregister().
+ */
+void
+dlz_ldap_clear(void) {
+ /*
+ * Write debugging message to log
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(2), "Unregistering DLZ ldap driver.");
+
+ if (dlz_ldap != NULL) {
+ dns_sdlzunregister(&dlz_ldap);
+ }
+}
+
+#endif /* ifdef DLZ_LDAP */
diff --git a/contrib/dlz/drivers/dlz_mysql_driver.c b/contrib/dlz/drivers/dlz_mysql_driver.c
new file mode 100644
index 0000000..35c3fd9
--- /dev/null
+++ b/contrib/dlz/drivers/dlz_mysql_driver.c
@@ -0,0 +1,1037 @@
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ * 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.
+ */
+
+/*
+ * Copyright (C) 1999-2001, 2016 Internet Systems Consortium, Inc. ("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 http://mozilla.org/MPL/2.0/.
+ */
+
+#ifdef DLZ_MYSQL
+#include <mysql.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <isc/mem.h>
+#include <isc/platform.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/log.h>
+#include <dns/result.h>
+#include <dns/sdlz.h>
+
+#include <dlz/dlz_mysql_driver.h>
+#include <dlz/sdlz_helper.h>
+#include <named/globals.h>
+
+#if !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 80000
+typedef bool my_bool;
+#endif /* !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 80000 */
+
+static dns_sdlzimplementation_t *dlz_mysql = NULL;
+
+#define dbc_search_limit 30
+#define ALLNODES 1
+#define ALLOWXFR 2
+#define AUTHORITY 3
+#define FINDZONE 4
+#define COUNTZONE 5
+#define LOOKUP 6
+
+#define safeGet(in) in == NULL ? "" : in
+
+/*
+ * Private methods
+ */
+
+/*%
+ * Allocates memory for a new string, and then constructs the new
+ * string by "escaping" the input string. The new string is
+ * safe to be used in queries. This is necessary because we cannot
+ * be sure of what types of strings are passed to us, and we don't
+ * want special characters in the string causing problems.
+ */
+
+static char *
+mysqldrv_escape_string(MYSQL *mysql, const char *instr) {
+ char *outstr;
+ unsigned int len;
+
+ if (instr == NULL) {
+ return (NULL);
+ }
+
+ len = strlen(instr);
+
+ outstr = isc_mem_allocate(named_g_mctx, (2 * len * sizeof(char)) + 1);
+
+ mysql_real_escape_string(mysql, outstr, instr, len);
+
+ return (outstr);
+}
+
+/*%
+ * This function is the real core of the driver. Zone, record
+ * and client strings are passed in (or NULL is passed if the
+ * string is not available). The type of query we want to run
+ * is indicated by the query flag, and the dbdata object is passed
+ * passed in to. dbdata really holds a single database instance.
+ * The function will construct and run the query, hopefully getting
+ * a result set.
+ */
+
+static isc_result_t
+mysql_get_resultset(const char *zone, const char *record, const char *client,
+ unsigned int query, void *dbdata, MYSQL_RES **rs) {
+ isc_result_t result;
+ dbinstance_t *dbi = NULL;
+ char *querystring = NULL;
+ unsigned int i = 0;
+ unsigned int j = 0;
+ int qres = 0;
+
+ if (query != COUNTZONE) {
+ REQUIRE(*rs == NULL);
+ } else {
+ REQUIRE(rs == NULL);
+ }
+
+ /* get db instance / connection */
+ dbi = (dbinstance_t *)dbdata;
+
+ /* if DBI is null, can't do anything else */
+ if (dbi == NULL) {
+ return (ISC_R_FAILURE);
+ }
+
+ /* what type of query are we going to run? */
+ switch (query) {
+ case ALLNODES:
+ /*
+ * if the query was not passed in from the config file
+ * then we can't run it. return not_implemented, so
+ * it's like the code for that operation was never
+ * built into the driver.... AHHH flexibility!!!
+ */
+ if (dbi->allnodes_q == NULL) {
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup;
+ }
+ break;
+ case ALLOWXFR:
+ /* same as comments as ALLNODES */
+ if (dbi->allowxfr_q == NULL) {
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup;
+ }
+ break;
+ case AUTHORITY:
+ /* same as comments as ALLNODES */
+ if (dbi->authority_q == NULL) {
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup;
+ }
+ break;
+ case FINDZONE:
+ /* this is required. It's the whole point of DLZ! */
+ if (dbi->findzone_q == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
+ "No query specified for findzone. "
+ "Findzone requires a query");
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+ break;
+ case COUNTZONE:
+ /* same as comments as ALLNODES */
+ if (dbi->countzone_q == NULL) {
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup;
+ }
+ break;
+ case LOOKUP:
+ /* this is required. It's also a major point of DLZ! */
+ if (dbi->lookup_q == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
+ "No query specified for lookup. "
+ "Lookup requires a query");
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+ break;
+ default:
+ /*
+ * this should never happen. If it does, the code is
+ * screwed up!
+ */
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "Incorrect query flag passed to "
+ "mysql_get_resultset");
+ result = ISC_R_UNEXPECTED;
+ goto cleanup;
+ }
+
+ /*
+ * was a zone string passed? If so, make it safe for use in
+ * queries.
+ */
+ if (zone != NULL) {
+ dbi->zone = mysqldrv_escape_string((MYSQL *)dbi->dbconn, zone);
+ if (dbi->zone == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ } else { /* no string passed, set the string pointer to NULL */
+ dbi->zone = NULL;
+ }
+
+ /*
+ * was a record string passed? If so, make it safe for use in
+ * queries.
+ */
+ if (record != NULL) {
+ dbi->record = mysqldrv_escape_string((MYSQL *)dbi->dbconn,
+ record);
+ if (dbi->record == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ } else { /* no string passed, set the string pointer to NULL */
+ dbi->record = NULL;
+ }
+
+ /*
+ * was a client string passed? If so, make it safe for use in
+ * queries.
+ */
+ if (client != NULL) {
+ dbi->client = mysqldrv_escape_string((MYSQL *)dbi->dbconn,
+ client);
+ if (dbi->client == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ } else { /* no string passed, set the string pointer to NULL */
+ dbi->client = NULL;
+ }
+
+ /*
+ * what type of query are we going to run? this time we build
+ * the actual query to run.
+ */
+ switch (query) {
+ case ALLNODES:
+ querystring = build_querystring(named_g_mctx, dbi->allnodes_q);
+ break;
+ case ALLOWXFR:
+ querystring = build_querystring(named_g_mctx, dbi->allowxfr_q);
+ break;
+ case AUTHORITY:
+ querystring = build_querystring(named_g_mctx, dbi->authority_q);
+ break;
+ case FINDZONE:
+ querystring = build_querystring(named_g_mctx, dbi->findzone_q);
+ break;
+ case COUNTZONE:
+ querystring = build_querystring(named_g_mctx, dbi->countzone_q);
+ break;
+ case LOOKUP:
+ querystring = build_querystring(named_g_mctx, dbi->lookup_q);
+ break;
+ default:
+ /*
+ * this should never happen. If it does, the code is
+ * screwed up!
+ */
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "Incorrect query flag passed to "
+ "mysql_get_resultset");
+ result = ISC_R_UNEXPECTED;
+ goto cleanup;
+ }
+
+ /* if the querystring is null, Bummer, outta RAM. UPGRADE TIME!!! */
+ if (querystring == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ /*
+ * output the full query string during debug so we can see
+ * what lame error the query has.
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(1), "\nQuery String: %s\n", querystring);
+
+ /* attempt query up to 3 times. */
+ for (i = 0; i < 3; i++) {
+ qres = mysql_query((MYSQL *)dbi->dbconn, querystring);
+ if (qres == 0) {
+ break;
+ }
+ for (j = 0; mysql_ping((MYSQL *)dbi->dbconn) != 0 && j < 4; j++)
+ {
+ ;
+ }
+ }
+
+ if (qres == 0) {
+ result = ISC_R_SUCCESS;
+ if (query != COUNTZONE) {
+ *rs = mysql_store_result((MYSQL *)dbi->dbconn);
+ if (*rs == NULL) {
+ result = ISC_R_FAILURE;
+ }
+ }
+ } else {
+ result = ISC_R_FAILURE;
+ }
+
+cleanup:
+ /* it's always good to cleanup after yourself */
+
+ /* free dbi->zone string */
+ if (dbi->zone != NULL) {
+ isc_mem_free(named_g_mctx, dbi->zone);
+ }
+
+ /* free dbi->record string */
+ if (dbi->record != NULL) {
+ isc_mem_free(named_g_mctx, dbi->record);
+ }
+
+ /* free dbi->client string */
+ if (dbi->client != NULL) {
+ isc_mem_free(named_g_mctx, dbi->client);
+ }
+
+ /* release query string */
+ if (querystring != NULL) {
+ isc_mem_free(named_g_mctx, querystring);
+ }
+
+ /* return result */
+ return (result);
+}
+
+/*%
+ * The processing of result sets for lookup and authority are
+ * exactly the same. So that functionality has been moved
+ * into this function to minimize code.
+ */
+
+static isc_result_t
+mysql_process_rs(dns_sdlzlookup_t *lookup, MYSQL_RES *rs) {
+ isc_result_t result = ISC_R_NOTFOUND;
+ MYSQL_ROW row;
+ unsigned int fields;
+ unsigned int j;
+ unsigned int len;
+ char *tmpString;
+ char *endp;
+ int ttl;
+
+ row = mysql_fetch_row(rs); /* get a row from the result set */
+ fields = mysql_num_fields(rs); /* how many columns in result set */
+ while (row != NULL) {
+ switch (fields) {
+ case 1:
+ /*
+ * one column in rs, it's the data field. use
+ * default type of A record, and default TTL
+ * of 86400
+ */
+ result = dns_sdlz_putrr(lookup, "a", 86400,
+ safeGet(row[0]));
+ break;
+ case 2:
+ /*
+ * two columns, data field, and data type.
+ * use default TTL of 86400.
+ */
+ result = dns_sdlz_putrr(lookup, safeGet(row[0]), 86400,
+ safeGet(row[1]));
+ break;
+ case 3:
+ /*
+ * three columns, all data no defaults.
+ * convert text to int, make sure it worked
+ * right.
+ */
+ ttl = strtol(safeGet(row[0]), &endp, 10);
+ if (*endp != '\0' || ttl < 0) {
+ isc_log_write(dns_lctx,
+ DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "mysql driver ttl must be "
+ "a positive number");
+ }
+ result = dns_sdlz_putrr(lookup, safeGet(row[1]), ttl,
+ safeGet(row[2]));
+ break;
+ default:
+ /*
+ * more than 3 fields, concatenate the last
+ * ones together. figure out how long to make
+ * string.
+ */
+ for (j = 2, len = 0; j < fields; j++) {
+ len += strlen(safeGet(row[j])) + 1;
+ }
+ /*
+ * allocate string memory, allow for NULL to
+ * term string
+ */
+ tmpString = isc_mem_allocate(named_g_mctx, len + 1);
+ /* copy field to tmpString */
+ strcpy(tmpString, safeGet(row[2]));
+
+ /*
+ * concat the rest of fields together, space
+ * between each one.
+ */
+ for (j = 3; j < fields; j++) {
+ strcat(tmpString, " ");
+ strcat(tmpString, safeGet(row[j]));
+ }
+ /* convert text to int, make sure it worked right */
+ ttl = strtol(safeGet(row[0]), &endp, 10);
+ if (*endp != '\0' || ttl < 0) {
+ isc_log_write(dns_lctx,
+ DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "mysql driver ttl must be "
+ "a positive number");
+ }
+ /* ok, now tell Bind about it. */
+ result = dns_sdlz_putrr(lookup, safeGet(row[1]), ttl,
+ tmpString);
+ /* done, get rid of this thing. */
+ isc_mem_free(named_g_mctx, tmpString);
+ }
+ /* I sure hope we were successful */
+ if (result != ISC_R_SUCCESS) {
+ /* nope, get rid of the Result set, and log a msg */
+ mysql_free_result(rs);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "dns_sdlz_putrr returned error. "
+ "Error code was: %s",
+ isc_result_totext(result));
+ return (ISC_R_FAILURE);
+ }
+ row = mysql_fetch_row(rs); /* get next row */
+ }
+
+ /* free result set memory */
+ mysql_free_result(rs);
+
+ /* return result code */
+ return (result);
+}
+
+/*
+ * SDLZ interface methods
+ */
+
+/*% determine if the zone is supported by (in) the database */
+
+static isc_result_t
+mysql_findzone(void *driverarg, void *dbdata, const char *name,
+ dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo) {
+ isc_result_t result;
+ MYSQL_RES *rs = NULL;
+ my_ulonglong rows;
+
+ UNUSED(driverarg);
+ UNUSED(methods);
+ UNUSED(clientinfo);
+
+ /* run the query and get the result set from the database. */
+ result = mysql_get_resultset(name, NULL, NULL, FINDZONE, dbdata, &rs);
+ /* if we didn't get a result set, log an err msg. */
+ if (result != ISC_R_SUCCESS || rs == NULL) {
+ if (rs != NULL) {
+ mysql_free_result(rs);
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "mysql driver unable to return "
+ "result set for findzone query");
+ return (ISC_R_FAILURE);
+ }
+ /* count how many rows in result set */
+ rows = mysql_num_rows(rs);
+ /* get rid of result set, we are done with it. */
+ mysql_free_result(rs);
+
+ /* if we returned any rows, zone is supported. */
+ if (rows > 0) {
+ mysql_get_resultset(name, NULL, NULL, COUNTZONE, dbdata, NULL);
+ return (ISC_R_SUCCESS);
+ }
+
+ /* no rows returned, zone is not supported. */
+ return (ISC_R_NOTFOUND);
+}
+
+/*% Determine if the client is allowed to perform a zone transfer */
+static isc_result_t
+mysql_allowzonexfr(void *driverarg, void *dbdata, const char *name,
+ const char *client) {
+ isc_result_t result;
+ MYSQL_RES *rs = NULL;
+ my_ulonglong rows;
+
+ UNUSED(driverarg);
+
+ /* first check if the zone is supported by the database. */
+ result = mysql_findzone(driverarg, dbdata, name, NULL, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ /*
+ * if we get to this point we know the zone is supported by
+ * the database the only questions now are is the zone
+ * transfer is allowed for this client and did the config file
+ * have an allow zone xfr query.
+ *
+ * Run our query, and get a result set from the database.
+ */
+ result = mysql_get_resultset(name, NULL, client, ALLOWXFR, dbdata, &rs);
+ /* if we get "not implemented", send it along. */
+ if (result == ISC_R_NOTIMPLEMENTED) {
+ return (result);
+ }
+ /* if we didn't get a result set, log an err msg. */
+ if (result != ISC_R_SUCCESS || rs == NULL) {
+ if (rs != NULL) {
+ mysql_free_result(rs);
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "mysql driver unable to return "
+ "result set for allow xfr query");
+ return (ISC_R_FAILURE);
+ }
+ /* count how many rows in result set */
+ rows = mysql_num_rows(rs);
+ /* get rid of result set, we are done with it. */
+ mysql_free_result(rs);
+
+ /* if we returned any rows, zone xfr is allowed. */
+ if (rows > 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /* no rows returned, zone xfr not allowed */
+ return (ISC_R_NOPERM);
+}
+
+/*%
+ * If the client is allowed to perform a zone transfer, the next order of
+ * business is to get all the nodes in the zone, so bind can respond to the
+ * query.
+ */
+static isc_result_t
+mysql_allnodes(const char *zone, void *driverarg, void *dbdata,
+ dns_sdlzallnodes_t *allnodes) {
+ isc_result_t result;
+ MYSQL_RES *rs = NULL;
+ MYSQL_ROW row;
+ unsigned int fields;
+ unsigned int j;
+ unsigned int len;
+ char *tmpString;
+ char *endp;
+ int ttl;
+
+ UNUSED(driverarg);
+
+ /* run the query and get the result set from the database. */
+ result = mysql_get_resultset(zone, NULL, NULL, ALLNODES, dbdata, &rs);
+ /* if we get "not implemented", send it along */
+ if (result == ISC_R_NOTIMPLEMENTED) {
+ return (result);
+ }
+ /* if we didn't get a result set, log an err msg. */
+ if (result != ISC_R_SUCCESS) {
+ if (rs != NULL) {
+ mysql_free_result(rs);
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "mysql driver unable to return "
+ "result set for all nodes query");
+ return (ISC_R_FAILURE);
+ }
+
+ result = ISC_R_NOTFOUND;
+
+ row = mysql_fetch_row(rs); /* get a row from the result set */
+ fields = mysql_num_fields(rs); /* how many columns in result set */
+ while (row != NULL) {
+ if (fields < 4) { /* gotta have at least 4 columns */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "mysql driver too few fields returned "
+ "by all nodes query");
+ }
+ /* convert text to int, make sure it worked right */
+ ttl = strtol(safeGet(row[0]), &endp, 10);
+ if (*endp != '\0' || ttl < 0) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "mysql driver ttl must be "
+ "a positive number");
+ }
+ if (fields == 4) {
+ /* tell Bind about it. */
+ result = dns_sdlz_putnamedrr(allnodes, safeGet(row[2]),
+ safeGet(row[1]), ttl,
+ safeGet(row[3]));
+ } else {
+ /*
+ * more than 4 fields, concatenate the last
+ * ones together. figure out how long to make
+ * string.
+ */
+ for (j = 3, len = 0; j < fields; j++) {
+ len += strlen(safeGet(row[j])) + 1;
+ }
+ /* allocate memory, allow for NULL to term string */
+ tmpString = isc_mem_allocate(named_g_mctx, len + 1);
+ /* copy this field to tmpString */
+ strcpy(tmpString, safeGet(row[3]));
+ /* concatenate the rest, with spaces between */
+ for (j = 4; j < fields; j++) {
+ strcat(tmpString, " ");
+ strcat(tmpString, safeGet(row[j]));
+ }
+ /* tell Bind about it. */
+ result = dns_sdlz_putnamedrr(allnodes, safeGet(row[2]),
+ safeGet(row[1]), ttl,
+ tmpString);
+ isc_mem_free(named_g_mctx, tmpString);
+ }
+ /* if we weren't successful, log err msg */
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "dns_sdlz_putnamedrr returned error. "
+ "Error code was: %s",
+ isc_result_totext(result));
+ result = ISC_R_FAILURE;
+ break;
+ }
+ /* get next row from the result set */
+ row = mysql_fetch_row(rs);
+ }
+
+ /* free result set memory */
+ mysql_free_result(rs);
+
+ return (result);
+}
+
+/*% if the lookup function does not return SOA or NS records for the zone,
+ * use this function to get that information for Bind.
+ */
+
+static isc_result_t
+mysql_authority(const char *zone, void *driverarg, void *dbdata,
+ dns_sdlzlookup_t *lookup) {
+ isc_result_t result;
+ MYSQL_RES *rs = NULL;
+
+ UNUSED(driverarg);
+
+ /* run the query and get the result set from the database. */
+ result = mysql_get_resultset(zone, NULL, NULL, AUTHORITY, dbdata, &rs);
+ /* if we get "not implemented", send it along */
+ if (result == ISC_R_NOTIMPLEMENTED) {
+ return (result);
+ }
+ /* if we didn't get a result set, log an err msg. */
+ if (result != ISC_R_SUCCESS) {
+ if (rs != NULL) {
+ mysql_free_result(rs);
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "mysql driver unable to return "
+ "result set for authority query");
+ return (ISC_R_FAILURE);
+ }
+ /*
+ * lookup and authority result sets are processed in the same
+ * manner mysql_process_rs does the job for both functions.
+ */
+ return (mysql_process_rs(lookup, rs));
+}
+
+/*% if zone is supported, lookup up a (or multiple) record(s) in it */
+static isc_result_t
+mysql_lookup(const char *zone, const char *name, void *driverarg, void *dbdata,
+ dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo) {
+ isc_result_t result;
+ MYSQL_RES *rs = NULL;
+
+ UNUSED(driverarg);
+ UNUSED(methods);
+ UNUSED(clientinfo);
+
+ /* run the query and get the result set from the database. */
+ result = mysql_get_resultset(zone, name, NULL, LOOKUP, dbdata, &rs);
+ /* if we didn't get a result set, log an err msg. */
+ if (result != ISC_R_SUCCESS) {
+ if (rs != NULL) {
+ mysql_free_result(rs);
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "mysql driver unable to return "
+ "result set for lookup query");
+ return (ISC_R_FAILURE);
+ }
+ /*
+ * lookup and authority result sets are processed in the same manner
+ * mysql_process_rs does the job for both functions.
+ */
+ return (mysql_process_rs(lookup, rs));
+}
+
+/*%
+ * create an instance of the driver. Remember, only 1 copy of the driver's
+ * code is ever loaded, the driver has to remember which context it's
+ * operating in. This is done via use of the dbdata argument which is
+ * passed into all query functions.
+ */
+static isc_result_t
+mysql_create(const char *dlzname, unsigned int argc, char *argv[],
+ void *driverarg, void **dbdata) {
+ isc_result_t result;
+ dbinstance_t *dbi = NULL;
+ char *tmp = NULL;
+ char *dbname = NULL;
+ char *host = NULL;
+ char *user = NULL;
+ char *pass = NULL;
+ char *socket = NULL;
+ int port;
+ MYSQL *dbc;
+ char *endp;
+ int j;
+ unsigned int flags = 0;
+
+ UNUSED(driverarg);
+ UNUSED(dlzname);
+
+ /* verify we have at least 4 arg's passed to the driver */
+ if (argc < 4) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "mysql driver requires "
+ "at least 4 command line args.");
+ return (ISC_R_FAILURE);
+ }
+
+ /* no more than 8 arg's should be passed to the driver */
+ if (argc > 8) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "mysql driver cannot accept "
+ "more than 7 command line args.");
+ return (ISC_R_FAILURE);
+ }
+
+ /* parse connection string and get parameters. */
+
+ /* get db name - required */
+ dbname = getParameterValue(argv[1], "dbname=");
+ if (dbname == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "mysql driver requires a dbname parameter.");
+ result = ISC_R_FAILURE;
+ goto full_cleanup;
+ }
+
+ /* get db port. Not required, but must be > 0 if specified */
+ tmp = getParameterValue(argv[1], "port=");
+ if (tmp == NULL) {
+ port = 0;
+ } else {
+ port = strtol(tmp, &endp, 10);
+ if (*endp != '\0' || port < 0) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Mysql driver port "
+ "must be a positive number.");
+ isc_mem_free(named_g_mctx, tmp);
+ result = ISC_R_FAILURE;
+ goto full_cleanup;
+ }
+ isc_mem_free(named_g_mctx, tmp);
+ }
+
+ /* how many queries were passed in from config file? */
+ switch (argc) {
+ case 4:
+ result = build_sqldbinstance(named_g_mctx, NULL, NULL, NULL,
+ argv[2], argv[3], NULL, &dbi);
+ break;
+ case 5:
+ result = build_sqldbinstance(named_g_mctx, NULL, NULL, argv[4],
+ argv[2], argv[3], NULL, &dbi);
+ break;
+ case 6:
+ result = build_sqldbinstance(named_g_mctx, argv[5], NULL,
+ argv[4], argv[2], argv[3], NULL,
+ &dbi);
+ break;
+ case 7:
+ result = build_sqldbinstance(named_g_mctx, argv[5], argv[6],
+ argv[4], argv[2], argv[3], NULL,
+ &dbi);
+ break;
+ case 8:
+ result = build_sqldbinstance(named_g_mctx, argv[5], argv[6],
+ argv[4], argv[2], argv[3], argv[7],
+ &dbi);
+ break;
+ default:
+ /* not really needed, should shut up compiler. */
+ result = ISC_R_FAILURE;
+ }
+
+ /* unsuccessful?, log err msg and cleanup. */
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "mysql driver could not create "
+ "database instance object.");
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ /* create and set db connection */
+ dbi->dbconn = mysql_init(NULL);
+
+ /* if db connection cannot be created, log err msg and cleanup. */
+ if (dbi->dbconn == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "mysql driver could not allocate "
+ "memory for database connection");
+ result = ISC_R_FAILURE;
+ goto full_cleanup;
+ }
+
+ tmp = getParameterValue(argv[1], "compress=");
+ if (tmp != NULL) {
+ if (strcasecmp(tmp, "true") == 0) {
+ flags = CLIENT_COMPRESS;
+ }
+ isc_mem_free(named_g_mctx, tmp);
+ }
+
+ tmp = getParameterValue(argv[1], "ssl=");
+ if (tmp != NULL) {
+ if (strcasecmp(tmp, "true") == 0) {
+ flags = flags | CLIENT_SSL;
+ }
+ isc_mem_free(named_g_mctx, tmp);
+ }
+
+ tmp = getParameterValue(argv[1], "space=");
+ if (tmp != NULL) {
+ if (strcasecmp(tmp, "ignore") == 0) {
+ flags = flags | CLIENT_IGNORE_SPACE;
+ }
+ isc_mem_free(named_g_mctx, tmp);
+ }
+
+ dbc = NULL;
+ host = getParameterValue(argv[1], "host=");
+ user = getParameterValue(argv[1], "user=");
+ pass = getParameterValue(argv[1], "pass=");
+ socket = getParameterValue(argv[1], "socket=");
+
+ /* enable automatic reconnection. */
+ if (mysql_options((MYSQL *)dbi->dbconn, MYSQL_OPT_RECONNECT,
+ &(my_bool){ 1 }) != 0)
+ {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_WARNING,
+ "mysql driver failed to set "
+ "MYSQL_OPT_RECONNECT option, "
+ "continuing");
+ }
+
+ for (j = 0; dbc == NULL && j < 4; j++) {
+ dbc = mysql_real_connect((MYSQL *)dbi->dbconn, host, user, pass,
+ dbname, port, socket, flags);
+ }
+
+ /* let user know if we couldn't connect. */
+ if (dbc == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "mysql driver failed to create "
+ "database connection after 4 attempts");
+ result = ISC_R_FAILURE;
+ goto full_cleanup;
+ }
+
+ /* return db connection via dbdata */
+ *dbdata = dbi;
+
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+
+full_cleanup:
+
+ if (dbi != NULL) {
+ destroy_sqldbinstance(dbi);
+ }
+
+cleanup:
+
+ if (dbname != NULL) {
+ isc_mem_free(named_g_mctx, dbname);
+ }
+ if (host != NULL) {
+ isc_mem_free(named_g_mctx, host);
+ }
+ if (user != NULL) {
+ isc_mem_free(named_g_mctx, user);
+ }
+ if (pass != NULL) {
+ isc_mem_free(named_g_mctx, pass);
+ }
+ if (socket != NULL) {
+ isc_mem_free(named_g_mctx, socket);
+ }
+
+ return (result);
+}
+
+/*%
+ * destroy the driver. Remember, only 1 copy of the driver's
+ * code is ever loaded, the driver has to remember which context it's
+ * operating in. This is done via use of the dbdata argument.
+ * so we really only need to clean it up since we are not using driverarg.
+ */
+
+static void
+mysql_destroy(void *driverarg, void *dbdata) {
+ dbinstance_t *dbi;
+
+ UNUSED(driverarg);
+
+ dbi = (dbinstance_t *)dbdata;
+
+ /* release DB connection */
+ if (dbi->dbconn != NULL) {
+ mysql_close((MYSQL *)dbi->dbconn);
+ }
+
+ /* destroy DB instance */
+ destroy_sqldbinstance(dbi);
+}
+
+/* pointers to all our runtime methods. */
+/* this is used during driver registration */
+/* i.e. in dlz_mysql_init below. */
+static dns_sdlzmethods_t dlz_mysql_methods = {
+ mysql_create,
+ mysql_destroy,
+ mysql_findzone,
+ mysql_lookup,
+ mysql_authority,
+ mysql_allnodes,
+ mysql_allowzonexfr,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+
+/*%
+ * Wrapper around dns_sdlzregister().
+ */
+isc_result_t
+dlz_mysql_init(void) {
+ isc_result_t result;
+
+ /*
+ * Write debugging message to log
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(2), "Registering DLZ mysql driver.");
+
+ /* Driver is always threadsafe. Because of the way MySQL handles
+ * threads the MySQL driver can only be used when bind is run single
+ * threaded. Using MySQL with Bind running multi-threaded is not
+ * allowed. When using the MySQL driver "-n1" should always be
+ * passed to Bind to guarantee single threaded operation.
+ */
+ result = dns_sdlzregister("mysql", &dlz_mysql_methods, NULL,
+ DNS_SDLZFLAG_RELATIVEOWNER |
+ DNS_SDLZFLAG_RELATIVERDATA |
+ DNS_SDLZFLAG_THREADSAFE,
+ named_g_mctx, &dlz_mysql);
+ /* if we can't register the driver, there are big problems. */
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "dns_sdlzregister() failed: %s",
+ isc_result_totext(result));
+ result = ISC_R_UNEXPECTED;
+ }
+
+ return (result);
+}
+
+/*%
+ * Wrapper around dns_sdlzunregister().
+ */
+void
+dlz_mysql_clear(void) {
+ /*
+ * Write debugging message to log
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(2), "Unregistering DLZ mysql driver.");
+
+ /* unregister the driver. */
+ if (dlz_mysql != NULL) {
+ dns_sdlzunregister(&dlz_mysql);
+ }
+}
+
+#endif /* ifdef DLZ_MYSQL */
diff --git a/contrib/dlz/drivers/dlz_odbc_driver.c b/contrib/dlz/drivers/dlz_odbc_driver.c
new file mode 100644
index 0000000..dae5510
--- /dev/null
+++ b/contrib/dlz/drivers/dlz_odbc_driver.c
@@ -0,0 +1,1406 @@
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ * 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.
+ */
+
+/*
+ * Copyright (C) 1999-2001, 2016 Internet Systems Consortium, Inc. ("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 http://mozilla.org/MPL/2.0/.
+ */
+
+#ifdef DLZ_ODBC
+#include <sql.h>
+#include <sqlext.h>
+#include <sqltypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <isc/mem.h>
+#include <isc/platform.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/log.h>
+#include <dns/result.h>
+#include <dns/sdlz.h>
+
+#include <dlz/dlz_odbc_driver.h>
+#include <dlz/sdlz_helper.h>
+#include <named/globals.h>
+
+static dns_sdlzimplementation_t *dlz_odbc = NULL;
+
+#define dbc_search_limit 30
+#define ALLNODES 1
+#define ALLOWXFR 2
+#define AUTHORITY 3
+#define FINDZONE 4
+#define LOOKUP 5
+
+#define sqlOK(a) ((a == SQL_SUCCESS || a == SQL_SUCCESS_WITH_INFO) ? -1 : 0)
+
+/*
+ * Private Structures
+ */
+
+/*
+ * structure to hold ODBC connection & statement
+ */
+
+typedef struct {
+ SQLHDBC dbc;
+ SQLHSTMT stmnt;
+} odbc_db_t;
+
+/*
+ * Structure to hold everything needed by this "instance" of the odbc driver
+ * remember, the driver code is only loaded once, but may have many separate
+ * instances
+ */
+
+typedef struct {
+ db_list_t *db; /* handle to a list of DB */
+ SQLHENV sql_env; /* handle to SQL environment */
+ SQLCHAR *dsn;
+ SQLCHAR *user;
+ SQLCHAR *pass;
+} odbc_instance_t;
+
+/* forward reference */
+
+static size_t
+odbc_makesafe(char *to, const char *from, size_t length);
+
+/*
+ * Private methods
+ */
+
+static SQLSMALLINT
+safeLen(void *a) {
+ if (a == NULL) {
+ return (0);
+ }
+ return (strlen((char *)a));
+}
+
+/*% properly cleans up an odbc_instance_t */
+
+static void
+destroy_odbc_instance(odbc_instance_t *odbc_inst) {
+ dbinstance_t *ndbi = NULL;
+ dbinstance_t *dbi = NULL;
+
+ /* get the first DBI in the list */
+ ndbi = ISC_LIST_HEAD(*odbc_inst->db);
+
+ /* loop through the list */
+ while (ndbi != NULL) {
+ dbi = ndbi;
+ /* get the next DBI in the list */
+ ndbi = ISC_LIST_NEXT(dbi, link);
+
+ /* if we have a connection / statement object in memory */
+ if (dbi->dbconn != NULL) {
+ /* free statement handle */
+ if (((odbc_db_t *)(dbi->dbconn))->stmnt != NULL) {
+ SQLFreeHandle(
+ SQL_HANDLE_STMT,
+ ((odbc_db_t *)(dbi->dbconn))->stmnt);
+ ((odbc_db_t *)(dbi->dbconn))->stmnt = NULL;
+ }
+
+ /* disconnect from database & free connection handle */
+ if (((odbc_db_t *)(dbi->dbconn))->dbc != NULL) {
+ SQLDisconnect(((odbc_db_t *)dbi->dbconn)->dbc);
+ SQLFreeHandle(
+ SQL_HANDLE_DBC,
+ ((odbc_db_t *)(dbi->dbconn))->dbc);
+ ((odbc_db_t *)(dbi->dbconn))->dbc = NULL;
+ }
+
+ /* free memory that held connection & statement. */
+ isc_mem_free(named_g_mctx, dbi->dbconn);
+ }
+ /* release all memory that comprised a DBI */
+ destroy_sqldbinstance(dbi);
+ }
+ /* release memory for the list structure */
+ isc_mem_put(named_g_mctx, odbc_inst->db, sizeof(db_list_t));
+
+ /* free sql environment */
+ if (odbc_inst->sql_env != NULL) {
+ SQLFreeHandle(SQL_HANDLE_ENV, odbc_inst->sql_env);
+ }
+
+ /* free ODBC instance strings */
+ if (odbc_inst->dsn != NULL) {
+ isc_mem_free(named_g_mctx, odbc_inst->dsn);
+ }
+ if (odbc_inst->pass != NULL) {
+ isc_mem_free(named_g_mctx, odbc_inst->pass);
+ }
+ if (odbc_inst->user != NULL) {
+ isc_mem_free(named_g_mctx, odbc_inst->user);
+ }
+
+ /* free memory for odbc_inst */
+ if (odbc_inst != NULL) {
+ isc_mem_put(named_g_mctx, odbc_inst, sizeof(odbc_instance_t));
+ }
+}
+
+/*% Connects to database, and creates ODBC statements */
+
+static isc_result_t
+odbc_connect(odbc_instance_t *dbi, odbc_db_t **dbc) {
+ odbc_db_t *ndb = *dbc;
+ SQLRETURN sqlRes;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ if (ndb != NULL) {
+ /*
+ * if db != null, we have to do some cleanup
+ * if statement handle != null free it
+ */
+ if (ndb->stmnt != NULL) {
+ SQLFreeHandle(SQL_HANDLE_STMT, ndb->stmnt);
+ ndb->stmnt = NULL;
+ }
+
+ /* if connection handle != null free it */
+ if (ndb->dbc != NULL) {
+ SQLFreeHandle(SQL_HANDLE_DBC, ndb->dbc);
+ ndb->dbc = NULL;
+ }
+ } else {
+ ndb = isc_mem_allocate(named_g_mctx, sizeof(odbc_db_t));
+ memset(ndb, 0, sizeof(odbc_db_t));
+ }
+
+ sqlRes = SQLAllocHandle(SQL_HANDLE_DBC, dbi->sql_env, &(ndb->dbc));
+ if (!sqlOK(sqlRes)) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Odbc driver unable to allocate memory");
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ sqlRes = SQLConnect(ndb->dbc, dbi->dsn, safeLen(dbi->dsn), dbi->user,
+ safeLen(dbi->user), dbi->pass, safeLen(dbi->pass));
+ if (!sqlOK(sqlRes)) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Odbc driver unable to connect");
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ sqlRes = SQLAllocHandle(SQL_HANDLE_STMT, ndb->dbc, &(ndb->stmnt));
+ if (!sqlOK(sqlRes)) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Odbc driver unable to allocate memory");
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ *dbc = ndb;
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+
+ if (ndb != NULL) {
+ /* if statement handle != null free it */
+ if (ndb->stmnt != NULL) {
+ SQLFreeHandle(SQL_HANDLE_STMT, ndb->stmnt);
+ ndb->stmnt = NULL;
+ }
+
+ /* if connection handle != null free it */
+ if (ndb->dbc != NULL) {
+ SQLDisconnect(ndb->dbc);
+ SQLFreeHandle(SQL_HANDLE_DBC, ndb->dbc);
+ ndb->dbc = NULL;
+ }
+ /* free memory holding ndb */
+ isc_mem_free(named_g_mctx, ndb);
+ }
+
+ return (result);
+}
+
+/*%
+ * Loops through the list of DB instances, attempting to lock
+ * on the mutex. If successful, the DBI is reserved for use
+ * and the thread can perform queries against the database.
+ * If the lock fails, the next one in the list is tried.
+ * looping continues until a lock is obtained, or until
+ * the list has been searched dbc_search_limit times.
+ * This function is only used when the driver is compiled for
+ * multithreaded operation.
+ */
+
+static dbinstance_t *
+odbc_find_avail_conn(db_list_t *dblist) {
+ dbinstance_t *dbi = NULL;
+ dbinstance_t *head;
+ int count = 0;
+
+ /* get top of list */
+ head = dbi = ISC_LIST_HEAD(*dblist);
+
+ /* loop through list */
+ while (count < dbc_search_limit) {
+ /* try to lock on the mutex */
+ if (isc_mutex_trylock(&dbi->instance_lock) == ISC_R_SUCCESS) {
+ return (dbi); /* success, return the DBI for use. */
+ }
+ /* not successful, keep trying */
+ dbi = ISC_LIST_NEXT(dbi, link);
+
+ /* check to see if we have gone to the top of the list. */
+ if (dbi == NULL) {
+ count++;
+ dbi = head;
+ }
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_INFO,
+ "Odbc driver unable to find available "
+ "connection after searching %d times",
+ count);
+ return (NULL);
+}
+
+/*% Allocates memory for a new string, and then constructs the new
+ * string by "escaping" the input string. The new string is
+ * safe to be used in queries. This is necessary because we cannot
+ * be sure of what types of strings are passed to us, and we don't
+ * want special characters in the string causing problems.
+ */
+
+static char *
+odbc_escape_string(const char *instr) {
+ char *outstr;
+ unsigned int len;
+
+ if (instr == NULL) {
+ return (NULL);
+ }
+
+ len = strlen(instr);
+
+ outstr = isc_mem_allocate(named_g_mctx, (2 * len * sizeof(char)) + 1);
+
+ odbc_makesafe(outstr, instr, len);
+
+ return (outstr);
+}
+
+/* ---------------
+ * Escaping arbitrary strings to get valid SQL strings/identifiers.
+ *
+ * Replaces "\\" with "\\\\" and "'" with "''".
+ * length is the length of the buffer pointed to by
+ * from. The buffer at to must be at least 2*length + 1 characters
+ * long. A terminating NUL character is written.
+ *
+ * NOTICE!!!
+ * This function was borrowed directly from PostgreSQL's libpq.
+ *
+ * The copyright statements from the original file containing this
+ * function are included below:
+ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ * ---------------
+ */
+
+static size_t
+odbc_makesafe(char *to, const char *from, size_t length) {
+ const char *source = from;
+ char *target = to;
+ unsigned int remaining = length;
+
+ while (remaining > 0) {
+ switch (*source) {
+ case '\\':
+ *target = '\\';
+ target++;
+ *target = '\\';
+ /* target and remaining are updated below. */
+ break;
+
+ case '\'':
+ *target = '\'';
+ target++;
+ *target = '\'';
+ /* target and remaining are updated below. */
+ break;
+
+ default:
+ *target = *source;
+ /* target and remaining are updated below. */
+ }
+ source++;
+ target++;
+ remaining--;
+ }
+
+ /* Write the terminating NUL character. */
+ *target = '\0';
+
+ return (target - to);
+}
+
+/*%
+ * This function is the real core of the driver. Zone, record
+ * and client strings are passed in (or NULL is passed if the
+ * string is not available). The type of query we want to run
+ * is indicated by the query flag, and the dbdata object is passed
+ * passed in to. dbdata really holds either:
+ * 1) a list of database instances (in multithreaded mode) OR
+ * 2) a single database instance (in single threaded mode)
+ * The function will construct the query and obtain an available
+ * database instance (DBI). It will then run the query and hopefully
+ * obtain a result set. The data base instance that is used is returned
+ * to the caller so they can get the data from the result set from it.
+ * If successful, it will be the responsibility of the caller to close
+ * the cursor, and unlock the mutex of the DBI when they are done with it.
+ * If not successful, this function will perform all the cleanup.
+ */
+
+static isc_result_t
+odbc_get_resultset(const char *zone, const char *record, const char *client,
+ unsigned int query, void *dbdata, dbinstance_t **r_dbi) {
+ isc_result_t result;
+ dbinstance_t *dbi = NULL;
+ char *querystring = NULL;
+ unsigned int j = 0;
+ SQLRETURN sqlRes;
+
+ REQUIRE(*r_dbi == NULL);
+
+ /* get db instance / connection */
+
+ /* find an available DBI from the list */
+ dbi = odbc_find_avail_conn(((odbc_instance_t *)dbdata)->db);
+
+ /* if DBI is null, can't do anything else */
+ if (dbi == NULL) {
+ return (ISC_R_FAILURE);
+ }
+
+ /* what type of query are we going to run? */
+ switch (query) {
+ case ALLNODES:
+ /*
+ * if the query was not passed in from the config file
+ * then we can't run it. return not_implemented, so
+ * it's like the code for that operation was never
+ * built into the driver.... AHHH flexibility!!!
+ */
+ if (dbi->allnodes_q == NULL) {
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup;
+ }
+ break;
+ case ALLOWXFR:
+ /* same as comments as ALLNODES */
+ if (dbi->allowxfr_q == NULL) {
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup;
+ }
+ break;
+ case AUTHORITY:
+ /* same as comments as ALLNODES */
+ if (dbi->authority_q == NULL) {
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup;
+ }
+ break;
+ case FINDZONE:
+ /* this is required. It's the whole point of DLZ! */
+ if (dbi->findzone_q == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
+ "No query specified for findzone. "
+ "Findzone requires a query");
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+ break;
+ case LOOKUP:
+ /* this is required. It's also a major point of DLZ! */
+ if (dbi->lookup_q == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
+ "No query specified for lookup. "
+ "Lookup requires a query");
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+ break;
+ default:
+ /*
+ * this should never happen. If it does, the code is
+ * screwed up!
+ */
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "Incorrect query flag passed to "
+ "odbc_get_resultset");
+ result = ISC_R_UNEXPECTED;
+ goto cleanup;
+ }
+
+ /*
+ * was a zone string passed? If so, make it safe for use in
+ * queries.
+ */
+ if (zone != NULL) {
+ dbi->zone = odbc_escape_string(zone);
+ if (dbi->zone == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ } else { /* no string passed, set the string pointer to NULL */
+ dbi->zone = NULL;
+ }
+
+ /*
+ * was a record string passed? If so, make it safe for use in
+ * queries.
+ */
+ if (record != NULL) {
+ dbi->record = odbc_escape_string(record);
+ if (dbi->record == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ } else { /* no string passed, set the string pointer to NULL */
+ dbi->record = NULL;
+ }
+
+ /*
+ * was a client string passed? If so, make it safe for use in
+ * queries.
+ */
+ if (client != NULL) {
+ dbi->client = odbc_escape_string(client);
+ if (dbi->client == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ } else { /* no string passed, set the string pointer to NULL */
+ dbi->client = NULL;
+ }
+
+ /*
+ * what type of query are we going to run?
+ * this time we build the actual query to run.
+ */
+ switch (query) {
+ case ALLNODES:
+ querystring = build_querystring(named_g_mctx, dbi->allnodes_q);
+ break;
+ case ALLOWXFR:
+ querystring = build_querystring(named_g_mctx, dbi->allowxfr_q);
+ break;
+ case AUTHORITY:
+ querystring = build_querystring(named_g_mctx, dbi->authority_q);
+ break;
+ case FINDZONE:
+ querystring = build_querystring(named_g_mctx, dbi->findzone_q);
+ break;
+ case LOOKUP:
+ querystring = build_querystring(named_g_mctx, dbi->lookup_q);
+ break;
+ default:
+ /*
+ * this should never happen. If it does, the code is
+ * screwed up!
+ */
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "Incorrect query flag passed to "
+ "odbc_get_resultset");
+ result = ISC_R_UNEXPECTED;
+ goto cleanup;
+ }
+
+ /* if the querystring is null, Bummer, outta RAM. UPGRADE TIME!!! */
+ if (querystring == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ /* output the full query string during debug so we can see */
+ /* what lame error the query has. */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(1), "\nQuery String: %s\n", querystring);
+
+ /* attempt query up to 3 times. */
+ for (j = 0; j < 3; j++) {
+ /* try to get result set */
+ sqlRes = SQLExecDirect(((odbc_db_t *)dbi->dbconn)->stmnt,
+ (SQLCHAR *)querystring,
+ (SQLINTEGER)strlen(querystring));
+
+ /* if error, reset DB connection */
+ if (!sqlOK(sqlRes)) {
+ /* close cursor */
+ SQLCloseCursor(((odbc_db_t *)dbi->dbconn)->stmnt);
+ /* attempt to reconnect */
+ result = odbc_connect((odbc_instance_t *)dbdata,
+ (odbc_db_t **)&(dbi->dbconn));
+ /* check if we reconnected */
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ /* in case this is the last time through the loop */
+ result = ISC_R_FAILURE;
+ } else {
+ result = ISC_R_SUCCESS;
+ /* return dbi */
+ *r_dbi = dbi;
+ /* result set ok, break loop */
+ break;
+ }
+ } /* end for loop */
+
+cleanup: /* it's always good to cleanup after yourself */
+
+ /* free dbi->zone string */
+ if (dbi->zone != NULL) {
+ isc_mem_free(named_g_mctx, dbi->zone);
+ }
+
+ /* free dbi->record string */
+ if (dbi->record != NULL) {
+ isc_mem_free(named_g_mctx, dbi->record);
+ }
+
+ /* free dbi->client string */
+ if (dbi->client != NULL) {
+ isc_mem_free(named_g_mctx, dbi->client);
+ }
+
+ /* if we are done using this dbi, release the lock */
+ if (result != ISC_R_SUCCESS) {
+ isc_mutex_unlock(&dbi->instance_lock);
+ }
+
+ /* release query string */
+ if (querystring != NULL) {
+ isc_mem_free(named_g_mctx, querystring);
+ }
+
+ /* return result */
+ return (result);
+}
+
+/*%
+ * Gets a single field from the ODBC statement. The memory for the
+ * returned data is dynamically allocated. If this method is successful
+ * it is the responsibility of the caller to free the memory using
+ * isc_mem_free(named_g_mctx, *ptr);
+ */
+
+static isc_result_t
+odbc_getField(SQLHSTMT *stmnt, SQLSMALLINT field, char **data) {
+ SQLLEN size;
+
+ REQUIRE(data != NULL && *data == NULL);
+
+ if (sqlOK(SQLColAttribute(stmnt, field, SQL_DESC_DISPLAY_SIZE, NULL, 0,
+ NULL, &size)) &&
+ size > 0)
+ {
+ *data = isc_mem_allocate(named_g_mctx, size + 1);
+ if (data != NULL) {
+ if (sqlOK(SQLGetData(stmnt, field, SQL_C_CHAR, *data,
+ size + 1, &size)))
+ {
+ return (ISC_R_SUCCESS);
+ }
+ isc_mem_free(named_g_mctx, *data);
+ }
+ }
+ return (ISC_R_FAILURE);
+}
+
+/*%
+ * Gets multiple fields from the ODBC statement. The memory for the
+ * returned data is dynamically allocated. If this method is successful
+ * it is the responsibility of the caller to free the memory using
+ * isc_mem_free(named_g_mctx, *ptr);
+ */
+
+static isc_result_t
+odbc_getManyFields(SQLHSTMT *stmnt, SQLSMALLINT startField,
+ SQLSMALLINT endField, char **retData) {
+ isc_result_t result;
+ SQLLEN size;
+ int totSize = 0;
+ SQLSMALLINT i;
+ int j = 0;
+ char *data;
+
+ REQUIRE(retData != NULL && *retData == NULL);
+ REQUIRE(startField > 0 && startField <= endField);
+
+ /* determine how large the data is */
+ for (i = startField; i <= endField; i++) {
+ if (sqlOK(SQLColAttribute(stmnt, i, SQL_DESC_DISPLAY_SIZE, NULL,
+ 0, NULL, &size)) &&
+ size > 0)
+ {
+ {
+ /* always allow for a " " (space) character */
+ totSize += (size + 1);
+ /* after the data item */
+ }
+ }
+ }
+
+ if (totSize < 1) {
+ return (ISC_R_FAILURE);
+ }
+
+ /* allow for a "\n" at the end of the string/ */
+ data = isc_mem_allocate(named_g_mctx, ++totSize);
+
+ result = ISC_R_FAILURE;
+
+ /* get the data and concat all fields into a large string */
+ for (i = startField; i <= endField; i++) {
+ if (sqlOK(SQLGetData(stmnt, i, SQL_C_CHAR, &(data[j]),
+ totSize - j, &size)))
+ {
+ if (size > 0) {
+ j += size;
+ data[j++] = ' ';
+ data[j] = '\0';
+ result = ISC_R_SUCCESS;
+ }
+ } else {
+ isc_mem_free(named_g_mctx, data);
+ return (ISC_R_FAILURE);
+ }
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ isc_mem_free(named_g_mctx, data);
+ return (result);
+ }
+
+ *retData = data;
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * The processing of result sets for lookup and authority are
+ * exactly the same. So that functionality has been moved
+ * into this function to minimize code.
+ */
+
+static isc_result_t
+odbc_process_rs(dns_sdlzlookup_t *lookup, dbinstance_t *dbi) {
+ isc_result_t result;
+ SQLSMALLINT fields;
+ SQLHSTMT *stmnt;
+ char *ttl_s;
+ char *type;
+ char *data;
+ char *endp;
+ int ttl;
+
+ REQUIRE(dbi != NULL);
+
+ stmnt = ((odbc_db_t *)(dbi->dbconn))->stmnt;
+
+ /* get number of columns */
+ if (!sqlOK(SQLNumResultCols(stmnt, &fields))) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Odbc driver unable to process result set");
+ result = ISC_R_FAILURE;
+ goto process_rs_cleanup;
+ }
+
+ /* get things ready for processing */
+ result = ISC_R_FAILURE;
+
+ while (sqlOK(SQLFetch(stmnt))) {
+ /* set to null for next pass through */
+ data = type = ttl_s = NULL;
+
+ switch (fields) {
+ case 1:
+ /*
+ * one column in rs, it's the data field. use
+ * default type of A record, and default TTL
+ * of 86400. attempt to get data, & tell bind
+ * about it.
+ */
+ if ((result = odbc_getField(stmnt, 1, &data)) ==
+ ISC_R_SUCCESS)
+ {
+ result = dns_sdlz_putrr(lookup, "a", 86400,
+ data);
+ }
+ break;
+ case 2:
+ /*
+ * two columns, data field, and data type.
+ * use default TTL of 86400. attempt to get
+ * DNS type & data, then tell bind about it.
+ */
+ if ((result = odbc_getField(stmnt, 1, &type)) ==
+ ISC_R_SUCCESS &&
+ (result = odbc_getField(stmnt, 2, &data)) ==
+ ISC_R_SUCCESS)
+ {
+ result = dns_sdlz_putrr(lookup, type, 86400,
+ data);
+ }
+ break;
+ default:
+ /*
+ * 3 fields or more, concatenate the last ones
+ * together. attempt to get DNS ttl, type,
+ * data then tell Bind about them.
+ */
+ if ((result = odbc_getField(stmnt, 1, &ttl_s)) ==
+ ISC_R_SUCCESS &&
+ (result = odbc_getField(stmnt, 2, &type)) ==
+ ISC_R_SUCCESS &&
+ (result = odbc_getManyFields(
+ stmnt, 3, fields, &data)) == ISC_R_SUCCESS)
+ {
+ /* try to convert ttl string to int */
+ ttl = strtol(ttl_s, &endp, 10);
+ /* failure converting ttl. */
+ if (*endp != '\0' || ttl < 0) {
+ isc_log_write(dns_lctx,
+ DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ,
+ ISC_LOG_ERROR,
+ "Odbc driver ttl must "
+ "be a positive number");
+ result = ISC_R_FAILURE;
+ } else {
+ /*
+ * successful converting TTL,
+ * tell Bind everything
+ */
+ result = dns_sdlz_putrr(lookup, type,
+ ttl, data);
+ }
+ } /* closes bid if () */
+ } /* closes switch(fields) */
+
+ /* clean up mem */
+ if (ttl_s != NULL) {
+ isc_mem_free(named_g_mctx, ttl_s);
+ }
+ if (type != NULL) {
+ isc_mem_free(named_g_mctx, type);
+ }
+ if (data != NULL) {
+ isc_mem_free(named_g_mctx, data);
+ }
+
+ /* I sure hope we were successful */
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "dns_sdlz_putrr returned error. "
+ "Error code was: %s",
+ isc_result_totext(result));
+ result = ISC_R_FAILURE;
+ goto process_rs_cleanup;
+ }
+ } /* closes while loop */
+
+process_rs_cleanup:
+
+ /* close cursor */
+ SQLCloseCursor(((odbc_db_t *)(dbi->dbconn))->stmnt);
+
+ /* free lock on dbi so someone else can use it. */
+ isc_mutex_unlock(&dbi->instance_lock);
+
+ return (result);
+}
+
+/*
+ * SDLZ interface methods
+ */
+
+/*% determine if the zone is supported by (in) the database */
+
+static isc_result_t
+odbc_findzone(void *driverarg, void *dbdata, const char *name,
+ dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo) {
+ isc_result_t result;
+ dbinstance_t *dbi = NULL;
+
+ UNUSED(driverarg);
+ UNUSED(methods);
+ UNUSED(clientinfo);
+
+ /* run the query and get the result set from the database. */
+ /* if result != ISC_R_SUCCESS cursor and mutex already cleaned up. */
+ /* so we don't have to do it here. */
+ result = odbc_get_resultset(name, NULL, NULL, FINDZONE, dbdata, &dbi);
+
+ /* Check that we got a result set with data */
+ if (result == ISC_R_SUCCESS &&
+ !sqlOK(SQLFetch(((odbc_db_t *)(dbi->dbconn))->stmnt)))
+ {
+ result = ISC_R_NOTFOUND;
+ }
+
+ if (dbi != NULL) {
+ /* get rid of result set, we are done with it. */
+ SQLCloseCursor(((odbc_db_t *)(dbi->dbconn))->stmnt);
+
+ /* free lock on dbi so someone else can use it. */
+ isc_mutex_unlock(&dbi->instance_lock);
+ }
+
+ return (result);
+}
+
+/*% Determine if the client is allowed to perform a zone transfer */
+static isc_result_t
+odbc_allowzonexfr(void *driverarg, void *dbdata, const char *name,
+ const char *client) {
+ isc_result_t result;
+ dbinstance_t *dbi = NULL;
+
+ UNUSED(driverarg);
+
+ /* first check if the zone is supported by the database. */
+ result = odbc_findzone(driverarg, dbdata, name, NULL, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ /*
+ * if we get to this point we know the zone is supported by
+ * the database. the only questions now are is the zone
+ * transfer is allowed for this client and did the config file
+ * have an allow zone xfr query
+ *
+ * Run our query, and get a result set from the database. if
+ * result != ISC_R_SUCCESS cursor and mutex already cleaned
+ * up, so we don't have to do it here.
+ */
+ result = odbc_get_resultset(name, NULL, client, ALLOWXFR, dbdata, &dbi);
+
+ /* if we get "not implemented", send it along. */
+ if (result == ISC_R_NOTIMPLEMENTED) {
+ return (result);
+ }
+
+ /* Check that we got a result set with data */
+ if (result == ISC_R_SUCCESS &&
+ !sqlOK(SQLFetch(((odbc_db_t *)(dbi->dbconn))->stmnt)))
+ {
+ result = ISC_R_NOPERM;
+ }
+
+ if (dbi != NULL) {
+ /* get rid of result set, we are done with it. */
+ SQLCloseCursor(((odbc_db_t *)(dbi->dbconn))->stmnt);
+
+ /* free lock on dbi so someone else can use it. */
+ isc_mutex_unlock(&dbi->instance_lock);
+ }
+
+ return (result);
+}
+
+/*%
+ * If the client is allowed to perform a zone transfer, the next order of
+ * business is to get all the nodes in the zone, so bind can respond to the
+ * query.
+ */
+
+static isc_result_t
+odbc_allnodes(const char *zone, void *driverarg, void *dbdata,
+ dns_sdlzallnodes_t *allnodes) {
+ isc_result_t result;
+ dbinstance_t *dbi = NULL;
+ SQLHSTMT *stmnt;
+ SQLSMALLINT fields;
+ char *data;
+ char *type;
+ char *ttl_s;
+ int ttl;
+ char *host;
+ char *endp;
+
+ UNUSED(driverarg);
+
+ /* run the query and get the result set from the database. */
+ result = odbc_get_resultset(zone, NULL, NULL, ALLNODES, dbdata, &dbi);
+
+ /* if we get "not implemented", send it along */
+ if (result == ISC_R_NOTIMPLEMENTED) {
+ return (result);
+ }
+
+ /* if we didn't get a result set, log an err msg. */
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Odbc driver unable to return "
+ "result set for all nodes query");
+ return (ISC_R_FAILURE);
+ }
+
+ stmnt = ((odbc_db_t *)(dbi->dbconn))->stmnt;
+
+ /* get number of columns */
+ if (!sqlOK(SQLNumResultCols(stmnt, &fields))) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Odbc driver unable to process result set");
+ result = ISC_R_FAILURE;
+ goto allnodes_cleanup;
+ }
+
+ if (fields < 4) { /* gotta have at least 4 columns */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Odbc driver too few fields returned by "
+ "all nodes query");
+ result = ISC_R_FAILURE;
+ goto allnodes_cleanup;
+ }
+
+ /* get things ready for processing */
+ result = ISC_R_FAILURE;
+
+ while (sqlOK(SQLFetch(stmnt))) {
+ /* set to null for next pass through */
+ data = host = type = ttl_s = NULL;
+
+ /*
+ * attempt to get DNS ttl, type, host, data then tell
+ * Bind about them
+ */
+ if ((result = odbc_getField(stmnt, 1, &ttl_s)) ==
+ ISC_R_SUCCESS &&
+ (result = odbc_getField(stmnt, 2, &type)) ==
+ ISC_R_SUCCESS &&
+ (result = odbc_getField(stmnt, 3, &host)) ==
+ ISC_R_SUCCESS &&
+ (result = odbc_getManyFields(stmnt, 4, fields, &data)) ==
+ ISC_R_SUCCESS)
+ {
+ /* convert ttl string to int */
+ ttl = strtol(ttl_s, &endp, 10);
+ /* failure converting ttl. */
+ if (*endp != '\0' || ttl < 0) {
+ isc_log_write(dns_lctx,
+ DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Odbc driver ttl must be "
+ "a positive number");
+ result = ISC_R_FAILURE;
+ } else {
+ /* successful converting TTL, tell Bind */
+ result = dns_sdlz_putnamedrr(allnodes, host,
+ type, ttl, data);
+ }
+ } /* closes big if () */
+
+ /* clean up mem */
+ if (ttl_s != NULL) {
+ isc_mem_free(named_g_mctx, ttl_s);
+ }
+ if (type != NULL) {
+ isc_mem_free(named_g_mctx, type);
+ }
+ if (host != NULL) {
+ isc_mem_free(named_g_mctx, host);
+ }
+ if (data != NULL) {
+ isc_mem_free(named_g_mctx, data);
+ }
+
+ /* if we weren't successful, log err msg */
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "dns_sdlz_putnamedrr returned error. "
+ "Error code was: %s",
+ isc_result_totext(result));
+ result = ISC_R_FAILURE;
+ goto allnodes_cleanup;
+ }
+ } /* closes while loop */
+
+allnodes_cleanup:
+
+ /* close cursor */
+ SQLCloseCursor(((odbc_db_t *)(dbi->dbconn))->stmnt);
+
+ /* free lock on dbi so someone else can use it. */
+ isc_mutex_unlock(&dbi->instance_lock);
+
+ return (result);
+}
+
+/*%
+ * if the lookup function does not return SOA or NS records for the zone,
+ * use this function to get that information for Bind.
+ */
+
+static isc_result_t
+odbc_authority(const char *zone, void *driverarg, void *dbdata,
+ dns_sdlzlookup_t *lookup) {
+ isc_result_t result;
+ dbinstance_t *dbi = NULL;
+
+ UNUSED(driverarg);
+
+ /* run the query and get the result set from the database. */
+ result = odbc_get_resultset(zone, NULL, NULL, AUTHORITY, dbdata, &dbi);
+ /* if we get "not implemented", send it along */
+ if (result == ISC_R_NOTIMPLEMENTED) {
+ return (result);
+ }
+ /* if we didn't get a result set, log an err msg. */
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Odbc driver unable to return "
+ "result set for authority query");
+ return (ISC_R_FAILURE);
+ }
+ /* lookup and authority result sets are processed in the same manner */
+ /* odbc_process_rs does the job for both functions. */
+ return (odbc_process_rs(lookup, dbi));
+}
+
+/*% if zone is supported, lookup up a (or multiple) record(s) in it */
+
+static isc_result_t
+odbc_lookup(const char *zone, const char *name, void *driverarg, void *dbdata,
+ dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo) {
+ isc_result_t result;
+ dbinstance_t *dbi = NULL;
+
+ UNUSED(driverarg);
+ UNUSED(methods);
+ UNUSED(clientinfo);
+
+ /* run the query and get the result set from the database. */
+ result = odbc_get_resultset(zone, name, NULL, LOOKUP, dbdata, &dbi);
+ /* if we didn't get a result set, log an err msg. */
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Odbc driver unable to return "
+ "result set for lookup query");
+ return (ISC_R_FAILURE);
+ }
+ /* lookup and authority result sets are processed in the same manner */
+ /* odbc_process_rs does the job for both functions. */
+ return (odbc_process_rs(lookup, dbi));
+}
+
+/*%
+ * create an instance of the driver. Remember, only 1 copy of the driver's
+ * code is ever loaded, the driver has to remember which context it's
+ * operating in. This is done via use of the dbdata argument which is
+ * passed into all query functions.
+ */
+static isc_result_t
+odbc_create(const char *dlzname, unsigned int argc, char *argv[],
+ void *driverarg, void **dbdata) {
+ isc_result_t result;
+ odbc_instance_t *odbc_inst = NULL;
+ dbinstance_t *db = NULL;
+ SQLRETURN sqlRes;
+ int dbcount;
+ int i;
+ char *endp;
+
+ UNUSED(dlzname);
+ UNUSED(driverarg);
+
+ /* if debugging, let user know we are multithreaded. */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(1), "Odbc driver running multithreaded");
+
+ /* verify we have at least 5 arg's passed to the driver */
+ if (argc < 5) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Odbc driver requires at least "
+ "4 command line args.");
+ return (ISC_R_FAILURE);
+ }
+
+ /* no more than 8 arg's should be passed to the driver */
+ if (argc > 8) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Odbc driver cannot accept more than "
+ "7 command line args.");
+ return (ISC_R_FAILURE);
+ }
+
+ /* multithreaded build can have multiple DB connections */
+ /* check how many db connections we should create */
+ dbcount = strtol(argv[1], &endp, 10);
+ if (*endp != '\0' || dbcount < 0) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Odbc driver database connection count "
+ "must be positive.");
+ return (ISC_R_FAILURE);
+ }
+
+ /* allocate memory for odbc instance */
+ odbc_inst = isc_mem_get(named_g_mctx, sizeof(odbc_instance_t));
+ memset(odbc_inst, 0, sizeof(odbc_instance_t));
+
+ /* parse connection string and get parameters. */
+
+ /* get odbc database dsn - required */
+ odbc_inst->dsn = (SQLCHAR *)getParameterValue(argv[2], "dsn=");
+ if (odbc_inst->dsn == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "odbc driver requires a dns parameter.");
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+ /* get odbc database username */
+ /* if no username was passed, set odbc_inst.user = NULL; */
+ odbc_inst->user = (SQLCHAR *)getParameterValue(argv[2], "user=");
+
+ /* get odbc database password */
+ /* if no password was passed, set odbc_inst.pass = NULL; */
+ odbc_inst->pass = (SQLCHAR *)getParameterValue(argv[2], "pass=");
+
+ /* create odbc environment & set environment to ODBC V3 */
+ if (odbc_inst->sql_env == NULL) {
+ /* create environment handle */
+ sqlRes = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE,
+ &(odbc_inst->sql_env));
+ if (!sqlOK(sqlRes)) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_INFO,
+ "Odbc driver unable to allocate memory");
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ /*set ODBC version = 3 */
+ sqlRes = SQLSetEnvAttr(odbc_inst->sql_env,
+ SQL_ATTR_ODBC_VERSION,
+ (void *)SQL_OV_ODBC3, 0);
+ if (!sqlOK(sqlRes)) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_INFO,
+ "Unable to configure ODBC environment");
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ }
+
+ /* allocate memory for database connection list */
+ odbc_inst->db = isc_mem_get(named_g_mctx, sizeof(db_list_t));
+
+ /* initialize DB connection list */
+ ISC_LIST_INIT(*odbc_inst->db);
+
+ /* create the appropriate number of database instances (DBI) */
+ /* append each new DBI to the end of the list */
+ for (i = 0; i < dbcount; i++) {
+ /* how many queries were passed in from config file? */
+ switch (argc) {
+ case 5:
+ result = build_sqldbinstance(named_g_mctx, NULL, NULL,
+ NULL, argv[3], argv[4],
+ NULL, &db);
+ break;
+ case 6:
+ result = build_sqldbinstance(named_g_mctx, NULL, NULL,
+ argv[5], argv[3], argv[4],
+ NULL, &db);
+ break;
+ case 7:
+ result = build_sqldbinstance(named_g_mctx, argv[6],
+ NULL, argv[5], argv[3],
+ argv[4], NULL, &db);
+ break;
+ case 8:
+ result = build_sqldbinstance(named_g_mctx, argv[6],
+ argv[7], argv[5], argv[3],
+ argv[4], NULL, &db);
+ break;
+ default:
+ /* not really needed, should shut up compiler. */
+ result = ISC_R_FAILURE;
+ }
+
+ /* unsuccessful?, log err msg and cleanup. */
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Odbc driver could not create "
+ "database instance object.");
+ goto cleanup;
+ }
+
+ /* when multithreaded, build a list of DBI's */
+ ISC_LINK_INIT(db, link);
+ ISC_LIST_APPEND(*odbc_inst->db, db, link);
+
+ result = odbc_connect(odbc_inst, (odbc_db_t **)&(db->dbconn));
+
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * if multi threaded, let user know which
+ * connection failed. user could be
+ * attempting to create 10 db connections and
+ * for some reason the db backend only allows
+ * 9.
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Odbc driver failed to create database "
+ "connection number %u after 3 attempts",
+ i + 1);
+ goto cleanup;
+ }
+
+ /* set DB = null for next loop through. */
+ db = NULL;
+ } /* end for loop */
+
+ /* set dbdata to the odbc_instance we created. */
+ *dbdata = odbc_inst;
+
+ /* hey, we got through all of that ok, return success. */
+ return (ISC_R_SUCCESS);
+
+cleanup:
+
+ destroy_odbc_instance(odbc_inst);
+
+ return (result);
+}
+
+/*%
+ * destroy an instance of the driver. Remember, only 1 copy of the driver's
+ * code is ever loaded, the driver has to remember which context it's
+ * operating in. This is done via use of the dbdata argument.
+ * so we really only need to clean it up since we are not using driverarg.
+ */
+
+static void
+odbc_destroy(void *driverarg, void *dbdata) {
+ UNUSED(driverarg);
+
+ destroy_odbc_instance((odbc_instance_t *)dbdata);
+}
+
+/* pointers to all our runtime methods. */
+/* this is used during driver registration */
+/* i.e. in dlz_odbc_init below. */
+static dns_sdlzmethods_t dlz_odbc_methods = {
+ odbc_create,
+ odbc_destroy,
+ odbc_findzone,
+ odbc_lookup,
+ odbc_authority,
+ odbc_allnodes,
+ odbc_allowzonexfr,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+
+/*%
+ * Wrapper around dns_sdlzregister().
+ */
+isc_result_t
+dlz_odbc_init(void) {
+ isc_result_t result;
+
+ /*
+ * Write debugging message to log
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(2), "Registering DLZ odbc driver.");
+
+ /*
+ * Driver is always threadsafe. When multithreaded all
+ * functions use multithreaded code. When not multithreaded,
+ * all functions can only be entered once, but only 1 thread
+ * of operation is available in Bind. So everything is still
+ * threadsafe.
+ */
+ result = dns_sdlzregister("odbc", &dlz_odbc_methods, NULL,
+ DNS_SDLZFLAG_RELATIVEOWNER |
+ DNS_SDLZFLAG_RELATIVERDATA |
+ DNS_SDLZFLAG_THREADSAFE,
+ named_g_mctx, &dlz_odbc);
+ /* if we can't register the driver, there are big problems. */
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "dns_sdlzregister() failed: %s",
+ isc_result_totext(result));
+ result = ISC_R_UNEXPECTED;
+ }
+
+ return (result);
+}
+
+/*%
+ * Wrapper around dns_sdlzunregister().
+ */
+void
+dlz_odbc_clear(void) {
+ /*
+ * Write debugging message to log
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(2), "Unregistering DLZ odbc driver.");
+
+ /* unregister the driver. */
+ if (dlz_odbc != NULL) {
+ dns_sdlzunregister(&dlz_odbc);
+ }
+}
+
+#endif /* ifdef DLZ_ODBC */
diff --git a/contrib/dlz/drivers/dlz_postgres_driver.c b/contrib/dlz/drivers/dlz_postgres_driver.c
new file mode 100644
index 0000000..d960c3f
--- /dev/null
+++ b/contrib/dlz/drivers/dlz_postgres_driver.c
@@ -0,0 +1,1240 @@
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ * 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.
+ */
+
+/*
+ * Copyright (C) 1999-2001, 2016 Internet Systems Consortium, Inc. ("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 http://mozilla.org/MPL/2.0/.
+ */
+
+#ifdef DLZ_POSTGRES
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <isc/mem.h>
+#include <isc/platform.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/log.h>
+#include <dns/result.h>
+#include <dns/sdlz.h>
+
+#include <dlz/dlz_postgres_driver.h>
+#include <dlz/sdlz_helper.h>
+#include <named/globals.h>
+
+/* temporarily include time. */
+#include <libpq-fe.h>
+#include <time.h>
+
+static dns_sdlzimplementation_t *dlz_postgres = NULL;
+
+#define dbc_search_limit 30
+#define ALLNODES 1
+#define ALLOWXFR 2
+#define AUTHORITY 3
+#define FINDZONE 4
+#define LOOKUP 5
+
+/*
+ * Private methods
+ */
+
+/* ---------------
+ * Escaping arbitrary strings to get valid SQL strings/identifiers.
+ *
+ * Replaces "\\" with "\\\\" and "'" with "''".
+ * length is the length of the buffer pointed to by
+ * from. The buffer at to must be at least 2*length + 1 characters
+ * long. A terminating NUL character is written.
+ *
+ * NOTICE!!!
+ * This function was borrowed directly from PostgreSQL's libpq.
+ * The function was originally called PQescapeString and renamed
+ * to postgres_makesafe to avoid a naming collision.
+ * PQescapeString is a new function made available in Postgres 7.2.
+ * For some reason the function is not properly exported on Win32
+ * builds making the function unavailable on Windows. Also, since
+ * this function is new it would require building this driver with
+ * the libpq 7.2. By borrowing this function the Windows problem
+ * is solved, and the dependence on libpq 7.2 is removed. Libpq is
+ * still required of course, but an older version should work now too.
+ *
+ * The copyright statements from the original file containing this
+ * function are included below:
+ * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ * ---------------
+ */
+
+static size_t
+postgres_makesafe(char *to, const char *from, size_t length) {
+ const char *source = from;
+ char *target = to;
+ unsigned int remaining = length;
+
+ while (remaining > 0) {
+ switch (*source) {
+ case '\\':
+ *target = '\\';
+ target++;
+ *target = '\\';
+ /* target and remaining are updated below. */
+ break;
+
+ case '\'':
+ *target = '\'';
+ target++;
+ *target = '\'';
+ /* target and remaining are updated below. */
+ break;
+
+ default:
+ *target = *source;
+ /* target and remaining are updated below. */
+ }
+ source++;
+ target++;
+ remaining--;
+ }
+
+ /* Write the terminating NUL character. */
+ *target = '\0';
+
+ return (target - to);
+}
+
+/*%
+ * Properly cleans up a list of database instances.
+ * This function is only used when the driver is compiled for
+ * multithreaded operation.
+ */
+static void
+postgres_destroy_dblist(db_list_t *dblist) {
+ dbinstance_t *ndbi = NULL;
+ dbinstance_t *dbi = NULL;
+
+ /* get the first DBI in the list */
+ ndbi = ISC_LIST_HEAD(*dblist);
+
+ /* loop through the list */
+ while (ndbi != NULL) {
+ dbi = ndbi;
+ /* get the next DBI in the list */
+ ndbi = ISC_LIST_NEXT(dbi, link);
+ /* release DB connection */
+ if (dbi->dbconn != NULL) {
+ PQfinish((PGconn *)dbi->dbconn);
+ }
+ /* release all memory that comprised a DBI */
+ destroy_sqldbinstance(dbi);
+ }
+ /* release memory for the list structure */
+ isc_mem_put(named_g_mctx, dblist, sizeof(db_list_t));
+}
+
+/*%
+ * Loops through the list of DB instances, attempting to lock
+ * on the mutex. If successful, the DBI is reserved for use
+ * and the thread can perform queries against the database.
+ * If the lock fails, the next one in the list is tried.
+ * looping continues until a lock is obtained, or until
+ * the list has been searched dbc_search_limit times.
+ * This function is only used when the driver is compiled for
+ * multithreaded operation.
+ */
+
+static dbinstance_t *
+postgres_find_avail_conn(db_list_t *dblist) {
+ dbinstance_t *dbi = NULL;
+ dbinstance_t *head;
+ int count = 0;
+
+ /* get top of list */
+ head = dbi = ISC_LIST_HEAD(*dblist);
+
+ /* loop through list */
+ while (count < dbc_search_limit) {
+ /* try to lock on the mutex */
+ if (isc_mutex_trylock(&dbi->instance_lock) == ISC_R_SUCCESS) {
+ return (dbi); /* success, return the DBI for use. */
+ }
+ /* not successful, keep trying */
+ dbi = ISC_LIST_NEXT(dbi, link);
+
+ /* check to see if we have gone to the top of the list. */
+ if (dbi == NULL) {
+ count++;
+ dbi = head;
+ }
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_INFO,
+ "Postgres driver unable to find available connection "
+ "after searching %d times",
+ count);
+ return (NULL);
+}
+
+/*%
+ * Allocates memory for a new string, and then constructs the new
+ * string by "escaping" the input string. The new string is
+ * safe to be used in queries. This is necessary because we cannot
+ * be sure of what types of strings are passed to us, and we don't
+ * want special characters in the string causing problems.
+ */
+
+static char *
+postgres_escape_string(const char *instr) {
+ char *outstr;
+ unsigned int len;
+
+ if (instr == NULL) {
+ return (NULL);
+ }
+
+ len = strlen(instr);
+
+ outstr = isc_mem_allocate(named_g_mctx, (2 * len * sizeof(char)) + 1);
+
+ postgres_makesafe(outstr, instr, len);
+ /* PQescapeString(outstr, instr, len); */
+
+ return (outstr);
+}
+
+/*%
+ * This function is the real core of the driver. Zone, record
+ * and client strings are passed in (or NULL is passed if the
+ * string is not available). The type of query we want to run
+ * is indicated by the query flag, and the dbdata object is passed
+ * passed in to. dbdata really holds either:
+ * 1) a list of database instances (in multithreaded mode) OR
+ * 2) a single database instance (in single threaded mode)
+ * The function will construct the query and obtain an available
+ * database instance (DBI). It will then run the query and hopefully
+ * obtain a result set. Postgres is nice, in that once the result
+ * set is returned, we can make the db connection available for another
+ * thread to use, while this thread continues on. So, the DBI is made
+ * available ASAP by unlocking the instance_lock after we have cleaned
+ * it up properly.
+ */
+static isc_result_t
+postgres_get_resultset(const char *zone, const char *record, const char *client,
+ unsigned int query, void *dbdata, PGresult **rs) {
+ isc_result_t result;
+ dbinstance_t *dbi = NULL;
+ char *querystring = NULL;
+ unsigned int i = 0;
+ unsigned int j = 0;
+
+#if 0
+ /* temporarily get a unique thread # */
+ unsigned int dlz_thread_num = 1 +
+ (int) (1000.0 * rand() /
+ (RAND_MAX + 1.0));
+#endif /* if 0 */
+
+ REQUIRE(*rs == NULL);
+
+#if 0
+ /* temporary logging message */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "%d Getting DBI", dlz_thread_num);
+#endif /* if 0 */
+
+ /* get db instance / connection */
+
+ /* find an available DBI from the list */
+ dbi = postgres_find_avail_conn((db_list_t *)dbdata);
+
+#if 0
+ /* temporary logging message */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "%d Got DBI - checking query", dlz_thread_num);
+#endif /* if 0 */
+
+ /* if DBI is null, can't do anything else */
+ if (dbi == NULL) {
+ return (ISC_R_FAILURE);
+ }
+
+ /* what type of query are we going to run? */
+ switch (query) {
+ case ALLNODES:
+ /*
+ * if the query was not passed in from the config file
+ * then we can't run it. return not_implemented, so
+ * it's like the code for that operation was never
+ * built into the driver.... AHHH flexibility!!!
+ */
+ if (dbi->allnodes_q == NULL) {
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup;
+ }
+ break;
+ case ALLOWXFR:
+ /* same as comments as ALLNODES */
+ if (dbi->allowxfr_q == NULL) {
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup;
+ }
+ break;
+ case AUTHORITY:
+ /* same as comments as ALLNODES */
+ if (dbi->authority_q == NULL) {
+ result = ISC_R_NOTIMPLEMENTED;
+ goto cleanup;
+ }
+ break;
+ case FINDZONE:
+ /* this is required. It's the whole point of DLZ! */
+ if (dbi->findzone_q == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
+ "No query specified for findzone. "
+ "Findzone requires a query");
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+ break;
+ case LOOKUP:
+ /* this is required. It's also a major point of DLZ! */
+ if (dbi->lookup_q == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
+ "No query specified for lookup. "
+ "Lookup requires a query");
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+ break;
+ default:
+ /*
+ * this should never happen. If it does, the code is
+ * screwed up!
+ */
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "Incorrect query flag passed to "
+ "postgres_get_resultset");
+ result = ISC_R_UNEXPECTED;
+ goto cleanup;
+ }
+
+#if 0
+ /* temporary logging message */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "%d checked query", dlz_thread_num);
+#endif /* if 0 */
+
+ /*
+ * was a zone string passed? If so, make it safe for use in
+ * queries.
+ */
+ if (zone != NULL) {
+ dbi->zone = postgres_escape_string(zone);
+ if (dbi->zone == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ } else { /* no string passed, set the string pointer to NULL */
+ dbi->zone = NULL;
+ }
+
+#if 0
+ /* temporary logging message */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "%d did zone", dlz_thread_num);
+#endif /* if 0 */
+
+ /*
+ * was a record string passed? If so, make it safe for use in
+ * queries.
+ */
+ if (record != NULL) {
+ dbi->record = postgres_escape_string(record);
+ if (dbi->record == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ } else { /* no string passed, set the string pointer to NULL */
+ dbi->record = NULL;
+ }
+
+#if 0
+ /* temporary logging message */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "%d did record", dlz_thread_num);
+#endif /* if 0 */
+
+ /*
+ * was a client string passed? If so, make it safe for use in
+ * queries.
+ */
+ if (client != NULL) {
+ dbi->client = postgres_escape_string(client);
+ if (dbi->client == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ } else { /* no string passed, set the string pointer to NULL */
+ dbi->client = NULL;
+ }
+
+#if 0
+ /* temporary logging message */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "%d did client", dlz_thread_num);
+#endif /* if 0 */
+
+ /*
+ * what type of query are we going to run?
+ * this time we build the actual query to run.
+ */
+ switch (query) {
+ case ALLNODES:
+ querystring = build_querystring(named_g_mctx, dbi->allnodes_q);
+ break;
+ case ALLOWXFR:
+ querystring = build_querystring(named_g_mctx, dbi->allowxfr_q);
+ break;
+ case AUTHORITY:
+ querystring = build_querystring(named_g_mctx, dbi->authority_q);
+ break;
+ case FINDZONE:
+ querystring = build_querystring(named_g_mctx, dbi->findzone_q);
+ break;
+ case LOOKUP:
+ querystring = build_querystring(named_g_mctx, dbi->lookup_q);
+ break;
+ default:
+ /*
+ * this should never happen. If it does, the code is
+ * screwed up!
+ */
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "Incorrect query flag passed to "
+ "postgres_get_resultset");
+ result = ISC_R_UNEXPECTED;
+ goto cleanup;
+ }
+
+#if 0
+ /* temporary logging message */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "%d built query", dlz_thread_num);
+#endif /* if 0 */
+
+ /* if the querystring is null, Bummer, outta RAM. UPGRADE TIME!!! */
+ if (querystring == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+#if 0
+ /* temporary logging message */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "%d query is '%s'", dlz_thread_num, querystring);
+#endif /* if 0 */
+
+ /*
+ * output the full query string during debug so we can see
+ * what lame error the query has.
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(1), "\nQuery String: %s\n", querystring);
+
+ /* attempt query up to 3 times. */
+ for (j = 0; j < 3; j++) {
+#if 0
+ /* temporary logging message */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "%d executing query for %d time",
+ dlz_thread_num, j);
+#endif /* if 0 */
+ /* try to get result set */
+ *rs = PQexec((PGconn *)dbi->dbconn, querystring);
+ result = ISC_R_SUCCESS;
+ /*
+ * if result set is null, reset DB connection, max 3
+ * attempts.
+ */
+ for (i = 0; *rs == NULL && i < 3; i++) {
+#if 0
+ /* temporary logging message */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "%d resetting connection",
+ dlz_thread_num);
+#endif /* if 0 */
+ result = ISC_R_FAILURE;
+ PQreset((PGconn *)dbi->dbconn);
+ /* connection ok, break inner loop */
+ if (PQstatus((PGconn *)dbi->dbconn) == CONNECTION_OK) {
+ break;
+ }
+ }
+ /* result set ok, break outer loop */
+ if (PQresultStatus(*rs) == PGRES_TUPLES_OK) {
+#if 0
+ /* temporary logging message */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "%d rs ok", dlz_thread_num);
+#endif /* if 0 */
+ break;
+ } else {
+ /* we got a result set object, but it's not right. */
+#if 0
+ /* temporary logging message */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "%d clearing rs", dlz_thread_num);
+#endif /* if 0 */
+ PQclear(*rs); /* get rid of it */
+ /* in case this was the last attempt */
+ *rs = NULL;
+ result = ISC_R_FAILURE;
+ }
+ }
+
+cleanup:
+ /* it's always good to cleanup after yourself */
+
+#if 0
+ /* temporary logging message */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "%d cleaning up", dlz_thread_num);
+#endif /* if 0 */
+
+ /* free dbi->zone string */
+ if (dbi->zone != NULL) {
+ isc_mem_free(named_g_mctx, dbi->zone);
+ }
+
+ /* free dbi->record string */
+ if (dbi->record != NULL) {
+ isc_mem_free(named_g_mctx, dbi->record);
+ }
+
+ /* free dbi->client string */
+ if (dbi->client != NULL) {
+ isc_mem_free(named_g_mctx, dbi->client);
+ }
+
+#if 0
+ /* temporary logging message */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "%d unlocking mutex", dlz_thread_num);
+#endif /* if 0 */
+
+ /* release the lock so another thread can use this dbi */
+ isc_mutex_unlock(&dbi->instance_lock);
+
+ /* release query string */
+ if (querystring != NULL) {
+ isc_mem_free(named_g_mctx, querystring);
+ }
+
+#if 0
+ /* temporary logging message */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "%d returning", dlz_thread_num);
+#endif /* if 0 */
+
+ /* return result */
+ return (result);
+}
+
+/*%
+ * The processing of result sets for lookup and authority are
+ * exactly the same. So that functionality has been moved
+ * into this function to minimize code.
+ */
+
+static isc_result_t
+postgres_process_rs(dns_sdlzlookup_t *lookup, PGresult *rs) {
+ isc_result_t result;
+ unsigned int i;
+ unsigned int rows;
+ unsigned int fields;
+ unsigned int j;
+ unsigned int len;
+ char *tmpString;
+ char *endp;
+ int ttl;
+
+ rows = PQntuples(rs); /* how many rows in result set */
+ fields = PQnfields(rs); /* how many columns in result set */
+ for (i = 0; i < rows; i++) {
+ switch (fields) {
+ case 1:
+ /*
+ * one column in rs, it's the data field. use
+ * default type of A record, and default TTL
+ * of 86400
+ */
+ result = dns_sdlz_putrr(lookup, "a", 86400,
+ PQgetvalue(rs, i, 0));
+ break;
+ case 2:
+ /* two columns, data field, and data type.
+ * use default TTL of 86400.
+ */
+ result = dns_sdlz_putrr(lookup, PQgetvalue(rs, i, 0),
+ 86400, PQgetvalue(rs, i, 1));
+ break;
+ case 3:
+ /* three columns, all data no defaults.
+ * convert text to int, make sure it worked
+ * right.
+ */
+ ttl = strtol(PQgetvalue(rs, i, 0), &endp, 10);
+ if (*endp != '\0' || ttl < 0) {
+ isc_log_write(dns_lctx,
+ DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Postgres driver ttl must be "
+ "a positive number");
+ }
+ result = dns_sdlz_putrr(lookup, PQgetvalue(rs, i, 1),
+ ttl, PQgetvalue(rs, i, 2));
+ break;
+ default:
+ /*
+ * more than 3 fields, concatenate the last
+ * ones together. figure out how long to make
+ * string
+ */
+ for (j = 2, len = 0; j < fields; j++) {
+ len += strlen(PQgetvalue(rs, i, j)) + 1;
+ }
+ /*
+ * allocate string memory, allow for NULL to
+ * term string
+ */
+ tmpString = isc_mem_allocate(named_g_mctx, len + 1);
+ /* copy field to tmpString */
+ strcpy(tmpString, PQgetvalue(rs, i, 2));
+ /*
+ * concat the rest of fields together, space
+ * between each one.
+ */
+ for (j = 3; j < fields; j++) {
+ strcat(tmpString, " ");
+ strcat(tmpString, PQgetvalue(rs, i, j));
+ }
+ /* convert text to int, make sure it worked right */
+ ttl = strtol(PQgetvalue(rs, i, 0), &endp, 10);
+ if (*endp != '\0' || ttl < 0) {
+ isc_log_write(dns_lctx,
+ DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Postgres driver ttl must be "
+ "a positive number");
+ }
+ /* ok, now tell Bind about it. */
+ result = dns_sdlz_putrr(lookup, PQgetvalue(rs, i, 1),
+ ttl, tmpString);
+ /* done, get rid of this thing. */
+ isc_mem_free(named_g_mctx, tmpString);
+ }
+ /* I sure hope we were successful */
+ if (result != ISC_R_SUCCESS) {
+ /* nope, get rid of the Result set, and log a msg */
+ PQclear(rs);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "dns_sdlz_putrr returned error. "
+ "Error code was: %s",
+ isc_result_totext(result));
+ return (ISC_R_FAILURE);
+ }
+ }
+
+ /* free result set memory */
+ PQclear(rs);
+
+ /* if we did return results, we are successful */
+ if (rows > 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /* empty result set, no data found */
+ return (ISC_R_NOTFOUND);
+}
+
+/*
+ * SDLZ interface methods
+ */
+
+/*% determine if the zone is supported by (in) the database */
+
+static isc_result_t
+postgres_findzone(void *driverarg, void *dbdata, const char *name,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo) {
+ isc_result_t result;
+ PGresult *rs = NULL;
+ unsigned int rows;
+
+ UNUSED(driverarg);
+ UNUSED(methods);
+ UNUSED(clientinfo);
+
+ /* run the query and get the result set from the database. */
+ result = postgres_get_resultset(name, NULL, NULL, FINDZONE, dbdata,
+ &rs);
+ /* if we didn't get a result set, log an err msg. */
+ if (result != ISC_R_SUCCESS) {
+ if (rs != NULL) {
+ PQclear(rs);
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Postgres driver unable to return "
+ "result set for findzone query");
+ return (ISC_R_FAILURE);
+ }
+ /* count how many rows in result set */
+ rows = PQntuples(rs);
+ /* get rid of result set, we are done with it. */
+ PQclear(rs);
+
+ /* if we returned any rows, zone is supported. */
+ if (rows > 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /* no rows returned, zone is not supported. */
+ return (ISC_R_NOTFOUND);
+}
+
+/*% Determine if the client is allowed to perform a zone transfer */
+static isc_result_t
+postgres_allowzonexfr(void *driverarg, void *dbdata, const char *name,
+ const char *client) {
+ isc_result_t result;
+ PGresult *rs = NULL;
+ unsigned int rows;
+ UNUSED(driverarg);
+
+ /* first check if the zone is supported by the database. */
+ result = postgres_findzone(driverarg, dbdata, name, NULL, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ /*
+ * if we get to this point we know the zone is supported by
+ * the database the only questions now are is the zone
+ * transfer is allowed for this client and did the config file
+ * have an allow zone xfr query.
+ *
+ * Run our query, and get a result set from the database.
+ */
+ result = postgres_get_resultset(name, NULL, client, ALLOWXFR, dbdata,
+ &rs);
+ /* if we get "not implemented", send it along. */
+ if (result == ISC_R_NOTIMPLEMENTED) {
+ return (result);
+ }
+ /* if we didn't get a result set, log an err msg. */
+ if (result != ISC_R_SUCCESS) {
+ if (rs != NULL) {
+ PQclear(rs);
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Postgres driver unable to return "
+ "result set for allow xfr query");
+ return (ISC_R_FAILURE);
+ }
+ /* count how many rows in result set */
+ rows = PQntuples(rs);
+ /* get rid of result set, we are done with it. */
+ PQclear(rs);
+
+ /* if we returned any rows, zone xfr is allowed. */
+ if (rows > 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /* no rows returned, zone xfr not allowed */
+ return (ISC_R_NOPERM);
+}
+
+/*%
+ * If the client is allowed to perform a zone transfer, the next order of
+ * business is to get all the nodes in the zone, so bind can respond to the
+ * query.
+ */
+static isc_result_t
+postgres_allnodes(const char *zone, void *driverarg, void *dbdata,
+ dns_sdlzallnodes_t *allnodes) {
+ isc_result_t result;
+ PGresult *rs = NULL;
+ unsigned int i;
+ unsigned int rows;
+ unsigned int fields;
+ unsigned int j;
+ unsigned int len;
+ char *tmpString;
+ char *endp;
+ int ttl;
+
+ UNUSED(driverarg);
+
+ /* run the query and get the result set from the database. */
+ result = postgres_get_resultset(zone, NULL, NULL, ALLNODES, dbdata,
+ &rs);
+ /* if we get "not implemented", send it along */
+ if (result == ISC_R_NOTIMPLEMENTED) {
+ return (result);
+ }
+ /* if we didn't get a result set, log an err msg. */
+ if (result != ISC_R_SUCCESS) {
+ if (rs != NULL) {
+ PQclear(rs);
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Postgres driver unable to return "
+ "result set for all nodes query");
+ return (ISC_R_FAILURE);
+ }
+
+ rows = PQntuples(rs); /* how many rows in result set */
+ fields = PQnfields(rs); /* how many columns in result set */
+ for (i = 0; i < rows; i++) {
+ if (fields < 4) { /* gotta have at least 4 columns */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Postgres driver too few fields "
+ "returned by all nodes query");
+ }
+ /* convert text to int, make sure it worked right */
+ ttl = strtol(PQgetvalue(rs, i, 0), &endp, 10);
+ if (*endp != '\0' || ttl < 0) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Postgres driver ttl must be "
+ "a positive number");
+ }
+ if (fields == 4) {
+ /* tell Bind about it. */
+ result = dns_sdlz_putnamedrr(allnodes,
+ PQgetvalue(rs, i, 2),
+ PQgetvalue(rs, i, 1), ttl,
+ PQgetvalue(rs, i, 3));
+ } else {
+ /*
+ * more than 4 fields, concatonat the last
+ * ones together. figure out how long to make
+ * string
+ */
+ for (j = 3, len = 0; j < fields; j++) {
+ len += strlen(PQgetvalue(rs, i, j)) + 1;
+ }
+ /* allocate memory, allow for NULL to term string */
+ tmpString = isc_mem_allocate(named_g_mctx, len + 1);
+ /* copy this field to tmpString */
+ strcpy(tmpString, PQgetvalue(rs, i, 3));
+ /* concatenate the rest, with spaces between */
+ for (j = 4; j < fields; j++) {
+ strcat(tmpString, " ");
+ strcat(tmpString, PQgetvalue(rs, i, j));
+ }
+ /* tell Bind about it. */
+ result = dns_sdlz_putnamedrr(
+ allnodes, PQgetvalue(rs, i, 2),
+ PQgetvalue(rs, i, 1), ttl, tmpString);
+ isc_mem_free(named_g_mctx, tmpString);
+ }
+ /* if we weren't successful, log err msg */
+ if (result != ISC_R_SUCCESS) {
+ PQclear(rs);
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "dns_sdlz_putnamedrr returned error. "
+ "Error code was: %s",
+ isc_result_totext(result));
+ return (ISC_R_FAILURE);
+ }
+ }
+
+ /* free result set memory */
+ PQclear(rs);
+
+ /* if we did return results, we are successful */
+ if (rows > 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /* empty result set, no data found */
+ return (ISC_R_NOTFOUND);
+}
+
+/*%
+ * if the lookup function does not return SOA or NS records for the zone,
+ * use this function to get that information for Bind.
+ */
+
+static isc_result_t
+postgres_authority(const char *zone, void *driverarg, void *dbdata,
+ dns_sdlzlookup_t *lookup) {
+ isc_result_t result;
+ PGresult *rs = NULL;
+
+ UNUSED(driverarg);
+
+ /* run the query and get the result set from the database. */
+ result = postgres_get_resultset(zone, NULL, NULL, AUTHORITY, dbdata,
+ &rs);
+ /* if we get "not implemented", send it along */
+ if (result == ISC_R_NOTIMPLEMENTED) {
+ return (result);
+ }
+ /* if we didn't get a result set, log an err msg. */
+ if (result != ISC_R_SUCCESS) {
+ if (rs != NULL) {
+ PQclear(rs);
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Postgres driver unable to return "
+ "result set for authority query");
+ return (ISC_R_FAILURE);
+ }
+ /*
+ * lookup and authority result sets are processed in the same
+ * manner postgres_process_rs does the job for both
+ * functions.
+ */
+ return (postgres_process_rs(lookup, rs));
+}
+
+/*% if zone is supported, lookup up a (or multiple) record(s) in it */
+static isc_result_t
+postgres_lookup(const char *zone, const char *name, void *driverarg,
+ void *dbdata, dns_sdlzlookup_t *lookup,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo) {
+ isc_result_t result;
+ PGresult *rs = NULL;
+
+ UNUSED(driverarg);
+ UNUSED(methods);
+ UNUSED(clientinfo);
+
+ /* run the query and get the result set from the database. */
+ result = postgres_get_resultset(zone, name, NULL, LOOKUP, dbdata, &rs);
+ /* if we didn't get a result set, log an err msg. */
+ if (result != ISC_R_SUCCESS) {
+ if (rs != NULL) {
+ PQclear(rs);
+ }
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Postgres driver unable to "
+ "return result set for lookup query");
+ return (ISC_R_FAILURE);
+ }
+ /*
+ * lookup and authority result sets are processed in the same
+ * manner postgres_process_rs does the job for both functions.
+ */
+ return (postgres_process_rs(lookup, rs));
+}
+
+/*%
+ * create an instance of the driver. Remember, only 1 copy of the driver's
+ * code is ever loaded, the driver has to remember which context it's
+ * operating in. This is done via use of the dbdata argument which is
+ * passed into all query functions.
+ */
+static isc_result_t
+postgres_create(const char *dlzname, unsigned int argc, char *argv[],
+ void *driverarg, void **dbdata) {
+ isc_result_t result;
+ dbinstance_t *dbi = NULL;
+ unsigned int j;
+
+ /* if multi-threaded, we need a few extra variables. */
+ int dbcount;
+ db_list_t *dblist = NULL;
+ int i;
+ char *endp;
+
+ UNUSED(driverarg);
+ UNUSED(dlzname);
+
+ /* seed random # generator */
+ srand((unsigned)time(NULL));
+
+ /* if debugging, let user know we are multithreaded. */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(1),
+ "Postgres driver running multithreaded");
+
+ /* verify we have at least 5 arg's passed to the driver */
+ if (argc < 5) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Postgres driver requires at least "
+ "4 command line args.");
+ return (ISC_R_FAILURE);
+ }
+
+ /* no more than 8 arg's should be passed to the driver */
+ if (argc > 8) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Postgres driver cannot accept more than "
+ "7 command line args.");
+ return (ISC_R_FAILURE);
+ }
+
+ /* multithreaded build can have multiple DB connections */
+
+ /* check how many db connections we should create */
+ dbcount = strtol(argv[1], &endp, 10);
+ if (*endp != '\0' || dbcount < 0) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Postgres driver database connection count "
+ "must be positive.");
+ return (ISC_R_FAILURE);
+ }
+
+ /* allocate memory for database connection list */
+ dblist = isc_mem_get(named_g_mctx, sizeof(db_list_t));
+
+ /* initialize DB connection list */
+ ISC_LIST_INIT(*dblist);
+
+ /*
+ * create the appropriate number of database instances (DBI)
+ * append each new DBI to the end of the list
+ */
+ for (i = 0; i < dbcount; i++) {
+ /* how many queries were passed in from config file? */
+ switch (argc) {
+ case 5:
+ result = build_sqldbinstance(named_g_mctx, NULL, NULL,
+ NULL, argv[3], argv[4],
+ NULL, &dbi);
+ break;
+ case 6:
+ result = build_sqldbinstance(named_g_mctx, NULL, NULL,
+ argv[5], argv[3], argv[4],
+ NULL, &dbi);
+ break;
+ case 7:
+ result = build_sqldbinstance(named_g_mctx, argv[6],
+ NULL, argv[5], argv[3],
+ argv[4], NULL, &dbi);
+ break;
+ case 8:
+ result = build_sqldbinstance(named_g_mctx, argv[6],
+ argv[7], argv[5], argv[3],
+ argv[4], NULL, &dbi);
+ break;
+ default:
+ /* not really needed, should shut up compiler. */
+ result = ISC_R_FAILURE;
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
+ "Postgres driver created database "
+ "instance object.");
+ } else { /* unsuccessful?, log err msg and cleanup. */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Postgres driver could not create "
+ "database instance object.");
+ goto cleanup;
+ }
+
+ /* when multithreaded, build a list of DBI's */
+ ISC_LINK_INIT(dbi, link);
+ ISC_LIST_APPEND(*dblist, dbi, link);
+
+ /* create and set db connection */
+ dbi->dbconn = PQconnectdb(argv[2]);
+ /*
+ * if db connection cannot be created, log err msg and
+ * cleanup.
+ */
+ if (dbi->dbconn == NULL) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Postgres driver could not allocate "
+ "memory for database connection");
+ goto cleanup;
+ }
+
+ /* if we cannot connect the first time, try 3 more times. */
+ for (j = 0;
+ PQstatus((PGconn *)dbi->dbconn) != CONNECTION_OK && j < 3;
+ j++)
+ {
+ PQreset((PGconn *)dbi->dbconn);
+ }
+
+ /*
+ * if multi threaded, let user know which connection
+ * failed. user could be attempting to create 10 db
+ * connections and for some reason the db backend only
+ * allows 9
+ */
+ if (PQstatus((PGconn *)dbi->dbconn) != CONNECTION_OK) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Postgres driver failed to create "
+ "database connection number %u "
+ "after 4 attempts",
+ i + 1);
+ goto cleanup;
+ }
+
+ /* set DBI = null for next loop through. */
+ dbi = NULL;
+ } /* end for loop */
+
+ /* set dbdata to the list we created. */
+ *dbdata = dblist;
+
+ /* hey, we got through all of that ok, return success. */
+ return (ISC_R_SUCCESS);
+
+cleanup:
+
+ /*
+ * if multithreaded, we could fail because only 1 connection
+ * couldn't be made. We should cleanup the other successful
+ * connections properly.
+ */
+ postgres_destroy_dblist(dblist);
+
+ return (ISC_R_FAILURE);
+}
+
+/*%
+ * destroy an instance of the driver. Remember, only 1 copy of the driver's
+ * code is ever loaded, the driver has to remember which context it's
+ * operating in. This is done via use of the dbdata argument.
+ * so we really only need to clean it up since we are not using driverarg.
+ */
+static void
+postgres_destroy(void *driverarg, void *dbdata) {
+ UNUSED(driverarg);
+ /* cleanup the list of DBI's */
+ postgres_destroy_dblist((db_list_t *)dbdata);
+}
+
+/* pointers to all our runtime methods. */
+/* this is used during driver registration */
+/* i.e. in dlz_postgres_init below. */
+static dns_sdlzmethods_t dlz_postgres_methods = {
+ postgres_create,
+ postgres_destroy,
+ postgres_findzone,
+ postgres_lookup,
+ postgres_authority,
+ postgres_allnodes,
+ postgres_allowzonexfr,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+
+/*%
+ * Wrapper around dns_sdlzregister().
+ */
+isc_result_t
+dlz_postgres_init(void) {
+ isc_result_t result;
+
+ /*
+ * Write debugging message to log
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(2), "Registering DLZ postgres driver.");
+
+ /*
+ * Driver is always threadsafe. When multithreaded all
+ * functions use multithreaded code. When not multithreaded,
+ * all functions can only be entered once, but only 1 thread
+ * of operation is available in Bind. So everything is still
+ * threadsafe.
+ */
+ result = dns_sdlzregister("postgres", &dlz_postgres_methods, NULL,
+ DNS_SDLZFLAG_RELATIVEOWNER |
+ DNS_SDLZFLAG_RELATIVERDATA |
+ DNS_SDLZFLAG_THREADSAFE,
+ named_g_mctx, &dlz_postgres);
+ /* if we can't register the driver, there are big problems. */
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "dns_sdlzregister() failed: %s",
+ isc_result_totext(result));
+ result = ISC_R_UNEXPECTED;
+ }
+
+ return (result);
+}
+
+/*%
+ * Wrapper around dns_sdlzunregister().
+ */
+void
+dlz_postgres_clear(void) {
+ /*
+ * Write debugging message to log
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(2), "Unregistering DLZ postgres driver.");
+
+ /* unregister the driver. */
+ if (dlz_postgres != NULL) {
+ dns_sdlzunregister(&dlz_postgres);
+ }
+}
+
+#endif /* ifdef DLZ_POSTGRES */
diff --git a/contrib/dlz/drivers/dlz_stub_driver.c b/contrib/dlz/drivers/dlz_stub_driver.c
new file mode 100644
index 0000000..f2e6f5f
--- /dev/null
+++ b/contrib/dlz/drivers/dlz_stub_driver.c
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ * 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.
+ */
+
+/*
+ * Copyright (C) 1999-2001, 2016 Internet Systems Consortium, Inc. ("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 http://mozilla.org/MPL/2.0/.
+ */
+
+#ifdef DLZ_STUB
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+#include <dns/log.h>
+#include <dns/result.h>
+#include <dns/sdlz.h>
+
+#include <dlz/dlz_stub_driver.h>
+#include <named/globals.h>
+
+static dns_sdlzimplementation_t *dlz_stub = NULL;
+
+typedef struct config_data {
+ char *myzone;
+ char *myname;
+ char *myip;
+ isc_mem_t *mctx;
+} config_data_t;
+
+/*
+ * SDLZ methods
+ */
+
+static isc_result_t
+stub_dlz_allnodes(const char *zone, void *driverarg, void *dbdata,
+ dns_sdlzallnodes_t *allnodes) {
+ config_data_t *cd;
+ isc_result_t result;
+
+ UNUSED(zone);
+ UNUSED(driverarg);
+
+ cd = (config_data_t *)dbdata;
+
+ result = dns_sdlz_putnamedrr(allnodes, cd->myname, "soa", 86400,
+ "web root.localhost. "
+ "0 28800 7200 604800 86400");
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_FAILURE);
+ }
+ result = dns_sdlz_putnamedrr(allnodes, "ns", "ns", 86400, cd->myname);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_FAILURE);
+ }
+ result = dns_sdlz_putnamedrr(allnodes, cd->myname, "a", 1, cd->myip);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_FAILURE);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+stub_dlz_allowzonexfr(void *driverarg, void *dbdata, const char *name,
+ const char *client) {
+ config_data_t *cd;
+
+ UNUSED(driverarg);
+ UNUSED(client);
+
+ cd = (config_data_t *)dbdata;
+
+ if (strcmp(name, cd->myname) == 0) {
+ return (ISC_R_SUCCESS);
+ }
+ return (ISC_R_NOTFOUND);
+}
+
+static isc_result_t
+stub_dlz_authority(const char *zone, void *driverarg, void *dbdata,
+ dns_sdlzlookup_t *lookup) {
+ isc_result_t result;
+ config_data_t *cd;
+
+ UNUSED(driverarg);
+
+ cd = (config_data_t *)dbdata;
+
+ if (strcmp(zone, cd->myzone) == 0) {
+ result = dns_sdlz_putsoa(lookup, cd->myname, "root.localhost.",
+ 0);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_FAILURE);
+ }
+
+ result = dns_sdlz_putrr(lookup, "ns", 86400, cd->myname);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_FAILURE);
+ }
+
+ return (ISC_R_SUCCESS);
+ }
+ return (ISC_R_NOTFOUND);
+}
+
+static isc_result_t
+stub_dlz_findzonedb(void *driverarg, void *dbdata, const char *name,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo) {
+ config_data_t *cd;
+
+ UNUSED(driverarg);
+ UNUSED(methods);
+ UNUSED(clientinfo);
+
+ cd = (config_data_t *)dbdata;
+
+ /* Write info message to log */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(2), "dlz_stub findzone looking for '%s'",
+ name);
+
+ if (strcmp(cd->myzone, name) == 0) {
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_NOTFOUND);
+ }
+}
+
+static isc_result_t
+stub_dlz_lookup(const char *zone, const char *name, void *driverarg,
+ void *dbdata, dns_sdlzlookup_t *lookup,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo) {
+ isc_result_t result;
+ config_data_t *cd;
+
+ UNUSED(zone);
+ UNUSED(driverarg);
+ UNUSED(methods);
+ UNUSED(clientinfo);
+
+ cd = (config_data_t *)dbdata;
+
+ if (strcmp(name, cd->myname) == 0) {
+ result = dns_sdlz_putrr(lookup, "a", 1, cd->myip);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_FAILURE);
+ }
+
+ return (ISC_R_SUCCESS);
+ }
+ return (ISC_R_FAILURE);
+}
+
+static isc_result_t
+stub_dlz_create(const char *dlzname, unsigned int argc, char *argv[],
+ void *driverarg, void **dbdata) {
+ config_data_t *cd;
+
+ UNUSED(driverarg);
+
+ if (argc < 4) {
+ return (ISC_R_FAILURE);
+ }
+ /*
+ * Write info message to log
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_INFO,
+ "Loading '%s' using DLZ_stub driver. "
+ "Zone: %s, Name: %s IP: %s",
+ dlzname, argv[1], argv[2], argv[3]);
+
+ cd = isc_mem_get(named_g_mctx, sizeof(config_data_t));
+ if ((cd) == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ memset(cd, 0, sizeof(config_data_t));
+
+ cd->myzone = isc_mem_strdup(named_g_mctx, argv[1]);
+
+ cd->myname = isc_mem_strdup(named_g_mctx, argv[2]);
+
+ cd->myip = isc_mem_strdup(named_g_mctx, argv[3]);
+
+ isc_mem_attach(named_g_mctx, &cd->mctx);
+
+ *dbdata = cd;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+stub_dlz_destroy(void *driverarg, void *dbdata) {
+ config_data_t *cd;
+ isc_mem_t *mctx;
+
+ UNUSED(driverarg);
+
+ cd = (config_data_t *)dbdata;
+
+ /*
+ * Write debugging message to log
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(2), "Unloading DLZ_stub driver.");
+
+ isc_mem_free(named_g_mctx, cd->myzone);
+ isc_mem_free(named_g_mctx, cd->myname);
+ isc_mem_free(named_g_mctx, cd->myip);
+ mctx = cd->mctx;
+ isc_mem_putanddetach(&mctx, cd, sizeof(config_data_t));
+}
+
+static dns_sdlzmethods_t dlz_stub_methods = {
+ stub_dlz_create,
+ stub_dlz_destroy,
+ stub_dlz_findzonedb,
+ stub_dlz_lookup,
+ stub_dlz_authority,
+ stub_dlz_allnodes,
+ stub_dlz_allowzonexfr,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+
+/*%
+ * Wrapper around dns_sdlzregister().
+ */
+isc_result_t
+dlz_stub_init(void) {
+ isc_result_t result;
+
+ /*
+ * Write debugging message to log
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(2), "Registering DLZ_stub driver.");
+
+ result = dns_sdlzregister("dlz_stub", &dlz_stub_methods, NULL,
+ DNS_SDLZFLAG_RELATIVEOWNER |
+ DNS_SDLZFLAG_RELATIVERDATA,
+ named_g_mctx, &dlz_stub);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "dns_sdlzregister() failed: %s",
+ isc_result_totext(result));
+ result = ISC_R_UNEXPECTED;
+ }
+
+ return (result);
+}
+
+/*
+ * Wrapper around dns_sdlzunregister().
+ */
+void
+dlz_stub_clear(void) {
+ /*
+ * Write debugging message to log
+ */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
+ ISC_LOG_DEBUG(2), "Unregistering DLZ_stub driver.");
+
+ if (dlz_stub != NULL) {
+ dns_sdlzunregister(&dlz_stub);
+ }
+}
+
+#endif /* ifdef DLZ_STUB */
diff --git a/contrib/dlz/drivers/include/.clang-format b/contrib/dlz/drivers/include/.clang-format
new file mode 120000
index 0000000..e919bba
--- /dev/null
+++ b/contrib/dlz/drivers/include/.clang-format
@@ -0,0 +1 @@
+../../../../.clang-format.headers \ No newline at end of file
diff --git a/contrib/dlz/drivers/include/dlz/dlz_bdb_driver.h b/contrib/dlz/drivers/include/dlz/dlz_bdb_driver.h
new file mode 100644
index 0000000..afe903d
--- /dev/null
+++ b/contrib/dlz/drivers/include/dlz/dlz_bdb_driver.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ * 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.
+ */
+
+#ifndef DLZ_BDB_DRIVER_H
+#define DLZ_BDB_DRIVER_H
+
+isc_result_t
+dlz_bdb_init(void);
+
+void
+dlz_bdb_clear(void);
+
+#endif /* ifndef DLZ_BDB_DRIVER_H */
diff --git a/contrib/dlz/drivers/include/dlz/dlz_bdbhpt_driver.h b/contrib/dlz/drivers/include/dlz/dlz_bdbhpt_driver.h
new file mode 100644
index 0000000..c4d151f
--- /dev/null
+++ b/contrib/dlz/drivers/include/dlz/dlz_bdbhpt_driver.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ * 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.
+ */
+
+#ifndef DLZ_BDBHPT_DRIVER_H
+#define DLZ_BDBHPT_DRIVER_H
+
+isc_result_t
+dlz_bdbhpt_init(void);
+
+void
+dlz_bdbhpt_clear(void);
+
+#endif /* ifndef DLZ_BDBHPT_DRIVER_H */
diff --git a/contrib/dlz/drivers/include/dlz/dlz_dlopen_driver.h b/contrib/dlz/drivers/include/dlz/dlz_dlopen_driver.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/contrib/dlz/drivers/include/dlz/dlz_dlopen_driver.h
diff --git a/contrib/dlz/drivers/include/dlz/dlz_drivers.h b/contrib/dlz/drivers/include/dlz/dlz_drivers.h
new file mode 100644
index 0000000..f3f66c5
--- /dev/null
+++ b/contrib/dlz/drivers/include/dlz/dlz_drivers.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2005 Internet Systems Consortium, Inc. ("ISC")
+ * 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.
+ */
+
+#ifndef DLZ_DRIVERS_H
+#define DLZ_DRIVERS_H 1
+
+/*! \file */
+
+isc_result_t
+dlz_drivers_init(void);
+
+void
+dlz_drivers_clear(void);
+
+#endif /* DLZ_DRIVERS_H */
diff --git a/contrib/dlz/drivers/include/dlz/dlz_filesystem_driver.h b/contrib/dlz/drivers/include/dlz/dlz_filesystem_driver.h
new file mode 100644
index 0000000..dc83582
--- /dev/null
+++ b/contrib/dlz/drivers/include/dlz/dlz_filesystem_driver.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ * 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.
+ */
+
+#ifndef DLZ_FILESYSTEM_DRIVER_H
+#define DLZ_FILESYSTEM_DRIVER_H
+
+isc_result_t
+dlz_fs_init(void);
+
+void
+dlz_fs_clear(void);
+
+#endif /* ifndef DLZ_FILESYSTEM_DRIVER_H */
diff --git a/contrib/dlz/drivers/include/dlz/dlz_ldap_driver.h b/contrib/dlz/drivers/include/dlz/dlz_ldap_driver.h
new file mode 100644
index 0000000..ec01d97
--- /dev/null
+++ b/contrib/dlz/drivers/include/dlz/dlz_ldap_driver.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ * 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.
+ */
+
+#ifndef DLZ_LDAP_DRIVER_H
+#define DLZ_LDAP_DRIVER_H
+
+isc_result_t
+dlz_ldap_init(void);
+
+void
+dlz_ldap_clear(void);
+
+#endif /* ifndef DLZ_LDAP_DRIVER_H */
diff --git a/contrib/dlz/drivers/include/dlz/dlz_mysql_driver.h b/contrib/dlz/drivers/include/dlz/dlz_mysql_driver.h
new file mode 100644
index 0000000..b419482
--- /dev/null
+++ b/contrib/dlz/drivers/include/dlz/dlz_mysql_driver.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ * 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.
+ */
+
+#ifndef DLZ_MYSQL_DRIVER_H
+#define DLZ_MYSQL_DRIVER_H
+
+isc_result_t
+dlz_mysql_init(void);
+
+void
+dlz_mysql_clear(void);
+
+#endif /* ifndef DLZ_MYSQL_DRIVER_H */
diff --git a/contrib/dlz/drivers/include/dlz/dlz_odbc_driver.h b/contrib/dlz/drivers/include/dlz/dlz_odbc_driver.h
new file mode 100644
index 0000000..023c746
--- /dev/null
+++ b/contrib/dlz/drivers/include/dlz/dlz_odbc_driver.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ * 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.
+ */
+
+#ifndef DLZ_ODBC_DRIVER_H
+#define DLZ_ODBC_DRIVER_H
+
+isc_result_t
+dlz_odbc_init(void);
+
+void
+dlz_odbc_clear(void);
+
+#endif /* ifndef DLZ_ODBC_DRIVER_H */
diff --git a/contrib/dlz/drivers/include/dlz/dlz_postgres_driver.h b/contrib/dlz/drivers/include/dlz/dlz_postgres_driver.h
new file mode 100644
index 0000000..f0692f1
--- /dev/null
+++ b/contrib/dlz/drivers/include/dlz/dlz_postgres_driver.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ * 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.
+ */
+
+#ifndef DLZ_POSTGRES_DRIVER_H
+#define DLZ_POSTGRES_DRIVER_H
+
+isc_result_t
+dlz_postgres_init(void);
+
+void
+dlz_postgres_clear(void);
+
+#endif /* ifndef DLZ_POSTGRES_DRIVER_H */
diff --git a/contrib/dlz/drivers/include/dlz/dlz_stub_driver.h b/contrib/dlz/drivers/include/dlz/dlz_stub_driver.h
new file mode 100644
index 0000000..bf9b90a
--- /dev/null
+++ b/contrib/dlz/drivers/include/dlz/dlz_stub_driver.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ * 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.
+ */
+
+#ifndef DLZ_STUB_DRIVER_H
+#define DLZ_STUB_DRIVER_H
+
+isc_result_t
+dlz_stub_init(void);
+
+void
+dlz_stub_clear(void);
+
+#endif /* ifndef DLZ_STUB_DRIVER_H */
diff --git a/contrib/dlz/drivers/include/dlz/sdlz_helper.h b/contrib/dlz/drivers/include/dlz/sdlz_helper.h
new file mode 100644
index 0000000..1ddf3b9
--- /dev/null
+++ b/contrib/dlz/drivers/include/dlz/sdlz_helper.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ * 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.
+ */
+
+#ifndef SDLZHELPER_H
+#define SDLZHELPER_H
+
+#include <stdbool.h>
+
+/*
+ * Types
+ */
+#define SDLZH_REQUIRE_CLIENT 0x01
+#define SDLZH_REQUIRE_QUERY 0x02
+#define SDLZH_REQUIRE_RECORD 0x04
+#define SDLZH_REQUIRE_ZONE 0x08
+
+typedef struct query_segment query_segment_t;
+typedef ISC_LIST(query_segment_t) query_list_t;
+typedef struct dbinstance dbinstance_t;
+typedef ISC_LIST(dbinstance_t) db_list_t;
+typedef struct driverinstance driverinstance_t;
+
+/*%
+ * a query segment is all the text between our special tokens
+ * special tokens are %zone%, %record%, %client%
+ */
+struct query_segment {
+ void *sql;
+ unsigned int strlen;
+ bool direct;
+ ISC_LINK(query_segment_t) link;
+};
+
+/*%
+ * a database instance contains everything we need for running
+ * a query against the database. Using it each separate thread
+ * can dynamically construct a query and execute it against the
+ * database. The "instance_lock" and locking code in the driver's
+ * make sure no two threads try to use the same DBI at a time.
+ */
+struct dbinstance {
+ void *dbconn;
+ query_list_t *allnodes_q;
+ query_list_t *allowxfr_q;
+ query_list_t *authority_q;
+ query_list_t *findzone_q;
+ query_list_t *lookup_q;
+ query_list_t *countzone_q;
+ char *query_buf;
+ char *zone;
+ char *record;
+ char *client;
+ isc_mem_t *mctx;
+ isc_mutex_t instance_lock;
+ ISC_LINK(dbinstance_t) link;
+};
+
+/*
+ * Method declarations
+ */
+
+/* see the code in sdlz_helper.c for more information on these methods */
+
+char *
+sdlzh_build_querystring(isc_mem_t *mctx, query_list_t *querylist);
+
+isc_result_t
+sdlzh_build_sqldbinstance(isc_mem_t *mctx, const char *allnodes_str,
+ const char *allowxfr_str, const char *authority_str,
+ const char *findzone_str, const char *lookup_str,
+ const char *countzone_str, dbinstance_t **dbi);
+
+void
+sdlzh_destroy_sqldbinstance(dbinstance_t *dbi);
+
+char *
+sdlzh_get_parameter_value(isc_mem_t *mctx, const char *input, const char *key);
+
+/* Compatibility with existing DLZ drivers */
+
+#define build_querystring sdlzh_build_querystring
+#define build_sqldbinstance sdlzh_build_sqldbinstance
+#define destroy_sqldbinstance sdlzh_destroy_sqldbinstance
+
+#define getParameterValue(x, y) \
+ sdlzh_get_parameter_value(named_g_mctx, (x), (y))
+
+#endif /* SDLZHELPER_H */
diff --git a/contrib/dlz/drivers/rules.in b/contrib/dlz/drivers/rules.in
new file mode 100644
index 0000000..560c200
--- /dev/null
+++ b/contrib/dlz/drivers/rules.in
@@ -0,0 +1,50 @@
+# Copyright (C) 2005 Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# Permission to use, copy, modify, and 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 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.
+
+dlz_drivers.@O@: ${DLZ_DRIVER_DIR}/dlz_drivers.c ${DLZ_DRIVER_DIR}/include/dlz/dlz_drivers.h
+ ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c ${DLZ_DRIVER_DIR}/dlz_drivers.c
+
+sdlz_helper.@O@: ${DLZ_DRIVER_DIR}/sdlz_helper.c ${DLZ_DRIVER_DIR}/include/dlz/sdlz_helper.h
+ ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c ${DLZ_DRIVER_DIR}/sdlz_helper.c
+
+
+dlz_bdb_driver.@O@: ${DLZ_DRIVER_DIR}/dlz_bdb_driver.c ${DLZ_DRIVER_DIR}/include/dlz/dlz_bdb_driver.h
+ ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c ${DLZ_DRIVER_DIR}/dlz_bdb_driver.c
+
+dlz_bdbhpt_driver.@O@: ${DLZ_DRIVER_DIR}/dlz_bdbhpt_driver.c ${DLZ_DRIVER_DIR}/include/dlz/dlz_bdbhpt_driver.h
+ ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c ${DLZ_DRIVER_DIR}/dlz_bdbhpt_driver.c
+
+dlz_filesystem_driver.@O@: ${DLZ_DRIVER_DIR}/dlz_filesystem_driver.c ${DLZ_DRIVER_DIR}/include/dlz/dlz_filesystem_driver.h
+ ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c ${DLZ_DRIVER_DIR}/dlz_filesystem_driver.c
+
+dlz_ldap_driver.@O@: ${DLZ_DRIVER_DIR}/dlz_ldap_driver.c ${DLZ_DRIVER_DIR}/include/dlz/dlz_ldap_driver.h
+ ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c ${DLZ_DRIVER_DIR}/dlz_ldap_driver.c
+
+dlz_mysql_driver.@O@: ${DLZ_DRIVER_DIR}/dlz_mysql_driver.c ${DLZ_DRIVER_DIR}/include/dlz/dlz_mysql_driver.h
+ ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c ${DLZ_DRIVER_DIR}/dlz_mysql_driver.c
+
+dlz_odbc_driver.@O@: ${DLZ_DRIVER_DIR}/dlz_odbc_driver.c ${DLZ_DRIVER_DIR}/include/dlz/dlz_odbc_driver.h
+ ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c ${DLZ_DRIVER_DIR}/dlz_odbc_driver.c
+
+dlz_postgres_driver.@O@: ${DLZ_DRIVER_DIR}/dlz_postgres_driver.c ${DLZ_DRIVER_DIR}/include/dlz/dlz_postgres_driver.h
+ ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c ${DLZ_DRIVER_DIR}/dlz_postgres_driver.c
+
+dlz_dlopen_driver.@O@: ${DLZ_DRIVER_DIR}/dlz_dlopen_driver.c ${DLZ_DRIVER_DIR}/include/dlz/dlz_dlopen_driver.h
+ ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c ${DLZ_DRIVER_DIR}/dlz_dlopen_driver.c
+
+dlz_stub_driver.@O@: ${DLZ_DRIVER_DIR}/dlz_stub_driver.c ${DLZ_DRIVER_DIR}/include/dlz/dlz_stub_driver.h
+ ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -c ${DLZ_DRIVER_DIR}/dlz_stub_driver.c
+
diff --git a/contrib/dlz/drivers/sdlz_helper.c b/contrib/dlz/drivers/sdlz_helper.c
new file mode 100644
index 0000000..96c2be7
--- /dev/null
+++ b/contrib/dlz/drivers/sdlz_helper.c
@@ -0,0 +1,468 @@
+/*
+ * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
+ * 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.
+ */
+
+/*
+ * Copyright (C) 1999-2001, 2016 Internet Systems Consortium, Inc. ("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 http://mozilla.org/MPL/2.0/.
+ */
+
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/log.h>
+#include <dns/result.h>
+
+#include <dlz/sdlz_helper.h>
+
+/*
+ * sdlz helper methods
+ */
+
+/*%
+ * properly destroys a querylist by de-allocating the
+ * memory for each query segment, and then the list itself
+ */
+
+static void
+destroy_querylist(isc_mem_t *mctx, query_list_t **querylist) {
+ query_segment_t *tseg = NULL;
+ query_segment_t *nseg = NULL;
+
+ REQUIRE(mctx != NULL);
+
+ /* if query list is null, nothing to do */
+ if (*querylist == NULL) {
+ return;
+ }
+
+ /* start at the top of the list */
+ nseg = ISC_LIST_HEAD(**querylist);
+ while (nseg != NULL) { /* loop, until end of list */
+ tseg = nseg;
+ /*
+ * free the query segment's text string but only if it
+ * was really a query segment, and not a pointer to
+ * %zone%, or %record%, or %client%
+ */
+ if (tseg->sql != NULL && tseg->direct) {
+ isc_mem_free(mctx, tseg->sql);
+ }
+ /* get the next query segment, before we destroy this one. */
+ nseg = ISC_LIST_NEXT(nseg, link);
+ /* deallocate this query segment. */
+ isc_mem_put(mctx, tseg, sizeof(query_segment_t));
+ }
+ /* deallocate the query segment list */
+ isc_mem_put(mctx, *querylist, sizeof(query_list_t));
+}
+
+/*% constructs a query list by parsing a string into query segments */
+static isc_result_t
+build_querylist(isc_mem_t *mctx, const char *query_str, char **zone,
+ char **record, char **client, query_list_t **querylist,
+ unsigned int flags) {
+ isc_result_t result;
+ bool foundzone = false;
+ bool foundrecord = false;
+ bool foundclient = false;
+ char *free_me = NULL;
+ char *temp_str = NULL;
+ query_list_t *tql;
+ query_segment_t *tseg = NULL;
+ char *last = NULL;
+
+ REQUIRE(querylist != NULL && *querylist == NULL);
+ REQUIRE(mctx != NULL);
+
+ /* if query string is null, or zero length */
+ if (query_str == NULL || strlen(query_str) < 1) {
+ if ((flags & SDLZH_REQUIRE_QUERY) == 0) {
+ /* we don't need it were ok. */
+ return (ISC_R_SUCCESS);
+ } else {
+ /* we did need it, PROBLEM!!! */
+ return (ISC_R_FAILURE);
+ }
+ }
+
+ /* allocate memory for query list */
+ tql = isc_mem_get(mctx, sizeof(query_list_t));
+
+ /* initialize the query segment list */
+ ISC_LIST_INIT(*tql);
+
+ /* make a copy of query_str so we can chop it up */
+ free_me = temp_str = isc_mem_strdup(mctx, query_str);
+ /* couldn't make a copy, problem!! */
+ if (temp_str == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ /* loop through the string and chop it up */
+ for (;;) {
+ /*
+ * Split string into tokens at '$'.
+ */
+ const char *sql = strtok_r(temp_str, "$", &last);
+ if (sql == NULL) {
+ break;
+ }
+ temp_str = NULL;
+
+ /* allocate memory for tseg */
+ tseg = isc_mem_get(mctx, sizeof(query_segment_t));
+ tseg->sql = NULL;
+ tseg->direct = false;
+ /* initialize the query segment link */
+ ISC_LINK_INIT(tseg, link);
+ /* append the query segment to the list */
+ ISC_LIST_APPEND(*tql, tseg, link);
+
+ tseg->sql = isc_mem_strdup(mctx, sql);
+ /* tseg->sql points directly to a string. */
+ tseg->direct = true;
+ tseg->strlen = strlen(tseg->sql);
+
+ /* check if we encountered "$zone$" token */
+ if (strcasecmp(tseg->sql, "zone") == 0) {
+ /*
+ * we don't really need, or want the "zone"
+ * text, so get rid of it.
+ */
+ isc_mem_free(mctx, tseg->sql);
+ /* set tseg->sql to in-direct zone string */
+ tseg->sql = zone;
+ tseg->strlen = 0;
+ /* tseg->sql points in-directly to a string */
+ tseg->direct = false;
+ foundzone = true;
+ /* check if we encountered "$record$" token */
+ } else if (strcasecmp(tseg->sql, "record") == 0) {
+ /*
+ * we don't really need, or want the "record"
+ * text, so get rid of it.
+ */
+ isc_mem_free(mctx, tseg->sql);
+ /* set tseg->sql to in-direct record string */
+ tseg->sql = record;
+ tseg->strlen = 0;
+ /* tseg->sql points in-directly points to a string */
+ tseg->direct = false;
+ foundrecord = true;
+ /* check if we encountered "$client$" token */
+ } else if (strcasecmp(tseg->sql, "client") == 0) {
+ /*
+ * we don't really need, or want the "client"
+ * text, so get rid of it.
+ */
+ isc_mem_free(mctx, tseg->sql);
+ /* set tseg->sql to in-direct record string */
+ tseg->sql = client;
+ tseg->strlen = 0;
+ /* tseg->sql points in-directly points to a string */
+ tseg->direct = false;
+ foundclient = true;
+ }
+ }
+
+ /* we don't need temp_str any more */
+ isc_mem_free(mctx, free_me);
+ /*
+ * add checks later to verify zone and record are found if
+ * necessary.
+ */
+
+ /* if this query requires %client%, make sure we found it */
+ if (((flags & SDLZH_REQUIRE_CLIENT) != 0) && (!foundclient)) {
+ /* Write error message to log */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Required token $client$ not found.");
+ result = ISC_R_FAILURE;
+ goto flag_fail;
+ }
+
+ /* if this query requires %record%, make sure we found it */
+ if (((flags & SDLZH_REQUIRE_RECORD) != 0) && (!foundrecord)) {
+ /* Write error message to log */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Required token $record$ not found.");
+ result = ISC_R_FAILURE;
+ goto flag_fail;
+ }
+
+ /* if this query requires %zone%, make sure we found it */
+ if (((flags & SDLZH_REQUIRE_ZONE) != 0) && (!foundzone)) {
+ /* Write error message to log */
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Required token $zone$ not found.");
+ result = ISC_R_FAILURE;
+ goto flag_fail;
+ }
+
+ /* pass back the query list */
+ *querylist = (query_list_t *)tql;
+
+ /* return success */
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ /* get rid of free_me */
+ if (free_me != NULL) {
+ isc_mem_free(mctx, free_me);
+ }
+
+flag_fail:
+ /* get rid of what was build of the query list */
+ destroy_querylist(mctx, &tql);
+ return (result);
+}
+
+/*%
+ * build a query string from query segments, and dynamic segments
+ * dynamic segments replace where the tokens %zone%, %record%, %client%
+ * used to be in our queries from named.conf
+ */
+char *
+sdlzh_build_querystring(isc_mem_t *mctx, query_list_t *querylist) {
+ query_segment_t *tseg = NULL;
+ unsigned int length = 0;
+ char *qs = NULL;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(querylist != NULL);
+
+ /* start at the top of the list */
+ tseg = ISC_LIST_HEAD(*querylist);
+ while (tseg != NULL) {
+ /*
+ * if this is a query segment, use the
+ * precalculated string length
+ */
+ if (tseg->direct) {
+ length += tseg->strlen;
+ } else { /* calculate string length for dynamic segments. */
+ length += strlen(*(char **)tseg->sql);
+ }
+ /* get the next segment */
+ tseg = ISC_LIST_NEXT(tseg, link);
+ }
+
+ /* allocate memory for the string */
+ qs = isc_mem_allocate(mctx, length + 1);
+
+ *qs = 0;
+ /* start at the top of the list again */
+ tseg = ISC_LIST_HEAD(*querylist);
+ while (tseg != NULL) {
+ if (tseg->direct) {
+ /* query segments */
+ strcat(qs, tseg->sql);
+ } else {
+ /* dynamic segments */
+ strcat(qs, *(char **)tseg->sql);
+ }
+ /* get the next segment */
+ tseg = ISC_LIST_NEXT(tseg, link);
+ }
+
+ return (qs);
+}
+
+/*% constructs a sql dbinstance (DBI) */
+isc_result_t
+sdlzh_build_sqldbinstance(isc_mem_t *mctx, const char *allnodes_str,
+ const char *allowxfr_str, const char *authority_str,
+ const char *findzone_str, const char *lookup_str,
+ const char *countzone_str, dbinstance_t **dbi) {
+ isc_result_t result;
+ dbinstance_t *db = NULL;
+
+ REQUIRE(dbi != NULL && *dbi == NULL);
+ REQUIRE(mctx != NULL);
+
+ /* allocate and zero memory for driver structure */
+ db = isc_mem_get(mctx, sizeof(dbinstance_t));
+ memset(db, 0, sizeof(dbinstance_t));
+ db->dbconn = NULL;
+ db->client = NULL;
+ db->record = NULL;
+ db->zone = NULL;
+ db->mctx = NULL;
+ db->query_buf = NULL;
+ db->allnodes_q = NULL;
+ db->allowxfr_q = NULL;
+ db->authority_q = NULL;
+ db->findzone_q = NULL;
+ db->countzone_q = NULL;
+ db->lookup_q = NULL;
+
+ /* attach to the memory context */
+ isc_mem_attach(mctx, &db->mctx);
+
+ /* initialize the reference count mutex */
+ isc_mutex_init(&db->instance_lock);
+
+ /* build the all nodes query list */
+ result = build_querylist(mctx, allnodes_str, &db->zone, &db->record,
+ &db->client, &db->allnodes_q,
+ SDLZH_REQUIRE_ZONE);
+ /* if unsuccessful, log err msg and cleanup */
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Could not build all nodes query list");
+ goto cleanup;
+ }
+
+ /* build the allow zone transfer query list */
+ result = build_querylist(mctx, allowxfr_str, &db->zone, &db->record,
+ &db->client, &db->allowxfr_q,
+ SDLZH_REQUIRE_ZONE | SDLZH_REQUIRE_CLIENT);
+ /* if unsuccessful, log err msg and cleanup */
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Could not build allow xfr query list");
+ goto cleanup;
+ }
+
+ /* build the authority query, query list */
+ result = build_querylist(mctx, authority_str, &db->zone, &db->record,
+ &db->client, &db->authority_q,
+ SDLZH_REQUIRE_ZONE);
+ /* if unsuccessful, log err msg and cleanup */
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Could not build authority query list");
+ goto cleanup;
+ }
+
+ /* build findzone query, query list */
+ result = build_querylist(mctx, findzone_str, &db->zone, &db->record,
+ &db->client, &db->findzone_q,
+ SDLZH_REQUIRE_ZONE);
+ /* if unsuccessful, log err msg and cleanup */
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Could not build find zone query list");
+ goto cleanup;
+ }
+
+ /* build countzone query, query list */
+ result = build_querylist(mctx, countzone_str, &db->zone, &db->record,
+ &db->client, &db->countzone_q,
+ SDLZH_REQUIRE_ZONE);
+ /* if unsuccessful, log err msg and cleanup */
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Could not build count zone query list");
+ goto cleanup;
+ }
+
+ /* build lookup query, query list */
+ result = build_querylist(mctx, lookup_str, &db->zone, &db->record,
+ &db->client, &db->lookup_q,
+ SDLZH_REQUIRE_RECORD);
+ /* if unsuccessful, log err msg and cleanup */
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
+ DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
+ "Could not build lookup query list");
+ goto cleanup;
+ }
+
+ /* pass back the db instance */
+ *dbi = (dbinstance_t *)db;
+
+ /* return success */
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ /* destroy whatever was build of the db instance */
+ destroy_sqldbinstance(db);
+ /* return failure */
+ return (ISC_R_FAILURE);
+}
+
+void
+sdlzh_destroy_sqldbinstance(dbinstance_t *dbi) {
+ isc_mem_t *mctx;
+
+ /* save mctx for later */
+ mctx = dbi->mctx;
+
+ /* destroy any query lists we created */
+ destroy_querylist(mctx, &dbi->allnodes_q);
+ destroy_querylist(mctx, &dbi->allowxfr_q);
+ destroy_querylist(mctx, &dbi->authority_q);
+ destroy_querylist(mctx, &dbi->findzone_q);
+ destroy_querylist(mctx, &dbi->countzone_q);
+ destroy_querylist(mctx, &dbi->lookup_q);
+
+ /* get rid of the mutex */
+ (void)isc_mutex_destroy(&dbi->instance_lock);
+
+ /* return, and detach the memory */
+ isc_mem_putanddetach(&mctx, dbi, sizeof(dbinstance_t));
+}
+
+char *
+sdlzh_get_parameter_value(isc_mem_t *mctx, const char *input, const char *key) {
+ int keylen;
+ char *keystart;
+ char value[255];
+ int i;
+
+ if (key == NULL || input == NULL || strlen(input) < 1) {
+ return (NULL);
+ }
+
+ keylen = strlen(key);
+
+ if (keylen < 1) {
+ return (NULL);
+ }
+
+ keystart = strstr(input, key);
+
+ if (keystart == NULL) {
+ return (NULL);
+ }
+
+ REQUIRE(mctx != NULL);
+
+ for (i = 0; i < 255; i++) {
+ value[i] = keystart[keylen + i];
+ if (value[i] == ' ' || value[i] == '\0') {
+ value[i] = '\0';
+ break;
+ }
+ }
+
+ return (isc_mem_strdup(mctx, value));
+}