/* $NetBSD: dlz_sqlite3_dynamic.c,v 1.3.4.1 2024/02/29 12:33:11 martin Exp $ */ /* * 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 #include #include #include #include #include #include #include #include #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; } }