mirror of
https://gitlab.isc.org/isc-projects/bind9.git
synced 2025-04-18 09:44:09 +03:00
1425 lines
38 KiB
C
1425 lines
38 KiB
C
/*
|
|
* Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
|
|
*
|
|
* 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.
|
|
*
|
|
* 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 ROB BUTLER
|
|
* DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
|
|
* ROB BUTLER 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.
|
|
*/
|
|
|
|
/*
|
|
* 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 */
|