diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 07:24:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 07:24:22 +0000 |
commit | 45d6379135504814ab723b57f0eb8be23393a51d (patch) | |
tree | d4f2ec4acca824a8446387a758b0ce4238a4dffa /contrib/dlz/drivers | |
parent | Initial commit. (diff) | |
download | bind9-45d6379135504814ab723b57f0eb8be23393a51d.tar.xz bind9-45d6379135504814ab723b57f0eb8be23393a51d.zip |
Adding upstream version 1:9.16.44.upstream/1%9.16.44upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
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)); +} |