1 /*	$NetBSD: dlz_odbc_driver.c,v 1.5 2014/12/10 04:37:55 christos Exp $	*/
2 
3 /*
4  * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the
8  * above copyright notice and this permission notice appear in all
9  * copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET
12  * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
13  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
14  * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
15  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
16  * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
17  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
18  * USE OR PERFORMANCE OF THIS SOFTWARE.
19  *
20  * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was
21  * conceived and contributed by Rob Butler.
22  *
23  * Permission to use, copy, modify, and distribute this software for any
24  * purpose with or without fee is hereby granted, provided that the
25  * above copyright notice and this permission notice appear in all
26  * copies.
27  *
28  * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER
29  * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
30  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
31  * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
32  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
33  * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
34  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
35  * USE OR PERFORMANCE OF THIS SOFTWARE.
36  */
37 
38 /*
39  * Copyright (C) 1999-2001  Internet Software Consortium.
40  *
41  * Permission to use, copy, modify, and distribute this software for any
42  * purpose with or without fee is hereby granted, provided that the above
43  * copyright notice and this permission notice appear in all copies.
44  *
45  * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
46  * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
47  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
48  * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
49  * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
50  * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
51  * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
52  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
53  */
54 
55 #ifdef DLZ_ODBC
56 
57 #include <config.h>
58 #include <stdio.h>
59 #include <string.h>
60 #include <stdlib.h>
61 
62 #include <dns/log.h>
63 #include <dns/sdlz.h>
64 #include <dns/result.h>
65 
66 #include <isc/mem.h>
67 #include <isc/platform.h>
68 #include <isc/print.h>
69 #include <isc/result.h>
70 #include <isc/string.h>
71 #include <isc/util.h>
72 
73 #include <named/globals.h>
74 
75 #include <dlz/sdlz_helper.h>
76 #include <dlz/dlz_odbc_driver.h>
77 
78 #include <sql.h>
79 #include <sqlext.h>
80 #include <sqltypes.h>
81 
82 static dns_sdlzimplementation_t *dlz_odbc = NULL;
83 
84 #define dbc_search_limit 30
85 #define ALLNODES 1
86 #define ALLOWXFR 2
87 #define AUTHORITY 3
88 #define FINDZONE 4
89 #define LOOKUP 5
90 
91 #define sqlOK(a) ((a == SQL_SUCCESS || a == SQL_SUCCESS_WITH_INFO) ? -1 : 0)
92 
93 /*
94  * Private Structures
95  */
96 
97 /*
98  * structure to hold ODBC connection & statement
99  */
100 
101 typedef struct{
102 	SQLHDBC   dbc;
103 	SQLHSTMT  stmnt;
104 } odbc_db_t;
105 
106 /*
107  * Structure to hold everthing needed by this "instance" of the odbc driver
108  * remember, the driver code is only loaded once, but may have many separate
109  * instances
110  */
111 
112 typedef struct {
113 
114 #ifdef ISC_PLATFORM_USETHREADS
115 
116     db_list_t    *db;       /* handle to a list of DB */
117 
118 #else
119 
120     dbinstance_t *db;       /* handle to db */
121 
122 #endif
123 
124     SQLHENV      sql_env;  /* handle to SQL environment */
125     SQLCHAR      *dsn;
126     SQLCHAR      *user;
127     SQLCHAR      *pass;
128 } odbc_instance_t;
129 
130 /* forward reference */
131 
132 static size_t
133 odbc_makesafe(char *to, const char *from, size_t length);
134 
135 /*
136  * Private methods
137  */
138 
139 static SQLSMALLINT
safeLen(void * a)140 safeLen(void *a) {
141 	if (a == NULL)
142 		return 0;
143 	return strlen((char *) a);
144 }
145 
146 /*% propertly cleans up an odbc_instance_t */
147 
148 static void
destroy_odbc_instance(odbc_instance_t * odbc_inst)149 destroy_odbc_instance(odbc_instance_t *odbc_inst) {
150 
151 #ifdef ISC_PLATFORM_USETHREADS
152 
153 	dbinstance_t *ndbi = NULL;
154 	dbinstance_t *dbi = NULL;
155 
156 	/* get the first DBI in the list */
157 	ndbi = ISC_LIST_HEAD(*odbc_inst->db);
158 
159 	/* loop through the list */
160 	while (ndbi != NULL) {
161 		dbi = ndbi;
162 		/* get the next DBI in the list */
163 		ndbi = ISC_LIST_NEXT(dbi, link);
164 
165 		/* if we have a connection / statement object in memory */
166 		if (dbi->dbconn != NULL) {
167 			/* free statement handle */
168 			if (((odbc_db_t *) (dbi->dbconn))->stmnt != NULL) {
169 				SQLFreeHandle(SQL_HANDLE_STMT,
170 					      ((odbc_db_t *)
171 					       (dbi->dbconn))->stmnt);
172 				((odbc_db_t *) (dbi->dbconn))->stmnt = NULL;
173 			}
174 
175 			/* disconnect from database & free connection handle */
176 			if (((odbc_db_t *) (dbi->dbconn))->dbc != NULL) {
177 				SQLDisconnect(((odbc_db_t *)
178 					       dbi->dbconn)->dbc);
179 				SQLFreeHandle(SQL_HANDLE_DBC,
180 					      ((odbc_db_t *)
181 					       (dbi->dbconn))->dbc);
182 				((odbc_db_t *) (dbi->dbconn))->dbc = NULL;
183 			}
184 
185 			/* free memory that held connection & statement. */
186 			isc_mem_free(ns_g_mctx, dbi->dbconn);
187 		}
188 		/* release all memory that comprised a DBI */
189 		destroy_sqldbinstance(dbi);
190 	}
191 	/* release memory for the list structure */
192 	isc_mem_put(ns_g_mctx, odbc_inst->db, sizeof(db_list_t));
193 
194 #else /* ISC_PLATFORM_USETHREADS */
195 
196 	/* free statement handle */
197 	if (((odbc_db_t *) (odbc_inst->db->dbconn))->stmnt != NULL) {
198 		SQLFreeHandle(SQL_HANDLE_STMT,
199 			      ((odbc_db_t *) (odbc_inst->db->dbconn))->stmnt);
200 		((odbc_db_t *) (odbc_inst->db->dbconn))->stmnt = NULL;
201 	}
202 
203 	/* disconnect from database, free connection handle */
204 	if (((odbc_db_t *) (odbc_inst->db->dbconn))->dbc != NULL) {
205 		SQLDisconnect(((odbc_db_t *) (odbc_inst->db->dbconn))->dbc);
206 		SQLFreeHandle(SQL_HANDLE_DBC,
207 			      ((odbc_db_t *) (odbc_inst->db->dbconn))->dbc);
208 		((odbc_db_t *) (odbc_inst->db->dbconn))->dbc = NULL;
209 	}
210 	/*	free mem for the odbc_db_t structure held in db */
211 	if (((odbc_db_t *) odbc_inst->db->dbconn) != NULL) {
212 		isc_mem_free(ns_g_mctx, odbc_inst->db->dbconn);
213 		odbc_inst->db->dbconn = NULL;
214 	}
215 
216 	if (odbc_inst->db != NULL)
217 		destroy_sqldbinstance(odbc_inst->db);
218 
219 #endif /* ISC_PLATFORM_USETHREADS */
220 
221 
222 	/* free sql environment */
223 	if (odbc_inst->sql_env != NULL)
224 		SQLFreeHandle(SQL_HANDLE_ENV, odbc_inst->sql_env);
225 
226 	/* free ODBC instance strings */
227 	if (odbc_inst->dsn != NULL)
228 		isc_mem_free(ns_g_mctx, odbc_inst->dsn);
229 	if (odbc_inst->pass != NULL)
230 		isc_mem_free(ns_g_mctx, odbc_inst->pass);
231 	if (odbc_inst->user != NULL)
232 		isc_mem_free(ns_g_mctx, odbc_inst->user);
233 
234 	/* free memory for odbc_inst */
235 	if (odbc_inst != NULL)
236 		isc_mem_put(ns_g_mctx, odbc_inst, sizeof(odbc_instance_t));
237 
238 }
239 
240 /*% Connects to database, and creates ODBC statements */
241 
242 static isc_result_t
odbc_connect(odbc_instance_t * dbi,odbc_db_t ** dbc)243 odbc_connect(odbc_instance_t *dbi, odbc_db_t **dbc) {
244 
245 	odbc_db_t *ndb = *dbc;
246 	SQLRETURN sqlRes;
247 	isc_result_t result = ISC_R_SUCCESS;
248 
249 	if (ndb != NULL) {
250 		/*
251 		 * if db != null, we have to do some cleanup
252 		 * if statement handle != null free it
253 		 */
254 		if (ndb->stmnt != NULL) {
255 			SQLFreeHandle(SQL_HANDLE_STMT, ndb->stmnt);
256 			ndb->stmnt = NULL;
257 		}
258 
259 		/* if connection handle != null free it */
260 		if (ndb->dbc != NULL) {
261 			SQLFreeHandle(SQL_HANDLE_DBC, ndb->dbc);
262 			ndb->dbc = NULL;
263 		}
264 	} else {
265 		ndb = isc_mem_allocate(ns_g_mctx, sizeof(odbc_db_t));
266 		if (ndb == NULL) {
267 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
268 				      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
269 				      "Odbc driver unable to allocate memory");
270 			return ISC_R_NOMEMORY;
271 		}
272 		memset(ndb, 0, sizeof(odbc_db_t));
273 	}
274 
275 	sqlRes = SQLAllocHandle(SQL_HANDLE_DBC, dbi->sql_env, &(ndb->dbc));
276 	if (!sqlOK(sqlRes)) {
277 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
278 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
279 			      "Odbc driver unable to allocate memory");
280 		result = ISC_R_NOMEMORY;
281 		goto cleanup;
282 	}
283 
284 	sqlRes = SQLConnect(ndb->dbc, dbi->dsn, safeLen(dbi->dsn), dbi->user,
285 			    safeLen(dbi->user), dbi->pass, safeLen(dbi->pass));
286 	if (!sqlOK(sqlRes)) {
287 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
288 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
289 			      "Odbc driver unable to connect");
290 		result = ISC_R_FAILURE;
291 		goto cleanup;
292 	}
293 
294 	sqlRes = SQLAllocHandle(SQL_HANDLE_STMT, ndb->dbc, &(ndb->stmnt));
295 	if (!sqlOK(sqlRes)) {
296 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
297 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
298 			      "Odbc driver unable to allocate memory");
299 		result = ISC_R_NOMEMORY;
300 		goto cleanup;
301 	}
302 
303 	*dbc = ndb;
304 
305 	return ISC_R_SUCCESS;
306 
307  cleanup:
308 
309 	if (ndb != NULL) {
310 
311 		/* if statement handle != null free it */
312 		if (ndb->stmnt != NULL) {
313 			SQLFreeHandle(SQL_HANDLE_STMT, ndb->stmnt);
314 			ndb->stmnt = NULL;
315 		}
316 
317 		/* if connection handle != null free it */
318 		if (ndb->dbc != NULL) {
319 			SQLDisconnect(ndb->dbc);
320 			SQLFreeHandle(SQL_HANDLE_DBC, ndb->dbc);
321 			ndb->dbc = NULL;
322 		}
323 		/* free memory holding ndb */
324 		isc_mem_free(ns_g_mctx, ndb);
325 	}
326 
327 	return result;
328 }
329 
330 /*%
331  * Loops through the list of DB instances, attempting to lock
332  * on the mutex.  If successful, the DBI is reserved for use
333  * and the thread can perform queries against the database.
334  * If the lock fails, the next one in the list is tried.
335  * looping continues until a lock is obtained, or until
336  * the list has been searched dbc_search_limit times.
337  * This function is only used when the driver is compiled for
338  * multithreaded operation.
339  */
340 
341 #ifdef ISC_PLATFORM_USETHREADS
342 
343 static dbinstance_t *
odbc_find_avail_conn(db_list_t * dblist)344 odbc_find_avail_conn(db_list_t *dblist)
345 {
346 	dbinstance_t *dbi = NULL;
347 	dbinstance_t *head;
348 	int count = 0;
349 
350 	/* get top of list */
351 	head = dbi = ISC_LIST_HEAD(*dblist);
352 
353 	/* loop through list */
354 	while (count < dbc_search_limit) {
355 		/* try to lock on the mutex */
356 		if (isc_mutex_trylock(&dbi->instance_lock) == ISC_R_SUCCESS)
357 			return dbi; /* success, return the DBI for use. */
358 
359 		/* not successful, keep trying */
360 		dbi = ISC_LIST_NEXT(dbi, link);
361 
362 		/* check to see if we have gone to the top of the list. */
363 		if (dbi == NULL) {
364 			count++;
365 			dbi = head;
366 		}
367 	}
368 	isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
369 		      DNS_LOGMODULE_DLZ, ISC_LOG_INFO,
370 		      "Odbc driver unable to find available "
371 		      "connection after searching %d times",
372 		      count);
373 	return NULL;
374 }
375 
376 #endif /* ISC_PLATFORM_USETHREADS */
377 
378 /*% Allocates memory for a new string, and then constructs the new
379  * string by "escaping" the input string.  The new string is
380  * safe to be used in queries.  This is necessary because we cannot
381  * be sure of what types of strings are passed to us, and we don't
382  * want special characters in the string causing problems.
383  */
384 
385 static char *
odbc_escape_string(const char * instr)386 odbc_escape_string(const char *instr) {
387 
388 	char *outstr;
389 	unsigned int len;
390 
391 	if (instr == NULL)
392 		return NULL;
393 
394 	len = strlen(instr);
395 
396 	outstr = isc_mem_allocate(ns_g_mctx ,(2 * len * sizeof(char)) + 1);
397 	if (outstr == NULL)
398 		return NULL;
399 
400 	odbc_makesafe(outstr, instr, len);
401 
402 	return outstr;
403 }
404 
405 /* ---------------
406  * Escaping arbitrary strings to get valid SQL strings/identifiers.
407  *
408  * Replaces "\\" with "\\\\" and "'" with "''".
409  * length is the length of the buffer pointed to by
410  * from.  The buffer at to must be at least 2*length + 1 characters
411  * long.  A terminating NUL character is written.
412  *
413  * NOTICE!!!
414  * This function was borrowed directly from PostgreSQL's libpq.
415  *
416  * The copyright statements from the original file containing this
417  * function are included below:
418  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
419  * Portions Copyright (c) 1994, Regents of the University of California
420  * ---------------
421  */
422 
423 static size_t
odbc_makesafe(char * to,const char * from,size_t length)424 odbc_makesafe(char *to, const char *from, size_t length)
425 {
426 	const char *source = from;
427 	char	   *target = to;
428 	unsigned int remaining = length;
429 
430 	while (remaining > 0)
431 	{
432 		switch (*source)
433 		{
434 			case '\\':
435 				*target = '\\';
436 				target++;
437 				*target = '\\';
438 				/* target and remaining are updated below. */
439 				break;
440 
441 			case '\'':
442 				*target = '\'';
443 				target++;
444 				*target = '\'';
445 				/* target and remaining are updated below. */
446 				break;
447 
448 			default:
449 				*target = *source;
450 				/* target and remaining are updated below. */
451 		}
452 		source++;
453 		target++;
454 		remaining--;
455 	}
456 
457 	/* Write the terminating NUL character. */
458 	*target = '\0';
459 
460 	return target - to;
461 }
462 
463 /*%
464  * This function is the real core of the driver.   Zone, record
465  * and client strings are passed in (or NULL is passed if the
466  * string is not available).  The type of query we want to run
467  * is indicated by the query flag, and the dbdata object is passed
468  * passed in to.  dbdata really holds either:
469  *		1) a list of database instances (in multithreaded mode) OR
470  *		2) a single database instance (in single threaded mode)
471  * The function will construct the query and obtain an available
472  * database instance (DBI).  It will then run the query and hopefully
473  * obtain a result set.  The data base instance that is used is returned
474  * to the caller so they can get the data from the result set from it.
475  * If successfull, it will be the responsibility of the caller to close
476  * the cursor, and unlock the mutex of the DBI when they are done with it.
477  * If not successfull, this function will perform all the cleanup.
478  */
479 
480 
481 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)482 odbc_get_resultset(const char *zone, const char *record,
483 		   const char *client, unsigned int query,
484 		   void *dbdata, dbinstance_t **r_dbi)
485 {
486 
487 	isc_result_t result;
488 	dbinstance_t *dbi = NULL;
489 	char *querystring = NULL;
490 	unsigned int j = 0;
491 	SQLRETURN sqlRes;
492 
493 	REQUIRE(*r_dbi == NULL);
494 
495 	/* get db instance / connection */
496 #ifdef ISC_PLATFORM_USETHREADS
497 
498 	/* find an available DBI from the list */
499 	dbi = odbc_find_avail_conn(((odbc_instance_t *) dbdata)->db);
500 
501 #else /* ISC_PLATFORM_USETHREADS */
502 
503 	/*
504 	 * only 1 DBI - no need to lock instance lock either
505 	 * only 1 thread in the whole process, no possible contention.
506 	 */
507 	dbi =  (dbinstance_t *) ((odbc_instance_t *) dbdata)->db;
508 
509 #endif /* ISC_PLATFORM_USETHREADS */
510 
511 	/* if DBI is null, can't do anything else */
512 	if (dbi == NULL) {
513 		result = ISC_R_FAILURE;
514 		goto cleanup;
515 	}
516 
517 	/* what type of query are we going to run? */
518 	switch(query) {
519 	case ALLNODES:
520 		/*
521 		 * if the query was not passed in from the config file
522 		 * then we can't run it.  return not_implemented, so
523 		 * it's like the code for that operation was never
524 		 * built into the driver.... AHHH flexibility!!!
525 		 */
526 		if (dbi->allnodes_q == NULL) {
527 			result = ISC_R_NOTIMPLEMENTED;
528 			goto cleanup;
529 		}
530 		break;
531 	case ALLOWXFR:
532 		/* same as comments as ALLNODES */
533 		if (dbi->allowxfr_q == NULL) {
534 			result = ISC_R_NOTIMPLEMENTED;
535 			goto cleanup;
536 		}
537 		break;
538 	case AUTHORITY:
539 		/* same as comments as ALLNODES */
540 		if (dbi->authority_q == NULL) {
541 			result = ISC_R_NOTIMPLEMENTED;
542 			goto cleanup;
543 		}
544 		break;
545 	case FINDZONE:
546 		/* this is required.  It's the whole point of DLZ! */
547 		if (dbi->findzone_q == NULL) {
548 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
549 				      DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
550 				      "No query specified for findzone.  "
551 				      "Findzone requires a query");
552 			result = ISC_R_FAILURE;
553 			goto cleanup;
554 		}
555 		break;
556 	case LOOKUP:
557 		/* this is required.  It's also a major point of DLZ! */
558 		if (dbi->lookup_q == NULL) {
559 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
560 				      DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
561 				      "No query specified for lookup.  "
562 				      "Lookup requires a query");
563 			result = ISC_R_FAILURE;
564 			goto cleanup;
565 		}
566 		break;
567 	default:
568 		/*
569 		 * this should never happen.  If it does, the code is
570 		 * screwed up!
571 		 */
572 		UNEXPECTED_ERROR(__FILE__, __LINE__,
573 				 "Incorrect query flag passed to "
574 				 "odbc_get_resultset");
575 		result = ISC_R_UNEXPECTED;
576 		goto cleanup;
577 	}
578 
579 
580 	/*
581 	 * was a zone string passed?  If so, make it safe for use in
582 	 * queries.
583 	 */
584 	if (zone != NULL) {
585 		dbi->zone = odbc_escape_string(zone);
586 		if (dbi->zone == NULL) {
587 			result = ISC_R_NOMEMORY;
588 			goto cleanup;
589 		}
590 	} else {	/* no string passed, set the string pointer to NULL */
591 		dbi->zone = NULL;
592 	}
593 
594 	/*
595 	 * was a record string passed?  If so, make it safe for use in
596 	 * queries.
597 	 */
598 	if (record != NULL) {
599 		dbi->record = odbc_escape_string(record);
600 		if (dbi->record == NULL) {
601 			result = ISC_R_NOMEMORY;
602 			goto cleanup;
603 		}
604 	} else {	/* no string passed, set the string pointer to NULL */
605 		dbi->record = NULL;
606 	}
607 
608 	/*
609 	 * was a client string passed?  If so, make it safe for use in
610 	 * queries.
611 	 */
612 	if (client != NULL) {
613 		dbi->client = odbc_escape_string(client);
614 		if (dbi->client == NULL) {
615 			result = ISC_R_NOMEMORY;
616 			goto cleanup;
617 		}
618 	} else {	/* no string passed, set the string pointer to NULL */
619 		dbi->client = NULL;
620 	}
621 
622 	/*
623 	 * what type of query are we going to run?
624 	 * this time we build the actual query to run.
625 	 */
626 	switch(query) {
627 	case ALLNODES:
628 		querystring = build_querystring(ns_g_mctx, dbi->allnodes_q);
629 		break;
630 	case ALLOWXFR:
631 		querystring = build_querystring(ns_g_mctx, dbi->allowxfr_q);
632 		break;
633 	case AUTHORITY:
634 		querystring = build_querystring(ns_g_mctx, dbi->authority_q);
635 		break;
636 	case FINDZONE:
637 		querystring = build_querystring(ns_g_mctx, dbi->findzone_q);
638 		break;
639 	case LOOKUP:
640 		querystring = build_querystring(ns_g_mctx, dbi->lookup_q);
641 		break;
642 	default:
643 		/*
644 		 * this should never happen.  If it does, the code is
645 		 * screwed up!
646 		 */
647 		UNEXPECTED_ERROR(__FILE__, __LINE__,
648 				 "Incorrect query flag passed to "
649 				 "odbc_get_resultset");
650 		result = ISC_R_UNEXPECTED;
651 		goto cleanup;
652 	}
653 
654 	/* if the querystring is null, Bummer, outta RAM.  UPGRADE TIME!!!   */
655 	if (querystring  == NULL) {
656 		result = ISC_R_NOMEMORY;
657 		goto cleanup;
658 	}
659 
660 	/* output the full query string during debug so we can see */
661 	/* what lame error the query has. */
662 	isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
663 		      DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(1),
664 		      "\nQuery String: %s\n", querystring);
665 
666 	/* attempt query up to 3 times. */
667 	for (j=0; j < 3; j++) {
668 		/* try to get result set */
669 		sqlRes = SQLExecDirect(((odbc_db_t *) dbi->dbconn)->stmnt,
670 				       (SQLCHAR *) querystring,
671 				       (SQLINTEGER) strlen(querystring));
672 
673 		/* if error, reset DB connection */
674 		if (!sqlOK(sqlRes)) {
675 			/* close cursor */
676 			SQLCloseCursor(((odbc_db_t *) dbi->dbconn)->stmnt);
677 			/* attempt to reconnect */
678 			result = odbc_connect((odbc_instance_t *) dbdata,
679 					      (odbc_db_t **) &(dbi->dbconn));
680 			/* check if we reconnected */
681 			if (result != ISC_R_SUCCESS)
682 				break;
683 			/* incase this is the last time through the loop */
684 			result = ISC_R_FAILURE;
685 		} else {
686 			result = ISC_R_SUCCESS;
687 			/* return dbi */
688 			*r_dbi = dbi;
689 			/* result set ok, break loop */
690 			break;
691 		}
692 	}	/* end for loop */
693 
694  cleanup:	/* it's always good to cleanup after yourself */
695 
696 		/* if we couldn't even allocate DBI, just return NULL */
697 	if (dbi == NULL)
698 		return ISC_R_FAILURE;
699 
700 	/* free dbi->zone string */
701 	if (dbi->zone != NULL)
702 		isc_mem_free(ns_g_mctx, dbi->zone);
703 
704 	/* free dbi->record string */
705 	if (dbi->record != NULL)
706 		isc_mem_free(ns_g_mctx, dbi->record);
707 
708 	/* free dbi->client string */
709 	if (dbi->client != NULL)
710 		isc_mem_free(ns_g_mctx, dbi->client);
711 
712 #ifdef ISC_PLATFORM_USETHREADS
713 
714 	/* if we are done using this dbi, release the lock */
715 	if (result != ISC_R_SUCCESS)
716 		isc_mutex_unlock(&dbi->instance_lock);
717 
718 #endif /* ISC_PLATFORM_USETHREADS */
719 
720 	/* release query string */
721 	if (querystring  != NULL)
722 		isc_mem_free(ns_g_mctx, querystring );
723 
724 	/* return result */
725 	return result;
726 
727 }
728 
729 /*%
730  * Gets a single field from the ODBC statement.  The memory for the
731  * returned data is dynamically allocated.  If this method is successful
732  * it is the reponsibility of the caller to free the memory using
733  * isc_mem_free(ns_g_mctx, *ptr);
734  */
735 
736 static isc_result_t
odbc_getField(SQLHSTMT * stmnt,SQLSMALLINT field,char ** data)737 odbc_getField(SQLHSTMT *stmnt, SQLSMALLINT field, char **data) {
738 
739 	SQLLEN size;
740 
741 	REQUIRE(data != NULL && *data == NULL);
742 
743 	if (sqlOK(SQLColAttribute(stmnt, field, SQL_DESC_DISPLAY_SIZE,
744 				  NULL, 0, NULL, &size)) && size > 0) {
745 		*data = isc_mem_allocate(ns_g_mctx, size + 1);
746 		if (data != NULL) {
747 			if (sqlOK(SQLGetData(stmnt, field, SQL_C_CHAR,
748 					     *data, size + 1,&size)))
749 				return ISC_R_SUCCESS;
750 			isc_mem_free(ns_g_mctx, *data);
751 		}
752 	}
753 	return ISC_R_FAILURE;
754 }
755 
756 /*%
757  * Gets multiple fields from the ODBC statement.  The memory for the
758  * returned data is dynamically allocated.  If this method is successful
759  * it is the reponsibility of the caller to free the memory using
760  * isc_mem_free(ns_g_mctx, *ptr);
761  */
762 
763 static isc_result_t
odbc_getManyFields(SQLHSTMT * stmnt,SQLSMALLINT startField,SQLSMALLINT endField,char ** retData)764 odbc_getManyFields(SQLHSTMT *stmnt, SQLSMALLINT startField,
765 		   SQLSMALLINT endField, char **retData) {
766 
767 	isc_result_t result;
768 	SQLLEN size;
769 	int totSize = 0;
770 	SQLSMALLINT i;
771 	int j = 0;
772 	char *data;
773 
774 	REQUIRE(retData != NULL && *retData == NULL);
775 	REQUIRE(startField > 0 && startField <= endField);
776 
777 	/* determine how large the data is */
778 	for (i=startField; i <= endField; i++)
779 		if (sqlOK(SQLColAttribute(stmnt, i, SQL_DESC_DISPLAY_SIZE,
780 					  NULL, 0, NULL, &size)) && size > 0) {
781 			/* always allow for a " " (space) character */
782 			totSize += (size + 1);
783 			/* after the data item */
784 		}
785 
786 	if (totSize < 1)
787 		return ISC_R_FAILURE;
788 
789 	/* allow for a "\n" at the end of the string/ */
790 	data = isc_mem_allocate(ns_g_mctx, ++totSize);
791 	if (data == NULL)
792 		return ISC_R_NOMEMORY;
793 
794 	result = ISC_R_FAILURE;
795 
796 	/* get the data and concat all fields into a large string */
797 	for (i=startField; i <= endField; i++) {
798 		if (sqlOK(SQLGetData(stmnt, i, SQL_C_CHAR, &(data[j]),
799 				     totSize - j, &size))) {
800 			if (size > 0) {
801 				j += size;
802 				data[j++] = ' ';
803 				data[j] = '\0';
804 				result = ISC_R_SUCCESS;
805 			}
806 		} else {
807 			isc_mem_free(ns_g_mctx, data);
808 			return ISC_R_FAILURE;
809 		}
810 	}
811 
812 	if (result != ISC_R_SUCCESS) {
813 		isc_mem_free(ns_g_mctx, data);
814 		return result;
815 	}
816 
817 	*retData = data;
818 	return ISC_R_SUCCESS;
819 
820 }
821 
822 /*%
823  * The processing of result sets for lookup and authority are
824  * exactly the same.  So that functionality has been moved
825  * into this function to minimize code.
826  */
827 
828 static isc_result_t
odbc_process_rs(dns_sdlzlookup_t * lookup,dbinstance_t * dbi)829 odbc_process_rs(dns_sdlzlookup_t *lookup, dbinstance_t *dbi)
830 {
831 
832 
833 	isc_result_t result;
834 	SQLSMALLINT fields;
835 	SQLHSTMT  *stmnt;
836 	char *ttl_s;
837 	char *type;
838 	char *data;
839 	char *endp;
840 	int ttl;
841 
842 	REQUIRE(dbi != NULL);
843 
844 	stmnt = ((odbc_db_t *) (dbi->dbconn))->stmnt;
845 
846 	/* get number of columns */
847 	if (!sqlOK(SQLNumResultCols(stmnt, &fields))) {
848 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
849 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
850 			      "Odbc driver unable to process result set");
851 		result = ISC_R_FAILURE;
852 		goto process_rs_cleanup;
853 	}
854 
855 	/* get things ready for processing */
856 	result = ISC_R_FAILURE;
857 
858 	while (sqlOK(SQLFetch(stmnt))) {
859 
860 		/* set to null for next pass through */
861 		data = type = ttl_s = NULL;
862 
863 		switch(fields) {
864 		case 1:
865 			/*
866 			 * one column in rs, it's the data field.  use
867 			 * default type of A record, and default TTL
868 			 * of 86400.  attempt to get data, & tell bind
869 			 * about it.
870 			 */
871 			if ((result = odbc_getField(stmnt, 1,
872 						    &data)) == ISC_R_SUCCESS) {
873 				result = dns_sdlz_putrr(lookup, "a",
874 							86400, data);
875 			}
876 			break;
877 		case 2:
878 			/*
879 			 * two columns, data field, and data type.
880 			 * use default TTL of 86400.  attempt to get
881 			 * DNS type & data, then tell bind about it.
882 			 */
883 			if ((result = odbc_getField(stmnt, 1,
884 						    &type)) == ISC_R_SUCCESS &&
885 			    (result = odbc_getField(stmnt, 2,
886 						    &data)) == ISC_R_SUCCESS) {
887 				result = dns_sdlz_putrr(lookup, type,
888 							86400, data);
889 			}
890 			break;
891 		default:
892 			/*
893 			 * 3 fields or more, concatenate the last ones
894 			 * together.  attempt to get DNS ttl, type,
895 			 * data then tell Bind about them.
896 			 */
897 			if ((result = odbc_getField(stmnt, 1, &ttl_s))
898 				== ISC_R_SUCCESS &&
899 			    (result = odbc_getField(stmnt, 2, &type))
900 				== ISC_R_SUCCESS &&
901 			    (result = odbc_getManyFields(stmnt, 3,
902 							 fields, &data))
903 				== ISC_R_SUCCESS) {
904 				/* try to convert ttl string to int */
905 				ttl = strtol(ttl_s, &endp, 10);
906 				/* failure converting ttl. */
907 				if (*endp != '\0' || ttl < 0) {
908 					isc_log_write(dns_lctx,
909 						      DNS_LOGCATEGORY_DATABASE,
910 						      DNS_LOGMODULE_DLZ,
911 						      ISC_LOG_ERROR,
912 						      "Odbc driver ttl must "
913 						      "be a postive number");
914 					result = ISC_R_FAILURE;
915 				} else {
916 					/*
917 					 * successful converting TTL,
918 					 * tell Bind everything
919 					 */
920 					result = dns_sdlz_putrr(lookup, type,
921 								ttl, data);
922 				}
923 			} /* closes bid if () */
924 		} /* closes switch(fields) */
925 
926 		/* clean up mem */
927 		if (ttl_s != NULL)
928 			isc_mem_free(ns_g_mctx, ttl_s);
929 		if (type != NULL)
930 			isc_mem_free(ns_g_mctx, type);
931 		if (data != NULL)
932 			isc_mem_free(ns_g_mctx, data);
933 
934 		/* I sure hope we were successful */
935 		if (result != ISC_R_SUCCESS) {
936 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
937 				      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
938 				      "dns_sdlz_putrr returned error. "
939 				      "Error code was: %s",
940 				      isc_result_totext(result));
941 			result = ISC_R_FAILURE;
942 			goto process_rs_cleanup;
943 		}
944 	} /* closes while loop */
945 
946  process_rs_cleanup:
947 
948 	/* close cursor */
949 	SQLCloseCursor(((odbc_db_t *) (dbi->dbconn))->stmnt);
950 
951 #ifdef ISC_PLATFORM_USETHREADS
952 
953 	/* free lock on dbi so someone else can use it. */
954 	isc_mutex_unlock(&dbi->instance_lock);
955 
956 #endif
957 
958 	return result;
959 }
960 
961 /*
962  * SDLZ interface methods
963  */
964 
965 /*% determine if the zone is supported by (in) the database */
966 
967 static isc_result_t
odbc_findzone(void * driverarg,void * dbdata,const char * name,dns_clientinfomethods_t * methods,dns_clientinfo_t * clientinfo)968 odbc_findzone(void *driverarg, void *dbdata, const char *name,
969 	      dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo)
970 {
971 
972 	isc_result_t result;
973 	dbinstance_t *dbi = NULL;
974 
975 	UNUSED(driverarg);
976 	UNUSED(methods);
977 	UNUSED(clientinfo);
978 
979 	/* run the query and get the result set from the database. */
980 	/* if result != ISC_R_SUCCESS cursor and mutex already cleaned up. */
981 	/* so we don't have to do it here. */
982 	result = odbc_get_resultset(name, NULL, NULL, FINDZONE, dbdata, &dbi);
983 
984 	/* Check that we got a result set with data */
985 	if (result == ISC_R_SUCCESS &&
986 	    !sqlOK(SQLFetch(((odbc_db_t *) (dbi->dbconn))->stmnt))) {
987 		result = ISC_R_NOTFOUND;
988 	}
989 
990 	if (dbi != NULL) {
991 		/* get rid of result set, we are done with it. */
992 		SQLCloseCursor(((odbc_db_t *) (dbi->dbconn))->stmnt);
993 
994 #ifdef ISC_PLATFORM_USETHREADS
995 
996 		/* free lock on dbi so someone else can use it. */
997 		isc_mutex_unlock(&dbi->instance_lock);
998 #endif
999 	}
1000 
1001 	return result;
1002 }
1003 
1004 /*% Determine if the client is allowed to perform a zone transfer */
1005 static isc_result_t
odbc_allowzonexfr(void * driverarg,void * dbdata,const char * name,const char * client)1006 odbc_allowzonexfr(void *driverarg, void *dbdata, const char *name,
1007 		  const char *client)
1008 {
1009 	isc_result_t result;
1010 	dbinstance_t *dbi = NULL;
1011 
1012 	UNUSED(driverarg);
1013 
1014 	/* first check if the zone is supported by the database. */
1015 	result = odbc_findzone(driverarg, dbdata, name, NULL, NULL);
1016 	if (result != ISC_R_SUCCESS)
1017 		return (ISC_R_NOTFOUND);
1018 
1019 	/*
1020 	 * if we get to this point we know the zone is supported by
1021 	 * the database.  the only questions now are is the zone
1022 	 * transfer is allowed for this client and did the config file
1023 	 * have an allow zone xfr query
1024 	 *
1025 	 * Run our query, and get a result set from the database.  if
1026 	 * result != ISC_R_SUCCESS cursor and mutex already cleaned
1027 	 * up, so we don't have to do it here.
1028 	 */
1029 	result = odbc_get_resultset(name, NULL, client, ALLOWXFR,
1030 				    dbdata, &dbi);
1031 
1032 	/* if we get "not implemented", send it along. */
1033 	if (result == ISC_R_NOTIMPLEMENTED)
1034 		return result;
1035 
1036 	/* Check that we got a result set with data */
1037 	if (result == ISC_R_SUCCESS &&
1038 	    !sqlOK(SQLFetch(((odbc_db_t *) (dbi->dbconn))->stmnt))) {
1039 		result = ISC_R_NOPERM;
1040 	}
1041 
1042 	if (dbi != NULL) {
1043 		/* get rid of result set, we are done with it. */
1044 		SQLCloseCursor(((odbc_db_t *) (dbi->dbconn))->stmnt);
1045 
1046 #ifdef ISC_PLATFORM_USETHREADS
1047 
1048 		/* free lock on dbi so someone else can use it. */
1049 		isc_mutex_unlock(&dbi->instance_lock);
1050 #endif
1051 
1052 	}
1053 
1054 	return result;
1055 }
1056 
1057 /*%
1058  * If the client is allowed to perform a zone transfer, the next order of
1059  * business is to get all the nodes in the zone, so bind can respond to the
1060  * query.
1061  */
1062 
1063 static isc_result_t
odbc_allnodes(const char * zone,void * driverarg,void * dbdata,dns_sdlzallnodes_t * allnodes)1064 odbc_allnodes(const char *zone, void *driverarg, void *dbdata,
1065 	      dns_sdlzallnodes_t *allnodes)
1066 {
1067 
1068 	isc_result_t result;
1069 	dbinstance_t *dbi = NULL;
1070 	SQLHSTMT  *stmnt;
1071 	SQLSMALLINT fields;
1072 	char *data;
1073 	char *type;
1074 	char *ttl_s;
1075 	int ttl;
1076 	char *host;
1077 	char *endp;
1078 
1079 	UNUSED(driverarg);
1080 
1081 	/* run the query and get the result set from the database. */
1082 	result = odbc_get_resultset(zone, NULL, NULL, ALLNODES, dbdata, &dbi);
1083 
1084 	/* if we get "not implemented", send it along */
1085 	if (result == ISC_R_NOTIMPLEMENTED)
1086 		return result;
1087 
1088 	/* if we didn't get a result set, log an err msg. */
1089 	if (result != ISC_R_SUCCESS) {
1090 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1091 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1092 			      "Odbc driver unable to return "
1093 			      "result set for all nodes query");
1094 		return (ISC_R_FAILURE);
1095 	}
1096 
1097 	stmnt = ((odbc_db_t *) (dbi->dbconn))->stmnt;
1098 
1099 	/* get number of columns */
1100 	if (!sqlOK(SQLNumResultCols(stmnt, &fields))) {
1101 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1102 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1103 			      "Odbc driver unable to process result set");
1104 		result = ISC_R_FAILURE;
1105 		goto allnodes_cleanup;
1106 	}
1107 
1108 	if (fields < 4) {	/* gotta have at least 4 columns */
1109 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1110 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1111 			      "Odbc driver too few fields returned by "
1112 			      "all nodes query");
1113 		result = ISC_R_FAILURE;
1114 		goto allnodes_cleanup;
1115 	}
1116 
1117 	/* get things ready for processing */
1118 	result = ISC_R_FAILURE;
1119 
1120 	while (sqlOK(SQLFetch(stmnt))) {
1121 
1122 		/* set to null for next pass through */
1123 		data = host = type = ttl_s = NULL;
1124 
1125 		/*
1126 		 * attempt to get DNS ttl, type, host, data then tell
1127 		 * Bind about them
1128 		 */
1129 		if ((result = odbc_getField(stmnt, 1,
1130 					    &ttl_s)) == ISC_R_SUCCESS &&
1131 		    (result = odbc_getField(stmnt, 2,
1132 					    &type)) == ISC_R_SUCCESS &&
1133 		    (result = odbc_getField(stmnt, 3,
1134 					    &host)) == ISC_R_SUCCESS &&
1135 		    (result = odbc_getManyFields(stmnt, 4, fields,
1136 						 &data)) == ISC_R_SUCCESS) {
1137 			/* convert ttl string to int */
1138 			ttl = strtol(ttl_s, &endp, 10);
1139 			/* failure converting ttl. */
1140 			if (*endp != '\0' || ttl < 0) {
1141 				isc_log_write(dns_lctx,
1142 					      DNS_LOGCATEGORY_DATABASE,
1143 					      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1144 					      "Odbc driver ttl must be "
1145 					      "a postive number");
1146 				result = ISC_R_FAILURE;
1147 			} else {
1148 				/* successful converting TTL, tell Bind  */
1149 				result = dns_sdlz_putnamedrr(allnodes, host,
1150 							     type, ttl, data);
1151 			}
1152 		} /* closes big if () */
1153 
1154 		/* clean up mem */
1155 		if (ttl_s != NULL)
1156 			isc_mem_free(ns_g_mctx, ttl_s);
1157 		if (type != NULL)
1158 			isc_mem_free(ns_g_mctx, type);
1159 		if (host != NULL)
1160 			isc_mem_free(ns_g_mctx, host);
1161 		if (data != NULL)
1162 			isc_mem_free(ns_g_mctx, data);
1163 
1164 		/* if we weren't successful, log err msg */
1165 		if (result != ISC_R_SUCCESS) {
1166 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1167 				      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1168 				      "dns_sdlz_putnamedrr returned error. "
1169 				      "Error code was: %s",
1170 				      isc_result_totext(result));
1171 			result = ISC_R_FAILURE;
1172 			goto allnodes_cleanup;
1173 		}
1174 	} /* closes while loop */
1175 
1176  allnodes_cleanup:
1177 
1178 	/* close cursor */
1179 	SQLCloseCursor(((odbc_db_t *) (dbi->dbconn))->stmnt);
1180 
1181 #ifdef ISC_PLATFORM_USETHREADS
1182 
1183 	/* free lock on dbi so someone else can use it. */
1184 	isc_mutex_unlock(&dbi->instance_lock);
1185 
1186 #endif
1187 
1188 	return result;
1189 }
1190 
1191 /*%
1192  * if the lookup function does not return SOA or NS records for the zone,
1193  * use this function to get that information for Bind.
1194  */
1195 
1196 static isc_result_t
odbc_authority(const char * zone,void * driverarg,void * dbdata,dns_sdlzlookup_t * lookup)1197 odbc_authority(const char *zone, void *driverarg, void *dbdata,
1198 	       dns_sdlzlookup_t *lookup)
1199 {
1200 	isc_result_t result;
1201 	dbinstance_t *dbi = NULL;
1202 
1203 	UNUSED(driverarg);
1204 
1205 	/* run the query and get the result set from the database. */
1206 	result = odbc_get_resultset(zone, NULL, NULL, AUTHORITY, dbdata, &dbi);
1207 	/* if we get "not implemented", send it along */
1208 	if (result == ISC_R_NOTIMPLEMENTED)
1209 		return result;
1210 	/* if we didn't get a result set, log an err msg. */
1211 	if (result != ISC_R_SUCCESS) {
1212 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1213 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1214 			      "Odbc driver unable to return "
1215 			      "result set for authority query");
1216 		return (ISC_R_FAILURE);
1217 	}
1218 	/* lookup and authority result sets are processed in the same manner */
1219 	/* odbc_process_rs does the job for both functions. */
1220 	return odbc_process_rs(lookup, dbi);
1221 }
1222 
1223 /*% if zone is supported, lookup up a (or multiple) record(s) in it */
1224 
1225 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)1226 odbc_lookup(const char *zone, const char *name, void *driverarg,
1227 	    void *dbdata, dns_sdlzlookup_t *lookup,
1228 	    dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo)
1229 {
1230 	isc_result_t result;
1231 	dbinstance_t *dbi = NULL;
1232 
1233 	UNUSED(driverarg);
1234 	UNUSED(methods);
1235 	UNUSED(clientinfo);
1236 
1237 	/* run the query and get the result set from the database. */
1238 	result = odbc_get_resultset(zone, name, NULL, LOOKUP, dbdata, &dbi);
1239 	/* if we didn't get a result set, log an err msg. */
1240 	if (result != ISC_R_SUCCESS) {
1241 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1242 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1243 			      "Odbc driver unable to return "
1244 			      "result set for lookup query");
1245 		return (ISC_R_FAILURE);
1246 	}
1247 	/* lookup and authority result sets are processed in the same manner */
1248 	/* odbc_process_rs does the job for both functions. */
1249 	return odbc_process_rs(lookup, dbi);
1250 }
1251 
1252 /*%
1253  * create an instance of the driver.  Remember, only 1 copy of the driver's
1254  * code is ever loaded, the driver has to remember which context it's
1255  * operating in.  This is done via use of the dbdata argument which is
1256  * passed into all query functions.
1257  */
1258 static isc_result_t
odbc_create(const char * dlzname,unsigned int argc,char * argv[],void * driverarg,void ** dbdata)1259 odbc_create(const char *dlzname, unsigned int argc, char *argv[],
1260 	    void *driverarg, void **dbdata)
1261 {
1262 	isc_result_t result;
1263 	odbc_instance_t *odbc_inst = NULL;
1264 	dbinstance_t *db = NULL;
1265 	SQLRETURN sqlRes;
1266 
1267 #ifdef ISC_PLATFORM_USETHREADS
1268 	/* if multi-threaded, we need a few extra variables. */
1269 	int dbcount;
1270 	int i;
1271 	char *endp;
1272 
1273 #endif /* ISC_PLATFORM_USETHREADS */
1274 
1275 	UNUSED(dlzname);
1276 	UNUSED(driverarg);
1277 
1278 #ifdef ISC_PLATFORM_USETHREADS
1279 	/* if debugging, let user know we are multithreaded. */
1280 	isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1281 		      DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(1),
1282 		      "Odbc driver running multithreaded");
1283 #else /* ISC_PLATFORM_USETHREADS */
1284 	/* if debugging, let user know we are single threaded. */
1285 	isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1286 		      DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(1),
1287 		      "Odbc driver running single threaded");
1288 #endif /* ISC_PLATFORM_USETHREADS */
1289 
1290 	/* verify we have at least 5 arg's passed to the driver */
1291 	if (argc < 5) {
1292 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1293 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1294 			      "Odbc driver requires at least "
1295 			      "4 command line args.");
1296 		return (ISC_R_FAILURE);
1297 	}
1298 
1299 	/* no more than 8 arg's should be passed to the driver */
1300 	if (argc > 8) {
1301 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1302 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1303 			      "Odbc driver cannot accept more than "
1304 			      "7 command line args.");
1305 		return (ISC_R_FAILURE);
1306 	}
1307 
1308 	/* multithreaded build can have multiple DB connections */
1309 #ifdef ISC_PLATFORM_USETHREADS
1310 
1311 	/* check how many db connections we should create */
1312 	dbcount = strtol(argv[1], &endp, 10);
1313 	if (*endp != '\0' || dbcount < 0) {
1314 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1315 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1316 			      "Odbc driver database connection count "
1317 			      "must be positive.");
1318 		return (ISC_R_FAILURE);
1319 	}
1320 
1321 #endif /* ISC_PLATFORM_USETHREADS */
1322 
1323 	/* allocate memory for odbc instance */
1324 	odbc_inst = isc_mem_get(ns_g_mctx, sizeof(odbc_instance_t));
1325 	if (odbc_inst == NULL)
1326 		return (ISC_R_NOMEMORY);
1327 	memset(odbc_inst, 0, sizeof(odbc_instance_t));
1328 
1329 	/* parse connection string and get paramters. */
1330 
1331 	/* get odbc database dsn - required */
1332 	odbc_inst->dsn = (SQLCHAR *) getParameterValue(argv[2],
1333 						       "dsn=");
1334 	if (odbc_inst->dsn == NULL) {
1335 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1336 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1337 			      "odbc driver requires a dns parameter.");
1338 		result = ISC_R_FAILURE;
1339 		goto cleanup;
1340 	}
1341 	/* get odbc database username */
1342 	/* if no username was passed, set odbc_inst.user = NULL; */
1343 	odbc_inst->user = (SQLCHAR *) getParameterValue(argv[2],
1344 							"user=");
1345 
1346 	/* get odbc database password */
1347 	/* if no password was passed, set odbc_inst.pass = NULL; */
1348 	odbc_inst->pass = (SQLCHAR *) getParameterValue(argv[2], "pass=");
1349 
1350 	/* create odbc environment & set environment to ODBC V3 */
1351 	if (odbc_inst->sql_env == NULL) {
1352 		/* create environment handle */
1353 		sqlRes = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE,
1354 					&(odbc_inst->sql_env));
1355 		if (!sqlOK(sqlRes)) {
1356 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1357 				      DNS_LOGMODULE_DLZ, ISC_LOG_INFO,
1358 				      "Odbc driver unable to allocate memory");
1359 			result = ISC_R_NOMEMORY;
1360 			goto cleanup;
1361 		}
1362 		/*set ODBC version = 3 */
1363 		sqlRes = SQLSetEnvAttr(odbc_inst->sql_env,
1364 				       SQL_ATTR_ODBC_VERSION,
1365 				       (void *) SQL_OV_ODBC3, 0);
1366 		if (!sqlOK(sqlRes)) {
1367 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1368 				      DNS_LOGMODULE_DLZ, ISC_LOG_INFO,
1369 				      "Unable to configure ODBC environment");
1370 			result = ISC_R_NOMEMORY;
1371 			goto cleanup;
1372 		}
1373 	}
1374 
1375 #ifdef ISC_PLATFORM_USETHREADS
1376 
1377 	/* allocate memory for database connection list */
1378 	odbc_inst->db = isc_mem_get(ns_g_mctx, sizeof(db_list_t));
1379 	if (odbc_inst->db == NULL) {
1380 		result = ISC_R_NOMEMORY;
1381 		goto cleanup;
1382 	}
1383 
1384 
1385 	/* initialize DB connection list */
1386 	ISC_LIST_INIT(*odbc_inst->db);
1387 
1388 	/* create the appropriate number of database instances (DBI) */
1389 	/* append each new DBI to the end of the list */
1390 	for (i=0; i < dbcount; i++) {
1391 
1392 #endif /* ISC_PLATFORM_USETHREADS */
1393 
1394 		/* how many queries were passed in from config file? */
1395 		switch(argc) {
1396 		case 5:
1397 			result = build_sqldbinstance(ns_g_mctx, NULL, NULL,
1398 						     NULL, argv[3], argv[4],
1399 						     NULL, &db);
1400 			break;
1401 		case 6:
1402 			result = build_sqldbinstance(ns_g_mctx, NULL, NULL,
1403 						     argv[5], argv[3], argv[4],
1404 						     NULL, &db);
1405 			break;
1406 		case 7:
1407 			result = build_sqldbinstance(ns_g_mctx, argv[6], NULL,
1408 						     argv[5], argv[3], argv[4],
1409 						     NULL, &db);
1410 			break;
1411 		case 8:
1412 			result = build_sqldbinstance(ns_g_mctx, argv[6],
1413 						     argv[7], argv[5], argv[3],
1414 						     argv[4], NULL, &db);
1415 			break;
1416 		default:
1417 			/* not really needed, should shut up compiler. */
1418 			result = ISC_R_FAILURE;
1419 		}
1420 
1421 		/* unsuccessful?, log err msg and cleanup. */
1422 		if (result != ISC_R_SUCCESS) {
1423 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1424 				      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1425 				      "Odbc driver could not create "
1426 				      "database instance object.");
1427 			goto cleanup;
1428 		}
1429 
1430 #ifdef ISC_PLATFORM_USETHREADS
1431 
1432 		/* when multithreaded, build a list of DBI's */
1433 		ISC_LINK_INIT(db, link);
1434 		ISC_LIST_APPEND(*odbc_inst->db, db, link);
1435 
1436 #endif
1437 
1438 		result = odbc_connect(odbc_inst, (odbc_db_t **) &(db->dbconn));
1439 
1440 		if (result != ISC_R_SUCCESS) {
1441 
1442 #ifdef ISC_PLATFORM_USETHREADS
1443 
1444 			/*
1445 			 * if multi threaded, let user know which
1446 			 * connection failed.  user could be
1447 			 * attempting to create 10 db connections and
1448 			 * for some reason the db backend only allows
1449 			 * 9.
1450 			 */
1451 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1452 				      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1453 				      "Odbc driver failed to create database "
1454 				      "connection number %u after 3 attempts",
1455 				      i+1);
1456 #else
1457 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1458 				      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
1459 				      "Odbc driver failed to create database "
1460 				      "connection after 3 attempts");
1461 #endif
1462 			goto cleanup;
1463 		}
1464 
1465 #ifdef ISC_PLATFORM_USETHREADS
1466 
1467 		/* set DB = null for next loop through. */
1468 		db = NULL;
1469 
1470 	}	/* end for loop */
1471 
1472 #else
1473 	/* tell odbc_inst about the db connection we just created. */
1474 	odbc_inst->db = db;
1475 
1476 #endif
1477 
1478 	/* set dbdata to the odbc_instance we created. */
1479 	*dbdata = odbc_inst;
1480 
1481 	/* hey, we got through all of that ok, return success. */
1482 	return(ISC_R_SUCCESS);
1483 
1484  cleanup:
1485 
1486 	destroy_odbc_instance(odbc_inst);
1487 
1488 	return result;
1489 }
1490 
1491 /*%
1492  * destroy an instance of the driver.  Remember, only 1 copy of the driver's
1493  * code is ever loaded, the driver has to remember which context it's
1494  * operating in.  This is done via use of the dbdata argument.
1495  * so we really only need to clean it up since we are not using driverarg.
1496  */
1497 
1498 static void
odbc_destroy(void * driverarg,void * dbdata)1499 odbc_destroy(void *driverarg, void *dbdata)
1500 {
1501 	UNUSED(driverarg);
1502 
1503 	destroy_odbc_instance((odbc_instance_t *) dbdata);
1504 }
1505 
1506 
1507 /* pointers to all our runtime methods. */
1508 /* this is used during driver registration */
1509 /* i.e. in dlz_odbc_init below. */
1510 static dns_sdlzmethods_t dlz_odbc_methods = {
1511 	odbc_create,
1512 	odbc_destroy,
1513 	odbc_findzone,
1514 	odbc_lookup,
1515 	odbc_authority,
1516 	odbc_allnodes,
1517 	odbc_allowzonexfr,
1518 	NULL,
1519 	NULL,
1520 	NULL,
1521 	NULL,
1522 	NULL,
1523 	NULL,
1524 	NULL,
1525 };
1526 
1527 /*%
1528  * Wrapper around dns_sdlzregister().
1529  */
1530 isc_result_t
dlz_odbc_init(void)1531 dlz_odbc_init(void) {
1532 	isc_result_t result;
1533 
1534 	/*
1535 	 * Write debugging message to log
1536 	 */
1537 	isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1538 		      DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
1539 		      "Registering DLZ odbc driver.");
1540 
1541 	/*
1542 	 * Driver is always threadsafe.  When multithreaded all
1543 	 * functions use multithreaded code.  When not multithreaded,
1544 	 * all functions can only be entered once, but only 1 thread
1545 	 * of operation is available in Bind.  So everything is still
1546 	 * threadsafe.
1547 	 */
1548 	result = dns_sdlzregister("odbc", &dlz_odbc_methods, NULL,
1549 				  DNS_SDLZFLAG_RELATIVEOWNER |
1550 				  DNS_SDLZFLAG_RELATIVERDATA |
1551 				  DNS_SDLZFLAG_THREADSAFE,
1552 				  ns_g_mctx, &dlz_odbc);
1553 	/* if we can't register the driver, there are big problems. */
1554 	if (result != ISC_R_SUCCESS) {
1555 		UNEXPECTED_ERROR(__FILE__, __LINE__,
1556 				 "dns_sdlzregister() failed: %s",
1557 				 isc_result_totext(result));
1558 		result = ISC_R_UNEXPECTED;
1559 	}
1560 
1561 
1562 	return result;
1563 }
1564 
1565 /*%
1566  * Wrapper around dns_sdlzunregister().
1567  */
1568 void
dlz_odbc_clear(void)1569 dlz_odbc_clear(void) {
1570 
1571 	/*
1572 	 * Write debugging message to log
1573 	 */
1574 	isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1575 		      DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
1576 		      "Unregistering DLZ odbc driver.");
1577 
1578 	/* unregister the driver. */
1579 	if (dlz_odbc != NULL)
1580 		dns_sdlzunregister(&dlz_odbc);
1581 }
1582 
1583 #endif
1584