diff options
Diffstat (limited to 'contrib/dlz/drivers/dlz_odbc_driver.c')
-rw-r--r-- | contrib/dlz/drivers/dlz_odbc_driver.c | 1406 |
1 files changed, 1406 insertions, 0 deletions
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 */ |