/*
* 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 Internet Software Consortium.
*
* 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 INTERNET SOFTWARE CONSORTIUM
* DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
* INTERNET SOFTWARE CONSORTIUM 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.
*/
#ifdef DLZ_ODBC
#include <config.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <isc/platform.h>
#include <dlz/sdlz_helper.h>
#include <dlz/dlz_odbc_driver.h>
#include <sql.h>
#include <sqlext.h>
#include <sqltypes.h>
/*
* Private Structures
*/
/*
* structure to hold ODBC connection & statement
*/
typedef struct{
} odbc_db_t;
/*
* Structure to hold everthing needed by this "instance" of the odbc driver
* remember, the driver code is only loaded once, but may have many separate
* instances
*/
typedef struct {
#ifdef ISC_PLATFORM_USETHREADS
#else
#endif
/* forward reference */
static size_t
/*
* Private methods
*/
static SQLSMALLINT
safeLen(void *a) {
if (a == NULL)
return 0;
return strlen((char *) a);
}
/*% propertly cleans up an odbc_instance_t */
static void
#ifdef ISC_PLATFORM_USETHREADS
/* get the first DBI in the list */
/* loop through the list */
/* get the next DBI in the list */
/* if we have a connection / statement object in memory */
/* free statement handle */
((odbc_db_t *)
}
/* disconnect from database & free connection handle */
SQLDisconnect(((odbc_db_t *)
((odbc_db_t *)
}
/* free memory that held connection & statement. */
}
/* release all memory that comprised a DBI */
}
/* release memory for the list structure */
#else /* ISC_PLATFORM_USETHREADS */
/* free statement handle */
}
/* disconnect from database, free connection handle */
}
/* free mem for the odbc_db_t structure held in db */
}
#endif /* ISC_PLATFORM_USETHREADS */
/* free sql environment */
/* free ODBC instance strings */
/* free memory for odbc_inst */
}
/*% Connects to database, and creates ODBC statements */
static isc_result_t
/*
* if db != null, we have to do some cleanup
* if statement handle != null free it
*/
}
/* if connection handle != null free it */
}
} else {
"Odbc driver unable to allocate memory");
return ISC_R_NOMEMORY;
}
}
"Odbc driver unable to allocate memory");
goto cleanup;
}
"Odbc driver unable to connect");
goto cleanup;
}
"Odbc driver unable to allocate memory");
goto cleanup;
}
return ISC_R_SUCCESS;
/* if statement handle != null free it */
}
/* if connection handle != null free it */
}
/* free memory holding 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.
*/
#ifdef ISC_PLATFORM_USETHREADS
static dbinstance_t *
{
int count = 0;
/* get top of list */
/* loop through list */
while (count < dbc_search_limit) {
/* try to lock on the mutex */
return dbi; /* success, return the DBI for use. */
/* not successful, keep trying */
/* check to see if we have gone to the top of the list. */
count++;
}
}
"Odbc driver unable to find available "
"connection after searching %d times",
count);
return NULL;
}
#endif /* ISC_PLATFORM_USETHREADS */
/*% 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 *
char *outstr;
unsigned int len;
return NULL;
return NULL;
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
{
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 and remaining are updated below. */
}
source++;
target++;
remaining--;
}
/* Write the terminating NUL character. */
*target = '\0';
}
/*%
* 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 successfull, 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 successfull, this function will perform all the cleanup.
*/
static isc_result_t
{
unsigned int j = 0;
/* get db instance / connection */
#ifdef ISC_PLATFORM_USETHREADS
/* find an available DBI from the list */
#else /* ISC_PLATFORM_USETHREADS */
/*
* only 1 DBI - no need to lock instance lock either
* only 1 thread in the whole process, no possible contention.
*/
#endif /* ISC_PLATFORM_USETHREADS */
/* if DBI is null, can't do anything else */
goto cleanup;
}
/* 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!!!
*/
goto cleanup;
}
break;
case ALLOWXFR:
/* same as comments as ALLNODES */
goto cleanup;
}
break;
case AUTHORITY:
/* same as comments as ALLNODES */
goto cleanup;
}
break;
case FINDZONE:
/* this is required. It's the whole point of DLZ! */
"No query specified for findzone. "
"Findzone requires a query");
goto cleanup;
}
break;
case LOOKUP:
/* this is required. It's also a major point of DLZ! */
"No query specified for lookup. "
"Lookup requires a query");
goto cleanup;
}
break;
default:
/*
* this should never happen. If it does, the code is
* screwed up!
*/
"Incorrect query flag passed to "
"odbc_get_resultset");
goto cleanup;
}
/*
* was a zone string passed? If so, make it safe for use in
* queries.
*/
goto cleanup;
}
} else { /* no string passed, set the string pointer to NULL */
}
/*
* was a record string passed? If so, make it safe for use in
* queries.
*/
goto cleanup;
}
} else { /* no string passed, set the string pointer to NULL */
}
/*
* was a client string passed? If so, make it safe for use in
* queries.
*/
goto cleanup;
}
} else { /* no string passed, set the string pointer to NULL */
}
/*
* what type of query are we going to run?
* this time we build the actual query to run.
*/
switch(query) {
case ALLNODES:
break;
case ALLOWXFR:
break;
case AUTHORITY:
break;
case FINDZONE:
break;
case LOOKUP:
break;
default:
/*
* this should never happen. If it does, the code is
* screwed up!
*/
"Incorrect query flag passed to "
"odbc_get_resultset");
goto cleanup;
}
/* if the querystring is null, Bummer, outta RAM. UPGRADE TIME!!! */
if (querystring == NULL) {
goto cleanup;
}
/* output the full query string during debug so we can see */
/* what lame error the query has. */
"\nQuery String: %s\n", querystring);
/* attempt query up to 3 times. */
for (j=0; j < 3; j++) {
/* try to get result set */
(SQLCHAR *) querystring,
/* if error, reset DB connection */
/* close cursor */
/* attempt to reconnect */
/* check if we reconnected */
if (result != ISC_R_SUCCESS)
break;
/* incase this is the last time through the loop */
} else {
/* return dbi */
/* result set ok, break loop */
break;
}
} /* end for loop */
cleanup: /* it's always good to cleanup after yourself */
/* if we couldn't even allocate DBI, just return NULL */
return ISC_R_FAILURE;
/* free dbi->zone string */
/* free dbi->record string */
/* free dbi->client string */
#ifdef ISC_PLATFORM_USETHREADS
/* if we are done using this dbi, release the lock */
if (result != ISC_R_SUCCESS)
#endif /* ISC_PLATFORM_USETHREADS */
/* release query string */
if (querystring != NULL)
/* 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 reponsibility of the caller to free the memory using
* isc_mem_free(ns_g_mctx, *ptr);
*/
static isc_result_t
return ISC_R_SUCCESS;
}
}
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 reponsibility of the caller to free the memory using
* isc_mem_free(ns_g_mctx, *ptr);
*/
static isc_result_t
int totSize = 0;
SQLSMALLINT i;
int j = 0;
char *data;
/* determine how large the data is */
for (i=startField; i <= endField; i++)
/* always allow for a " " (space) character */
/* after the data item */
}
if (totSize < 1)
return ISC_R_FAILURE;
/* allow for a "\n" at the end of the string/ */
return ISC_R_NOMEMORY;
/* get the data and concat all fields into a large string */
for (i=startField; i <= endField; i++) {
if (size > 0) {
j += size;
data[j++] = ' ';
data[j] = '\0';
}
} else {
return ISC_R_FAILURE;
}
}
if (result != ISC_R_SUCCESS) {
return result;
}
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
{
char *ttl_s;
char *type;
char *data;
char *endp;
int ttl;
/* get number of columns */
"Odbc driver unable to process result set");
goto process_rs_cleanup;
}
/* get things ready for processing */
/* set to null for next pass through */
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.
*/
&data)) == ISC_R_SUCCESS) {
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.
*/
&type)) == ISC_R_SUCCESS &&
&data)) == ISC_R_SUCCESS) {
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.
*/
== ISC_R_SUCCESS &&
== ISC_R_SUCCESS &&
== ISC_R_SUCCESS) {
/* try to convert ttl string to int */
/* failure converting ttl. */
"Odbc driver ttl must "
"be a postive number");
} else {
/*
* successful converting TTL,
* tell Bind everything
*/
}
} /* closes bid if () */
} /* closes switch(fields) */
/* clean up mem */
/* I sure hope we were successful */
if (result != ISC_R_SUCCESS) {
"dns_sdlz_putrr returned error. "
"Error code was: %s",
goto process_rs_cleanup;
}
} /* closes while loop */
/* close cursor */
#ifdef ISC_PLATFORM_USETHREADS
/* free lock on dbi so someone else can use it. */
#endif
return result;
}
/*
* SDLZ interface methods
*/
/*% determine if the zone is supported by (in) the database */
static isc_result_t
{
/* 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. */
/* Check that we got a result set with data */
if (result == ISC_R_SUCCESS &&
}
/* get rid of result set, we are done with it. */
#ifdef ISC_PLATFORM_USETHREADS
/* free lock on dbi so someone else can use it. */
#endif
}
return result;
}
/*% Determine if the client is allowed to perform a zone transfer */
static isc_result_t
const char *client)
{
/* first check if the zone is supported by the database. */
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.
*/
/* 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 &&
}
/* get rid of result set, we are done with it. */
#ifdef ISC_PLATFORM_USETHREADS
/* free lock on dbi so someone else can use it. */
#endif
}
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
{
char *data;
char *type;
char *ttl_s;
int ttl;
char *host;
char *endp;
/* run the query and get the result set from the database. */
/* 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) {
"Odbc driver unable to return "
"result set for all nodes query");
return (ISC_R_FAILURE);
}
/* get number of columns */
"Odbc driver unable to process result set");
goto allnodes_cleanup;
}
"Odbc driver too few fields returned by "
"all nodes query");
goto allnodes_cleanup;
}
/* get things ready for processing */
/* set to null for next pass through */
/*
* attempt to get DNS ttl, type, host, data then tell
* Bind about them
*/
&ttl_s)) == ISC_R_SUCCESS &&
&type)) == ISC_R_SUCCESS &&
&host)) == ISC_R_SUCCESS &&
&data)) == ISC_R_SUCCESS) {
/* convert ttl string to int */
/* failure converting ttl. */
"Odbc driver ttl must be "
"a postive number");
} else {
/* successful converting TTL, tell Bind */
}
} /* closes big if () */
/* clean up mem */
/* if we weren't successful, log err msg */
if (result != ISC_R_SUCCESS) {
"dns_sdlz_putnamedrr returned error. "
"Error code was: %s",
goto allnodes_cleanup;
}
} /* closes while loop */
/* close cursor */
#ifdef ISC_PLATFORM_USETHREADS
/* free lock on dbi so someone else can use it. */
#endif
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
{
/* run the query and get the result set from the database. */
/* 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) {
"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. */
}
/*% if zone is supported, lookup up a (or multiple) record(s) in it */
static isc_result_t
{
/* run the query and get the result set from the database. */
/* if we didn't get a result set, log an err msg. */
if (result != ISC_R_SUCCESS) {
"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. */
}
/*%
* 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
{
#ifdef ISC_PLATFORM_USETHREADS
/* if multi-threaded, we need a few extra variables. */
int dbcount;
int i;
char *endp;
#endif /* ISC_PLATFORM_USETHREADS */
#ifdef ISC_PLATFORM_USETHREADS
/* if debugging, let user know we are multithreaded. */
"Odbc driver running multithreaded");
#else /* ISC_PLATFORM_USETHREADS */
/* if debugging, let user know we are single threaded. */
"Odbc driver running single threaded");
#endif /* ISC_PLATFORM_USETHREADS */
/* verify we have at least 5 arg's passed to the driver */
if (argc < 5) {
"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) {
"Odbc driver cannot accept more than "
"7 command line args.");
return (ISC_R_FAILURE);
}
/* multithreaded build can have multiple DB connections */
#ifdef ISC_PLATFORM_USETHREADS
/* check how many db connections we should create */
"Odbc driver database connection count "
"must be positive.");
return (ISC_R_FAILURE);
}
#endif /* ISC_PLATFORM_USETHREADS */
/* allocate memory for odbc instance */
return (ISC_R_NOMEMORY);
/* parse connection string and get paramters. */
/* get odbc database dsn - required */
"dsn=");
"odbc driver requires a dns parameter.");
goto cleanup;
}
/* get odbc database username */
/* if no username was passed, set odbc_inst.user = NULL; */
"user=");
/* get odbc database password */
/* if no password was passed, set odbc_inst.pass = NULL; */
/* create odbc environment & set environment to ODBC V3 */
/* create environment handle */
"Odbc driver unable to allocate memory");
goto cleanup;
}
/*set ODBC version = 3 */
(void *) SQL_OV_ODBC3, 0);
"Unable to configure ODBC environment");
goto cleanup;
}
}
#ifdef ISC_PLATFORM_USETHREADS
/* allocate memory for database connection list */
goto cleanup;
}
/* initialize DB connection list */
/* create the appropriate number of database instances (DBI) */
/* append each new DBI to the end of the list */
for (i=0; i < dbcount; i++) {
#endif /* ISC_PLATFORM_USETHREADS */
/* how many queries were passed in from config file? */
switch(argc) {
case 5:
break;
case 6:
break;
case 7:
break;
case 8:
break;
default:
/* not really needed, should shut up compiler. */
}
/* unsuccessful?, log err msg and cleanup. */
if (result != ISC_R_SUCCESS) {
"Odbc driver could not create "
"database instance object.");
goto cleanup;
}
#ifdef ISC_PLATFORM_USETHREADS
/* when multithreaded, build a list of DBI's */
#endif
if (result != ISC_R_SUCCESS) {
#ifdef ISC_PLATFORM_USETHREADS
/*
* 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.
*/
"Odbc driver failed to create database "
"connection number %u after 3 attempts",
i+1);
#else
"Odbc driver failed to create database "
"connection after 3 attempts");
#endif
goto cleanup;
}
#ifdef ISC_PLATFORM_USETHREADS
/* set DB = null for next loop through. */
} /* end for loop */
#else
/* tell odbc_inst about the db connection we just created. */
#endif
/* set dbdata to the odbc_instance we created. */
/* hey, we got through all of that ok, return success. */
return(ISC_R_SUCCESS);
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
{
}
/* pointers to all our runtime methods. */
/* this is used during driver registration */
/* i.e. in dlz_odbc_init below. */
};
/*%
* Wrapper around dns_sdlzregister().
*/
dlz_odbc_init(void) {
/*
* Write debugging message to log
*/
"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.
*/
/* if we can't register the driver, there are big problems. */
if (result != ISC_R_SUCCESS) {
"dns_sdlzregister() failed: %s",
}
return result;
}
/*%
* Wrapper around dns_sdlzunregister().
*/
void
dlz_odbc_clear(void) {
/*
* Write debugging message to log
*/
"Unregistering DLZ odbc driver.");
/* unregister the driver. */
}
#endif