diff options
Diffstat (limited to '')
-rw-r--r-- | contrib/dlz/modules/sqlite3/Makefile | 46 | ||||
-rw-r--r-- | contrib/dlz/modules/sqlite3/dlz_sqlite3_dynamic.c | 1056 | ||||
-rw-r--r-- | contrib/dlz/modules/sqlite3/testing/README | 10 | ||||
-rw-r--r-- | contrib/dlz/modules/sqlite3/testing/dlz.data | 18 | ||||
-rw-r--r-- | contrib/dlz/modules/sqlite3/testing/dlz.schema | 28 | ||||
-rw-r--r-- | contrib/dlz/modules/sqlite3/testing/named.conf | 45 |
6 files changed, 1203 insertions, 0 deletions
diff --git a/contrib/dlz/modules/sqlite3/Makefile b/contrib/dlz/modules/sqlite3/Makefile new file mode 100644 index 0000000..1532921 --- /dev/null +++ b/contrib/dlz/modules/sqlite3/Makefile @@ -0,0 +1,46 @@ +# Copyright 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 https://mozilla.org/MPL/2.0/. + +# Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. +# +# The development of Dynamically Loadable Zones (DLZ) for Bind 9 was +# conceived and contributed by Rob Butler. +# +# SPDX-License-Identifier: ISC and 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 STICHTING NLNET DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL STICHTING NLNET 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. + +prefix = /usr +libdir = $(prefix)/lib/bind9 + +CFLAGS += -fPIC -g -I../include +SQLITE3_LIBS=-lsqlite3 + +all: dlz_sqlite3_dynamic.so + +dlz_dbi.o: ../common/dlz_dbi.c + $(CC) $(CFLAGS) -c ../common/dlz_dbi.c + +dlz_sqlite3_dynamic.so: dlz_sqlite3_dynamic.c dlz_dbi.o + $(CC) $(CFLAGS) -shared -o dlz_sqlite3_dynamic.so \ + dlz_sqlite3_dynamic.c dlz_dbi.o $(SQLITE3_LIBS) + +clean: + rm -f dlz_sqlite3_dynamic.so *.o + +install: dlz_sqlite3_dynamic.so + mkdir -p $(DESTDIR)$(libdir) + install dlz_sqlite3_dynamic.so $(DESTDIR)$(libdir) diff --git a/contrib/dlz/modules/sqlite3/dlz_sqlite3_dynamic.c b/contrib/dlz/modules/sqlite3/dlz_sqlite3_dynamic.c new file mode 100644 index 0000000..be6c6d7 --- /dev/null +++ b/contrib/dlz/modules/sqlite3/dlz_sqlite3_dynamic.c @@ -0,0 +1,1056 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 and ISC + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + */ + +/* + * Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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. + */ + +/* + * This provides the externally loadable SQLitee DLZ module, without + * update support. Based in part on SQLite code contributed by Tim Tessier. + */ + +#include <sqlite3.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <dlz_dbi.h> +#include <dlz_list.h> +#include <dlz_minimal.h> +#include <dlz_pthread.h> + +#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 + +/*% + * Structure to hold everything needed by this "instance" of the SQLite3 + * module remember, the module code is only loaded once, but may have + * many separate instances. + */ +typedef struct { + db_list_t *db; /*%< handle to a list of DB */ + int dbcount; + + char *dbname; + + /* Helper functions from the dlz_dlopen driver */ + log_t *log; + dns_sdlz_putrr_t *putrr; + dns_sdlz_putnamedrr_t *putnamedrr; + dns_dlz_writeablezone_t *writeable_zone; +} sqlite3_instance_t; + +/* + * SQLite3 result set + */ +typedef struct { + char **pazResult; /* Result of the query */ + int pnRow; /* Number of result rows */ + int pnColumn; /* Number of result columns */ + int curRow; /* Current row */ + char *pzErrmsg; /* Error message */ +} sqlite3_res_t; + +/* forward references */ +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo); + +void +dlz_destroy(void *dbdata); + +static void +b9_add_helper(sqlite3_instance_t *db, const char *helper_name, void *ptr); + +/* + * Private methods + */ + +static void +dlz_sqlite3_destroy(dbinstance_t *db) { + /* release DB connection */ + if (db->dbconn != NULL) { + sqlite3_close((sqlite3 *)db->dbconn); + } + sqlite3_shutdown(); + + /* destroy DB instance */ + destroy_dbinstance(db); +} + +/*% + * Properly cleans up a list of database instances. + * This function is only used when the module is compiled for + * multithreaded operation. + */ +static void +dlz_sqlite3_destroy_dblist(db_list_t *dblist) { + dbinstance_t *ndbi = NULL; + dbinstance_t *dbi = NULL; + + ndbi = DLZ_LIST_HEAD(*dblist); + while (ndbi != NULL) { + dbi = ndbi; + ndbi = DLZ_LIST_NEXT(dbi, link); + + dlz_sqlite3_destroy(dbi); + } + + /* release memory for the list structure */ + free(dblist); +} + +/*% + * 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 module is compiled for + * multithreaded operation. + */ +static dbinstance_t * +sqlite3_find_avail(sqlite3_instance_t *sqlite3) { + dbinstance_t *dbi = NULL, *head; + int count = 0; + + /* get top of list */ + head = dbi = DLZ_LIST_HEAD(*(sqlite3->db)); + + /* loop through list */ + while (count < dbc_search_limit) { + /* try to lock on the mutex */ + if (dlz_mutex_trylock(&dbi->lock) == 0) { + return (dbi); /* success, return the DBI for use. */ + } + /* not successful, keep trying */ + dbi = DLZ_LIST_NEXT(dbi, link); + + /* check to see if we have gone to the top of the list. */ + if (dbi == NULL) { + count++; + dbi = head; + } + } + + sqlite3->log(ISC_LOG_INFO, + "SQLite3 module: 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 * +escape_string(const char *instr) { + char *outstr; + char *ptr; + unsigned int len; + unsigned int tlen = 0; + unsigned int atlen = 0; + unsigned int i; + + if (instr == NULL) { + return (NULL); + } + len = strlen(instr); + atlen = (2 * len * sizeof(char)) + 1; + outstr = malloc(atlen); + if (outstr == NULL) { + return (NULL); + } + + ptr = outstr; + for (i = 0; i < len; i++) { + if (tlen > atlen || instr[i] == '\0') { + break; + } + + if (instr[i] == '\'') { + *ptr++ = '\''; + tlen++; + } + + *ptr++ = instr[i]; + tlen++; + } + *ptr = '\0'; + + return (outstr); +} + +/*% + * This function is the real core of the module. 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 too. dbdata really holds a single database instance. + * The function will construct and run the query, hopefully getting + * a result set. + */ +static isc_result_t +sqlite3_get_resultset(const char *zone, const char *record, const char *client, + unsigned int query, void *dbdata, sqlite3_res_t **rsp) { + isc_result_t result; + dbinstance_t *dbi = NULL; + sqlite3_instance_t *db = (sqlite3_instance_t *)dbdata; + char *querystring = NULL; + sqlite3_res_t *rs = NULL; + int qres = 0; + + if ((query == COUNTZONE && rsp != NULL) || + (query != COUNTZONE && (rsp == NULL || *rsp != NULL))) + { + db->log(ISC_LOG_DEBUG(2), "Invalid result set pointer."); + result = ISC_R_FAILURE; + goto cleanup; + } + + /* find an available DBI from the list */ + dbi = sqlite3_find_avail(db); + + if (dbi == NULL) { + return (ISC_R_FAILURE); + } + + /* what type of query are we going to run? */ + switch (query) { + case ALLNODES: + if (dbi->allnodes_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + break; + case ALLOWXFR: + if (dbi->allowxfr_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + break; + case AUTHORITY: + if (dbi->authority_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + break; + case FINDZONE: + if (dbi->findzone_q == NULL) { + db->log(ISC_LOG_DEBUG(2), "No query specified for " + "findzone. " + "Findzone requires a query"); + result = ISC_R_FAILURE; + goto cleanup; + } + break; + case COUNTZONE: + if (dbi->countzone_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } + break; + case LOOKUP: + if (dbi->lookup_q == NULL) { + db->log(ISC_LOG_DEBUG(2), "No query specified for " + "lookup. " + "Lookup requires a query"); + result = ISC_R_FAILURE; + goto cleanup; + } + break; + default: + db->log(ISC_LOG_ERROR, "Incorrect query flag passed to " + "sqlite3_get_resultset"); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + if (zone != NULL) { + if (dbi->zone != NULL) { + free(dbi->zone); + } + + dbi->zone = escape_string(zone); + if (dbi->zone == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { + dbi->zone = NULL; + } + + if (record != NULL) { + if (dbi->record != NULL) { + free(dbi->record); + } + + dbi->record = escape_string(record); + if (dbi->record == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { + dbi->record = NULL; + } + + if (client != NULL) { + if (dbi->client != NULL) { + free(dbi->client); + } + + dbi->client = escape_string(client); + if (dbi->client == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { + 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(dbi->allnodes_q); + break; + case ALLOWXFR: + querystring = build_querystring(dbi->allowxfr_q); + break; + case AUTHORITY: + querystring = build_querystring(dbi->authority_q); + break; + case FINDZONE: + querystring = build_querystring(dbi->findzone_q); + break; + case COUNTZONE: + querystring = build_querystring(dbi->countzone_q); + break; + case LOOKUP: + querystring = build_querystring(dbi->lookup_q); + break; + default: + db->log(ISC_LOG_ERROR, "Incorrect query flag passed to " + "sqlite3_get_resultset"); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + if (querystring == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* output the full query string when debugging */ + db->log(ISC_LOG_DEBUG(1), "\nQuery String: %s\n", querystring); + + rs = malloc(sizeof(sqlite3_res_t)); + if (rs == NULL) { + db->log(ISC_LOG_ERROR, "Failed to allocate result set"); + result = ISC_R_NOMEMORY; + goto cleanup; + } + memset(rs, 0, sizeof(sqlite3_res_t)); + + qres = sqlite3_get_table(dbi->dbconn, querystring, &rs->pazResult, + &rs->pnRow, &rs->pnColumn, &rs->pzErrmsg); + if (qres != SQLITE_OK) { + db->log(ISC_LOG_DEBUG(1), "SQLite3 query failed; %s", + rs->pzErrmsg != NULL ? rs->pzErrmsg : "unknown error"); + sqlite3_free(rs->pzErrmsg); + rs->pzErrmsg = NULL; + result = ISC_R_FAILURE; + goto cleanup; + } + + result = ISC_R_SUCCESS; + if (query == COUNTZONE) { + sqlite3_free_table(rs->pazResult); + } + + if (rsp != NULL) { + *rsp = rs; + } + +cleanup: + if (dbi->zone != NULL) { + free(dbi->zone); + dbi->zone = NULL; + } + if (dbi->record != NULL) { + free(dbi->record); + dbi->record = NULL; + } + if (dbi->client != NULL) { + free(dbi->client); + dbi->client = NULL; + } + + /* release the lock so another thread can use this dbi */ + (void)dlz_mutex_unlock(&dbi->lock); + + if (querystring != NULL) { + free(querystring); + } + + 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 char ** +dlz_sqlite3_fetch_row(sqlite3_res_t *rs) { + char **retval = NULL; + if (rs != NULL) { + if (rs->pnRow > 0 && rs->curRow < rs->pnRow) { + int index = (rs->curRow + 1) * rs->pnColumn; + retval = &rs->pazResult[index]; + rs->curRow++; + } + } + return (retval); +} + +static unsigned int +dlz_sqlite3_num_fields(sqlite3_res_t *rs) { + unsigned int retval = 0; + if (rs != NULL) { + retval = rs->pnColumn; + } + return (retval); +} + +static unsigned int +dlz_sqlite3_num_rows(sqlite3_res_t *rs) { + unsigned int retval = 0; + if (rs != NULL) { + retval = rs->pnRow; + } + return (retval); +} + +static void +dlz_sqlite3_free_result(sqlite3_res_t *rs) { + if (rs != NULL) { + sqlite3_free_table(rs->pazResult); + free(rs); + } +} + +static isc_result_t +dlz_sqlite3_process_rs(sqlite3_instance_t *db, dns_sdlzlookup_t *lookup, + sqlite3_res_t *rs) { + isc_result_t result = ISC_R_NOTFOUND; + char **row; + unsigned int fields; + unsigned int j; + char *tmpString; + char *endp; + int ttl; + + row = dlz_sqlite3_fetch_row(rs); /* get a row from the result set */ + fields = dlz_sqlite3_num_fields(rs); /* how many columns in result set + */ + while (row != NULL) { + unsigned int len = 0; + + 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 = db->putrr(lookup, "a", 86400, safeGet(row[0])); + break; + case 2: + /* + * two columns, data field, and data type. + * use default TTL of 86400. + */ + result = db->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) { + db->log(ISC_LOG_ERROR, "SQLite3 module: TTL " + "must be " + "a positive number"); + return (ISC_R_FAILURE); + } + + result = db->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; j < fields; j++) { + len += strlen(safeGet(row[j])) + 1; + } + + /* + * allocate string memory, allow for NULL to + * term string + */ + tmpString = malloc(len + 1); + if (tmpString == NULL) { + db->log(ISC_LOG_ERROR, "SQLite3 module: unable " + "to allocate " + "memory for temporary " + "string"); + dlz_sqlite3_free_result(rs); + return (ISC_R_FAILURE); + } + + strcpy(tmpString, safeGet(row[2])); + for (j = 3; j < fields; j++) { + strcat(tmpString, " "); + strcat(tmpString, safeGet(row[j])); + } + + ttl = strtol(safeGet(row[0]), &endp, 10); + if (*endp != '\0' || ttl < 0) { + db->log(ISC_LOG_ERROR, "SQLite3 module: TTL " + "must be " + "a positive number"); + free(tmpString); + return (ISC_R_FAILURE); + } + + result = db->putrr(lookup, safeGet(row[1]), ttl, + tmpString); + free(tmpString); + } + + if (result != ISC_R_SUCCESS) { + dlz_sqlite3_free_result(rs); + db->log(ISC_LOG_ERROR, "putrr returned error: %d", + result); + return (ISC_R_FAILURE); + } + + row = dlz_sqlite3_fetch_row(rs); + } + + dlz_sqlite3_free_result(rs); + return (result); +} + +/* + * DLZ methods + */ + +/*% determine if the zone is supported by (in) the database */ +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) { + isc_result_t result; + sqlite3_res_t *rs = NULL; + sqlite3_uint64 rows; + sqlite3_instance_t *db = (sqlite3_instance_t *)dbdata; + + UNUSED(methods); + UNUSED(clientinfo); + + result = sqlite3_get_resultset(name, NULL, NULL, FINDZONE, dbdata, &rs); + if (result != ISC_R_SUCCESS || rs == NULL) { + if (rs != NULL) { + dlz_sqlite3_free_result(rs); + } + + db->log(ISC_LOG_ERROR, "SQLite3 module: unable to return " + "result set for FINDZONE query"); + + return (ISC_R_FAILURE); + } + + /* + * if we returned any rows, the zone is supported. + */ + rows = dlz_sqlite3_num_rows(rs); + dlz_sqlite3_free_result(rs); + if (rows > 0) { + sqlite3_get_resultset(name, NULL, NULL, COUNTZONE, dbdata, + NULL); + return (ISC_R_SUCCESS); + } + + return (ISC_R_NOTFOUND); +} + +/*% Determine if the client is allowed to perform a zone transfer */ +isc_result_t +dlz_allowzonexfr(void *dbdata, const char *name, const char *client) { + isc_result_t result; + sqlite3_instance_t *db = (sqlite3_instance_t *)dbdata; + sqlite3_res_t *rs = NULL; + sqlite3_uint64 rows; + + /* first check if the zone is supported by the database. */ + result = dlz_findzonedb(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. + */ + result = sqlite3_get_resultset(name, NULL, client, ALLOWXFR, dbdata, + &rs); + if (result == ISC_R_NOTIMPLEMENTED) { + return (result); + } + + if (result != ISC_R_SUCCESS || rs == NULL) { + if (rs != NULL) { + dlz_sqlite3_free_result(rs); + } + db->log(ISC_LOG_ERROR, "SQLite3 module: unable to return " + "result set for ALLOWXFR query"); + return (ISC_R_FAILURE); + } + + /* + * count how many rows in result set; if we returned any, + * zone xfr is allowed. + */ + rows = dlz_sqlite3_num_rows(rs); + dlz_sqlite3_free_result(rs); + if (rows > 0) { + return (ISC_R_SUCCESS); + } + + 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. + */ +isc_result_t +dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) { + isc_result_t result; + sqlite3_instance_t *db = (sqlite3_instance_t *)dbdata; + sqlite3_res_t *rs = NULL; + char **row; + unsigned int fields; + unsigned int j; + char *tmpString; + char *endp; + int ttl; + + result = sqlite3_get_resultset(zone, NULL, NULL, ALLNODES, dbdata, &rs); + if (result == ISC_R_NOTIMPLEMENTED) { + return (result); + } + + /* if we didn't get a result set, log an err msg. */ + if (result != ISC_R_SUCCESS) { + db->log(ISC_LOG_ERROR, "SQLite3 module: unable to return " + "result set for all nodes query"); + goto cleanup; + } + + result = ISC_R_NOTFOUND; + + fields = dlz_sqlite3_num_fields(rs); + row = dlz_sqlite3_fetch_row(rs); + while (row != NULL) { + if (fields < 4) { + db->log(ISC_LOG_ERROR, "SQLite3 module: too few fields " + "returned " + "by ALLNODES query"); + result = ISC_R_FAILURE; + goto cleanup; + } + + ttl = strtol(safeGet(row[0]), &endp, 10); + if (*endp != '\0' || ttl < 0) { + db->log(ISC_LOG_ERROR, "SQLite3 module: TTL must be " + "a positive number"); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (fields == 4) { + result = db->putnamedrr(allnodes, safeGet(row[2]), + safeGet(row[1]), ttl, + safeGet(row[3])); + } else { + unsigned int len = 0; + + /* + * more than 4 fields, concatenate the last + * ones together. + */ + for (j = 3; j < fields; j++) { + len += strlen(safeGet(row[j])) + 1; + } + + tmpString = malloc(len + 1); + if (tmpString == NULL) { + db->log(ISC_LOG_ERROR, "SQLite3 module: unable " + "to allocate " + "memory for temporary " + "string"); + result = ISC_R_FAILURE; + goto cleanup; + } + + strcpy(tmpString, safeGet(row[3])); + for (j = 4; j < fields; j++) { + strcat(tmpString, " "); + strcat(tmpString, safeGet(row[j])); + } + + result = db->putnamedrr(allnodes, safeGet(row[2]), + safeGet(row[1]), ttl, + tmpString); + free(tmpString); + } + + if (result != ISC_R_SUCCESS) { + db->log(ISC_LOG_ERROR, "putnamedrr returned error: %s", + result); + result = ISC_R_FAILURE; + break; + } + + row = dlz_sqlite3_fetch_row(rs); + } + +cleanup: + if (rs != NULL) { + dlz_sqlite3_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 named. + */ +isc_result_t +dlz_authority(const char *zone, void *dbdata, dns_sdlzlookup_t *lookup) { + isc_result_t result; + sqlite3_res_t *rs = NULL; + sqlite3_instance_t *db = (sqlite3_instance_t *)dbdata; + + result = sqlite3_get_resultset(zone, NULL, NULL, AUTHORITY, dbdata, + &rs); + if (result == ISC_R_NOTIMPLEMENTED) { + return (result); + } + + if (result != ISC_R_SUCCESS) { + if (rs != NULL) { + dlz_sqlite3_free_result(rs); + } + db->log(ISC_LOG_ERROR, "SQLite3 module: unable to return " + "result set for AUTHORITY query"); + return (ISC_R_FAILURE); + } + + /* + * lookup and authority result sets are processed in the same + * manner: dlz_sqlite3_process_rs does the job for both functions. + */ + return (dlz_sqlite3_process_rs(db, lookup, rs)); +} + +/*% If zone is supported, lookup up a (or multiple) record(s) in it */ +isc_result_t +dlz_lookup(const char *zone, const char *name, void *dbdata, + dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) { + isc_result_t result; + sqlite3_res_t *rs = NULL; + sqlite3_instance_t *db = (sqlite3_instance_t *)dbdata; + + UNUSED(methods); + UNUSED(clientinfo); + + result = sqlite3_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) { + dlz_sqlite3_free_result(rs); + } + db->log(ISC_LOG_ERROR, "SQLite3 module: unable to return " + "result set for LOOKUP query"); + return (ISC_R_FAILURE); + } + + /* + * lookup and authority result sets are processed in the same + * manner: dlz_sqlite3_process_rs does the job for both functions. + */ + return (dlz_sqlite3_process_rs(db, lookup, rs)); +} + +/*% + * Create an instance of the module. + */ +isc_result_t +dlz_create(const char *dlzname, unsigned int argc, char *argv[], void **dbdata, + ...) { + isc_result_t result = ISC_R_FAILURE; + sqlite3_instance_t *s3 = NULL; + dbinstance_t *dbi = NULL; + sqlite3 *dbc = NULL; + char *tmp = NULL; + char *endp = NULL; + const char *helper_name; + int dbcount, i, ret; + va_list ap; + + UNUSED(dlzname); + + /* allocate memory for SQLite3 instance */ + s3 = calloc(1, sizeof(sqlite3_instance_t)); + if (s3 == NULL) { + return (ISC_R_NOMEMORY); + } + memset(s3, 0, sizeof(sqlite3_instance_t)); + + /* Fill in the helper functions */ + va_start(ap, dbdata); + while ((helper_name = va_arg(ap, const char *)) != NULL) { + b9_add_helper(s3, helper_name, va_arg(ap, void *)); + } + va_end(ap); + + /* if debugging, let user know we are multithreaded. */ + s3->log(ISC_LOG_DEBUG(1), "SQLite3 module: running multithreaded"); + + /* verify we have at least 4 arg's passed to the module */ + if (argc < 4) { + s3->log(ISC_LOG_ERROR, "SQLite3 module requires " + "at least 4 command line args."); + return (ISC_R_FAILURE); + } + + /* no more than 8 arg's should be passed to the module */ + if (argc > 8) { + s3->log(ISC_LOG_ERROR, "SQLite3 module cannot accept " + "more than 8 command line args."); + return (ISC_R_FAILURE); + } + + /* get db name - required */ + s3->dbname = get_parameter_value(argv[1], "dbname="); + if (s3->dbname == NULL) { + s3->log(ISC_LOG_ERROR, "SQLite3 module requires a dbname " + "parameter."); + result = ISC_R_FAILURE; + goto cleanup; + } + + /* multithreaded build can have multiple DB connections */ + tmp = get_parameter_value(argv[1], "threads="); + if (tmp == NULL) { + dbcount = 1; + } else { + dbcount = strtol(tmp, &endp, 10); + if (*endp != '\0' || dbcount < 1) { + s3->log(ISC_LOG_ERROR, "SQLite3 module: database " + "connection count " + "must be positive."); + free(tmp); + result = ISC_R_FAILURE; + goto cleanup; + } + free(tmp); + } + + /* allocate memory for database connection list */ + s3->db = calloc(1, sizeof(db_list_t)); + if (s3->db == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* initialize DB connection list */ + DLZ_LIST_INIT(*(s3->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++) { + switch (argc) { + case 4: + result = build_dbinstance(NULL, NULL, NULL, argv[2], + argv[3], NULL, &dbi, s3->log); + break; + case 5: + result = build_dbinstance(NULL, NULL, argv[4], argv[2], + argv[3], NULL, &dbi, s3->log); + break; + case 6: + result = build_dbinstance(argv[5], NULL, argv[4], + argv[2], argv[3], NULL, &dbi, + s3->log); + break; + case 7: + result = build_dbinstance(argv[5], argv[6], argv[4], + argv[2], argv[3], NULL, &dbi, + s3->log); + break; + case 8: + result = build_dbinstance(argv[5], argv[6], argv[4], + argv[2], argv[3], argv[7], + &dbi, s3->log); + break; + default: + result = ISC_R_FAILURE; + } + + if (result != ISC_R_SUCCESS) { + s3->log(ISC_LOG_ERROR, "SQLite3 module: could not " + "create " + "database instance object."); + result = ISC_R_FAILURE; + goto cleanup; + } + + /* create and set db connection */ + ret = sqlite3_initialize(); + if (ret != SQLITE_OK) { + s3->log(ISC_LOG_ERROR, "SQLite3 module: could not " + "initialize database object."); + result = ISC_R_FAILURE; + goto cleanup; + } + + ret = sqlite3_open(s3->dbname, &dbc); + if (ret != SQLITE_OK) { + s3->log(ISC_LOG_ERROR, + "SQLite3 module: could not " + "open '%s'.", + s3->dbname); + result = ISC_R_FAILURE; + goto cleanup; + } + + /* when multithreaded, build a list of DBI's */ + DLZ_LINK_INIT(dbi, link); + DLZ_LIST_APPEND(*(s3->db), dbi, link); + + dbi->dbconn = dbc; + dbc = NULL; + /* set DBI = null for next loop through. */ + dbi = NULL; + } + + *dbdata = s3; + return (ISC_R_SUCCESS); + +cleanup: + dlz_destroy(s3); + + return (result); +} + +/*% + * Destroy the module. + */ +void +dlz_destroy(void *dbdata) { + sqlite3_instance_t *db = (sqlite3_instance_t *)dbdata; + /* cleanup the list of DBI's */ + if (db->db != NULL) { + dlz_sqlite3_destroy_dblist((db_list_t *)(db->db)); + } + + if (db->dbname != NULL) { + free(db->dbname); + } +} + +/* + * Return the version of the API + */ +int +dlz_version(unsigned int *flags) { + *flags |= (DNS_SDLZFLAG_RELATIVEOWNER | DNS_SDLZFLAG_RELATIVERDATA | + DNS_SDLZFLAG_THREADSAFE); + return (DLZ_DLOPEN_VERSION); +} + +/* + * Register a helper function from the bind9 dlz_dlopen driver + */ +static void +b9_add_helper(sqlite3_instance_t *db, const char *helper_name, void *ptr) { + if (strcmp(helper_name, "log") == 0) { + db->log = (log_t *)ptr; + } + if (strcmp(helper_name, "putrr") == 0) { + db->putrr = (dns_sdlz_putrr_t *)ptr; + } + if (strcmp(helper_name, "putnamedrr") == 0) { + db->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr; + } + if (strcmp(helper_name, "writeable_zone") == 0) { + db->writeable_zone = (dns_dlz_writeablezone_t *)ptr; + } +} diff --git a/contrib/dlz/modules/sqlite3/testing/README b/contrib/dlz/modules/sqlite3/testing/README new file mode 100644 index 0000000..c7af001 --- /dev/null +++ b/contrib/dlz/modules/sqlite3/testing/README @@ -0,0 +1,10 @@ +These files were used for testing on Ubuntu Linux using SQLite3 + +- Install SQLite3: sudo apt-get install sqlite3 libsqlite3-dev +- Build sqlite3 DLZ module +- Run "sqlite3 BindDB < dlz.schema" to set up database +- Run "sqlite3 BindDB < dlz.data" to populate it +- Run "named -gc named.conf" +- Send test queries, e.g "dig @localhost -p 5300 example.com", + "dig @localhost -p 5300 axfr example.com" (AXFR should be + allowed from 127.0.0.1 only). diff --git a/contrib/dlz/modules/sqlite3/testing/dlz.data b/contrib/dlz/modules/sqlite3/testing/dlz.data new file mode 100644 index 0000000..015607f --- /dev/null +++ b/contrib/dlz/modules/sqlite3/testing/dlz.data @@ -0,0 +1,18 @@ +INSERT INTO `records` +(`zone`, `ttl`, `type`, `host`, `mx_priority`, `data`, `primary_ns`, `resp_contact`, `serial`, `refresh`, `retry`, `expire`, `minimum`) +VALUES +('example.com', 86400, 'SOA', '@', NULL, NULL, 'ns1.example.com.', 'info.example.com.', 2011043001, 10800, 7200, 604800, 86400), +('example.com', 86400, 'NS', '@', NULL, 'ns1.example.com.', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('example.com', 86400, 'NS', '@', NULL, 'ns2.example.com.', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('example.com', 86400, 'MX', '@', 10, 'mail.example.com.', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('example.com', 86400, 'A', '@', NULL, '192.168.0.2', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('example.com', 86400, 'CNAME', 'www', NULL, '@', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('example.com', 86400, 'A', 'ns1', NULL, '192.168.0.111', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('example.com', 86400, 'A', 'ns2', NULL, '192.168.0.222', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('example.com', 86400, 'A', 'mail', NULL, '192.168.0.3', NULL, NULL, NULL, NULL, NULL, NULL, NULL), +('example.com', 86400, 'TXT', '@', NULL, 'v=spf1 ip:192.168.0.3 ~all', NULL, NULL, NULL, NULL, NULL, NULL, NULL); + +INSERT INTO `xfr` +(`zone`, `client`) +VALUES +('example.com', '127.0.0.1'); diff --git a/contrib/dlz/modules/sqlite3/testing/dlz.schema b/contrib/dlz/modules/sqlite3/testing/dlz.schema new file mode 100644 index 0000000..4cbcb34 --- /dev/null +++ b/contrib/dlz/modules/sqlite3/testing/dlz.schema @@ -0,0 +1,28 @@ +CREATE TABLE IF NOT EXISTS `records` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `zone` CHAR(255) NOT NULL, + `ttl` INT NOT NULL DEFAULT '86400', + `type` CHAR(255) NOT NULL, + `host` CHAR(255) NOT NULL DEFAULT '@', + `mx_priority` INT DEFAULT NULL, + `data` text, + `primary_ns` CHAR(255) DEFAULT NULL, + `resp_contact` CHAR(255) DEFAULT NULL, + `serial` bigint DEFAULT NULL, + `refresh` INT DEFAULT NULL, + `retry` INT DEFAULT NULL, + `expire` INT DEFAULT NULL, + `minimum` INT DEFAULT NULL +); + +CREATE INDEX IF NOT EXISTS record_type on records (type); +CREATE INDEX IF NOT EXISTS record_host on records (host); +CREATE INDEX IF NOT EXISTS record_zone on records (zone); + +CREATE TABLE IF NOT EXISTS `xfr` ( + `zone` CHAR(255) NOT NULL, + `client` CHAR(255) NOT NULL +); + +CREATE INDEX IF NOT EXISTS xfr_zone on xfr (zone); +CREATE INDEX IF NOT EXISTS xfr_client on xfr (client); diff --git a/contrib/dlz/modules/sqlite3/testing/named.conf b/contrib/dlz/modules/sqlite3/testing/named.conf new file mode 100644 index 0000000..d5ce0bb --- /dev/null +++ b/contrib/dlz/modules/sqlite3/testing/named.conf @@ -0,0 +1,45 @@ +/* + * 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. + */ + +controls { }; + +options { + directory "."; + port 5300; + pid-file "named.pid"; + session-keyfile "session.key"; + listen-on { any; }; + listen-on-v6 { none; }; + recursion no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-md5; +}; + +controls { + inet 127.0.0.1 port 9953 allow { any; } keys { rndc_key; }; +}; + +dlz "test" { + database "dlopen ../dlz_sqlite3_dynamic.so + { + dbname=BindDB threads=2 + } + {SELECT zone FROM records WHERE zone = '$zone$'} + {SELECT ttl, type, mx_priority, CASE WHEN type = 'TXT' THEN '\"' || data || '\"' ELSE data END AS data FROM records WHERE zone = '$zone$' AND host = '$record$' AND type <> 'SOA' AND type <> 'NS'} + {SELECT ttl, type, data, primary_ns, resp_contact, serial, refresh, retry, expire, minimum FROM records WHERE zone = '$zone$' AND (type = 'SOA' OR type='NS')} + {SELECT ttl, type, host, mx_priority, CASE WHEN type = 'TXT' THEN '\"' || data || '\"' ELSE data END AS data, resp_contact, serial, refresh, retry, expire, minimum FROM records WHERE zone = '$zone$' AND type <> 'SOA' AND type <> 'NS'} + {SELECT zone FROM xfr where zone='$zone$' AND client = '$client$'}"; +}; |