1 /*	$NetBSD: dlz_mysql_driver.c,v 1.6 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_MYSQL
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_mysql_driver.h>
77 
78 #include <mysql.h>
79 
80 static dns_sdlzimplementation_t *dlz_mysql = NULL;
81 
82 #define dbc_search_limit 30
83 #define ALLNODES 1
84 #define ALLOWXFR 2
85 #define AUTHORITY 3
86 #define FINDZONE 4
87 #define COUNTZONE 5
88 #define LOOKUP 6
89 
90 #define safeGet(in) in == NULL ? "" : in
91 
92 /*
93  * Private methods
94  */
95 
96 /*%
97  * Allocates memory for a new string, and then constructs the new
98  * string by "escaping" the input string.  The new string is
99  * safe to be used in queries.  This is necessary because we cannot
100  * be sure of what types of strings are passed to us, and we don't
101  * want special characters in the string causing problems.
102  */
103 
104 static char *
105 mysqldrv_escape_string(MYSQL *mysql, const char *instr) {
106 
107 	char *outstr;
108 	unsigned int len;
109 
110 	if (instr == NULL)
111 		return NULL;
112 
113 	len = strlen(instr);
114 
115 	outstr = isc_mem_allocate(ns_g_mctx ,(2 * len * sizeof(char)) + 1);
116 	if (outstr == NULL)
117 		return NULL;
118 
119 	mysql_real_escape_string(mysql, outstr, instr, len);
120 
121 	return outstr;
122 }
123 
124 /*%
125  * This function is the real core of the driver.   Zone, record
126  * and client strings are passed in (or NULL is passed if the
127  * string is not available).  The type of query we want to run
128  * is indicated by the query flag, and the dbdata object is passed
129  * passed in to.  dbdata really holds a single database instance.
130  * The function will construct and run the query, hopefully getting
131  * a result set.
132  */
133 
134 static isc_result_t
135 mysql_get_resultset(const char *zone, const char *record,
136 		    const char *client, unsigned int query,
137 		    void *dbdata, MYSQL_RES **rs)
138 {
139 	isc_result_t result;
140 	dbinstance_t *dbi = NULL;
141 	char *querystring = NULL;
142 	unsigned int i = 0;
143 	unsigned int j = 0;
144 	int qres = 0;
145 
146 	if (query != COUNTZONE)
147 		REQUIRE(*rs == NULL);
148 	else
149 		REQUIRE(rs == NULL);
150 
151 	/* get db instance / connection */
152 	dbi =  (dbinstance_t *) dbdata;
153 
154 	/* if DBI is null, can't do anything else */
155 	if (dbi == NULL) {
156 		result = ISC_R_FAILURE;
157 		goto cleanup;
158 	}
159 
160 	/* what type of query are we going to run? */
161 	switch(query) {
162 	case ALLNODES:
163 		/*
164 		 * if the query was not passed in from the config file
165 		 * then we can't run it.  return not_implemented, so
166 		 * it's like the code for that operation was never
167 		 * built into the driver.... AHHH flexibility!!!
168 		 */
169 		if (dbi->allnodes_q == NULL) {
170 			result = ISC_R_NOTIMPLEMENTED;
171 			goto cleanup;
172 		}
173 		break;
174 	case ALLOWXFR:
175 		/* same as comments as ALLNODES */
176 		if (dbi->allowxfr_q == NULL) {
177 			result = ISC_R_NOTIMPLEMENTED;
178 			goto cleanup;
179 		}
180 		break;
181 	case AUTHORITY:
182 		/* same as comments as ALLNODES */
183 		if (dbi->authority_q == NULL) {
184 			result = ISC_R_NOTIMPLEMENTED;
185 			goto cleanup;
186 		}
187 		break;
188 	case FINDZONE:
189 		/* this is required.  It's the whole point of DLZ! */
190 		if (dbi->findzone_q == NULL) {
191 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
192 				      DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
193 				      "No query specified for findzone.  "
194 				      "Findzone requires a query");
195 			result = ISC_R_FAILURE;
196 			goto cleanup;
197 		}
198 		break;
199 	case COUNTZONE:
200 		/* same as comments as ALLNODES */
201 		if (dbi->countzone_q == NULL) {
202 			result = ISC_R_NOTIMPLEMENTED;
203 			goto cleanup;
204 		}
205 		break;
206 	case LOOKUP:
207 		/* this is required.  It's also a major point of DLZ! */
208 		if (dbi->lookup_q == NULL) {
209 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
210 				      DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
211 				      "No query specified for lookup.  "
212 				      "Lookup requires a query");
213 			result = ISC_R_FAILURE;
214 			goto cleanup;
215 		}
216 		break;
217 	default:
218 		/*
219 		 * this should never happen.  If it does, the code is
220 		 * screwed up!
221 		 */
222 		UNEXPECTED_ERROR(__FILE__, __LINE__,
223 				 "Incorrect query flag passed to "
224 				 "mysql_get_resultset");
225 		result = ISC_R_UNEXPECTED;
226 		goto cleanup;
227 	}
228 
229 
230 	/*
231 	 * was a zone string passed?  If so, make it safe for use in
232 	 * queries.
233 	 */
234 	if (zone != NULL) {
235 		dbi->zone = mysqldrv_escape_string((MYSQL *) dbi->dbconn,
236 						   zone);
237 		if (dbi->zone == NULL) {
238 			result = ISC_R_NOMEMORY;
239 			goto cleanup;
240 		}
241 	} else {	/* no string passed, set the string pointer to NULL */
242 		dbi->zone = NULL;
243 	}
244 
245 	/*
246 	 * was a record string passed?  If so, make it safe for use in
247 	 * queries.
248 	 */
249 	if (record != NULL) {
250 		dbi->record = mysqldrv_escape_string((MYSQL *) dbi->dbconn,
251 						     record);
252 		if (dbi->record == NULL) {
253 			result = ISC_R_NOMEMORY;
254 			goto cleanup;
255 		}
256 	} else {	/* no string passed, set the string pointer to NULL */
257 		dbi->record = NULL;
258 	}
259 
260 	/*
261 	 * was a client string passed?  If so, make it safe for use in
262 	 * queries.
263 	 */
264 	if (client != NULL) {
265 		dbi->client = mysqldrv_escape_string((MYSQL *) dbi->dbconn,
266 						     client);
267 		if (dbi->client == NULL) {
268 			result = ISC_R_NOMEMORY;
269 			goto cleanup;
270 		}
271 	} else {	/* no string passed, set the string pointer to NULL */
272 		dbi->client = NULL;
273 	}
274 
275 	/*
276 	 * what type of query are we going to run?  this time we build
277 	 * the actual query to run.
278 	 */
279 	switch(query) {
280 	case ALLNODES:
281 		querystring = build_querystring(ns_g_mctx, dbi->allnodes_q);
282 		break;
283 	case ALLOWXFR:
284 		querystring = build_querystring(ns_g_mctx, dbi->allowxfr_q);
285 		break;
286 	case AUTHORITY:
287 		querystring = build_querystring(ns_g_mctx, dbi->authority_q);
288 		break;
289 	case FINDZONE:
290 		querystring = build_querystring(ns_g_mctx, dbi->findzone_q);
291 		break;
292 	case COUNTZONE:
293 		querystring = build_querystring(ns_g_mctx, dbi->countzone_q);
294 		break;
295 	case LOOKUP:
296 		querystring = build_querystring(ns_g_mctx, dbi->lookup_q);
297 		break;
298 	default:
299 		/*
300 		 * this should never happen.  If it does, the code is
301 		 * screwed up!
302 		 */
303 		UNEXPECTED_ERROR(__FILE__, __LINE__,
304 				 "Incorrect query flag passed to "
305 				 "mysql_get_resultset");
306 		result = ISC_R_UNEXPECTED;
307 		goto cleanup;
308 	}
309 
310 	/* if the querystring is null, Bummer, outta RAM.  UPGRADE TIME!!!   */
311 	if (querystring == NULL) {
312 		result = ISC_R_NOMEMORY;
313 		goto cleanup;
314 	}
315 
316 	/*
317 	 * output the full query string during debug so we can see
318 	 * what lame error the query has.
319 	 */
320 	isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
321 		      DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(1),
322 		      "\nQuery String: %s\n", querystring);
323 
324 	/* attempt query up to 3 times. */
325 	for (i=0; i < 3; i++) {
326 		qres = mysql_query((MYSQL *) dbi->dbconn, querystring);
327 		if (qres == 0)
328 			break;
329 		for (j=0; mysql_ping((MYSQL *) dbi->dbconn) != 0 && j < 4; j++)
330 			;
331 	}
332 
333 	if (qres == 0) {
334 		result = ISC_R_SUCCESS;
335 		if (query != COUNTZONE) {
336 			*rs = mysql_store_result((MYSQL *) dbi->dbconn);
337 			if (*rs == NULL)
338 				result = ISC_R_FAILURE;
339 		}
340 	} else {
341 		result = ISC_R_FAILURE;
342 	}
343 
344 
345  cleanup:
346 	/* it's always good to cleanup after yourself */
347 
348 	/* if we couldn't even get DBI, just return NULL */
349 	if (dbi == NULL)
350 		return ISC_R_FAILURE;
351 
352 	/* free dbi->zone string */
353 	if (dbi->zone != NULL)
354 		isc_mem_free(ns_g_mctx, dbi->zone);
355 
356 	/* free dbi->record string */
357 	if (dbi->record != NULL)
358 		isc_mem_free(ns_g_mctx, dbi->record);
359 
360 	/* free dbi->client string */
361 	if (dbi->client != NULL)
362 		isc_mem_free(ns_g_mctx, dbi->client);
363 
364 	/* release query string */
365 	if (querystring  != NULL)
366 		isc_mem_free(ns_g_mctx, querystring);
367 
368 	/* return result */
369 	return result;
370 }
371 
372 /*%
373  * The processing of result sets for lookup and authority are
374  * exactly the same.  So that functionality has been moved
375  * into this function to minimize code.
376  */
377 
378 static isc_result_t
379 mysql_process_rs(dns_sdlzlookup_t *lookup, MYSQL_RES *rs)
380 {
381 	isc_result_t result = ISC_R_NOTFOUND;
382 	MYSQL_ROW row;
383 	unsigned int fields;
384 	unsigned int j;
385 	unsigned int len;
386 	char *tmpString;
387 	char *endp;
388 	int ttl;
389 
390 	row = mysql_fetch_row(rs);	/* get a row from the result set */
391 	fields = mysql_num_fields(rs);	/* how many columns in result set */
392 	while (row != NULL) {
393 		switch(fields) {
394 		case 1:
395 			/*
396 			 * one column in rs, it's the data field.  use
397 			 * default type of A record, and default TTL
398 			 * of 86400
399 			 */
400 			result = dns_sdlz_putrr(lookup, "a", 86400,
401 						safeGet(row[0]));
402 			break;
403 		case 2:
404 			/*
405 			 * two columns, data field, and data type.
406 			 * use default TTL of 86400.
407 			 */
408 			result = dns_sdlz_putrr(lookup, safeGet(row[0]), 86400,
409 						safeGet(row[1]));
410 			break;
411 		case 3:
412 			/*
413 			 * three columns, all data no defaults.
414 			 * convert text to int, make sure it worked
415 			 * right.
416 			 */
417 			ttl = strtol(safeGet(row[0]), &endp, 10);
418 			if (*endp != '\0' || ttl < 0) {
419 				isc_log_write(dns_lctx,
420 					      DNS_LOGCATEGORY_DATABASE,
421 					      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
422 					      "mysql driver ttl must be "
423 					      "a postive number");
424 			}
425 			result = dns_sdlz_putrr(lookup, safeGet(row[1]), ttl,
426 						safeGet(row[2]));
427 			break;
428 		default:
429 			/*
430 			 * more than 3 fields, concatenate the last
431 			 * ones together.  figure out how long to make
432 			 * string.
433 			 */
434 			for (j=2, len=0; j < fields; j++) {
435 				len += strlen(safeGet(row[j])) + 1;
436 			}
437 			/*
438 			 * allocate string memory, allow for NULL to
439 			 * term string
440 			 */
441 			tmpString = isc_mem_allocate(ns_g_mctx, len + 1);
442 			if (tmpString == NULL) {
443 				/* major bummer, need more ram */
444 				isc_log_write(dns_lctx,
445 					      DNS_LOGCATEGORY_DATABASE,
446 					      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
447 					      "mysql driver unable "
448 					      "to allocate memory for "
449 					      "temporary string");
450 				mysql_free_result(rs);
451 				return (ISC_R_FAILURE);	/* Yeah, I'd say! */
452 			}
453 			/* copy field to tmpString */
454 			strcpy(tmpString, safeGet(row[2]));
455 
456 
457 			/*
458 			 * concat the rest of fields together, space
459 			 * between each one.
460 			 */
461 			for (j=3; j < fields; j++) {
462 				strcat(tmpString, " ");
463 				strcat(tmpString, safeGet(row[j]));
464 			}
465 			/* convert text to int, make sure it worked right */
466 			ttl = strtol(safeGet(row[0]), &endp, 10);
467 			if (*endp != '\0' || ttl < 0) {
468 				isc_log_write(dns_lctx,
469 					      DNS_LOGCATEGORY_DATABASE,
470 					      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
471 					      "mysql driver ttl must be "
472 					      "a postive number");
473 			}
474 			/* ok, now tell Bind about it. */
475 			result = dns_sdlz_putrr(lookup, safeGet(row[1]),
476 						ttl, tmpString);
477 			/* done, get rid of this thing. */
478 			isc_mem_free(ns_g_mctx, tmpString);
479 		}
480 		/* I sure hope we were successful */
481 		if (result != ISC_R_SUCCESS) {
482 			/* nope, get rid of the Result set, and log a msg */
483 			mysql_free_result(rs);
484 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
485 				      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
486 				      "dns_sdlz_putrr returned error. "
487 				      "Error code was: %s",
488 				      isc_result_totext(result));
489 			return (ISC_R_FAILURE);
490 		}
491 		row = mysql_fetch_row(rs);	/* get next row */
492 	}
493 
494 	/* free result set memory */
495 	mysql_free_result(rs);
496 
497 	/* return result code */
498 	return result;
499 }
500 
501 /*
502  * SDLZ interface methods
503  */
504 
505 /*% determine if the zone is supported by (in) the database */
506 
507 static isc_result_t
508 mysql_findzone(void *driverarg, void *dbdata, const char *name,
509 	       dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo)
510 {
511 	isc_result_t result;
512 	MYSQL_RES *rs = NULL;
513 	my_ulonglong rows;
514 
515 	UNUSED(driverarg);
516 	UNUSED(methods);
517 	UNUSED(clientinfo);
518 
519 	/* run the query and get the result set from the database. */
520 	result = mysql_get_resultset(name, NULL, NULL, FINDZONE, dbdata, &rs);
521 	/* if we didn't get a result set, log an err msg. */
522 	if (result != ISC_R_SUCCESS || rs == NULL) {
523 		if (rs != NULL)
524 			mysql_free_result(rs);
525 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
526 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
527 			      "mysql driver unable to return "
528 			      "result set for findzone query");
529 		return (ISC_R_FAILURE);
530 	}
531 	/* count how many rows in result set */
532 	rows = mysql_num_rows(rs);
533 	/* get rid of result set, we are done with it. */
534 	mysql_free_result(rs);
535 
536 	/* if we returned any rows, zone is supported. */
537 	if (rows > 0) {
538 		mysql_get_resultset(name, NULL, NULL, COUNTZONE, dbdata, NULL);
539 		return (ISC_R_SUCCESS);
540 	}
541 
542 	/* no rows returned, zone is not supported. */
543 	return (ISC_R_NOTFOUND);
544 }
545 
546 /*% Determine if the client is allowed to perform a zone transfer */
547 static isc_result_t
548 mysql_allowzonexfr(void *driverarg, void *dbdata, const char *name,
549 		   const char *client)
550 {
551 	isc_result_t result;
552 	MYSQL_RES *rs = NULL;
553 	my_ulonglong rows;
554 
555 	UNUSED(driverarg);
556 
557 	/* first check if the zone is supported by the database. */
558 	result = mysql_findzone(driverarg, dbdata, name, NULL, NULL);
559 	if (result != ISC_R_SUCCESS)
560 		return (ISC_R_NOTFOUND);
561 
562 	/*
563 	 * if we get to this point we know the zone is supported by
564 	 * the database the only questions now are is the zone
565 	 * transfer is allowed for this client and did the config file
566 	 * have an allow zone xfr query.
567 	 *
568 	 * Run our query, and get a result set from the database.
569 	 */
570 	result = mysql_get_resultset(name, NULL, client, ALLOWXFR,
571 				     dbdata, &rs);
572 	/* if we get "not implemented", send it along. */
573 	if (result == ISC_R_NOTIMPLEMENTED)
574 		return result;
575 	/* if we didn't get a result set, log an err msg. */
576 	if (result != ISC_R_SUCCESS || rs == NULL) {
577 		if (rs != NULL)
578 			mysql_free_result(rs);
579 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
580 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
581 			      "mysql driver unable to return "
582 			      "result set for allow xfr query");
583 		return (ISC_R_FAILURE);
584 	}
585 	/* count how many rows in result set */
586 	rows = mysql_num_rows(rs);
587 	/* get rid of result set, we are done with it. */
588 	mysql_free_result(rs);
589 
590 	/* if we returned any rows, zone xfr is allowed. */
591 	if (rows > 0)
592 		return (ISC_R_SUCCESS);
593 
594 	/* no rows returned, zone xfr not allowed */
595 	return (ISC_R_NOPERM);
596 }
597 
598 /*%
599  * If the client is allowed to perform a zone transfer, the next order of
600  * business is to get all the nodes in the zone, so bind can respond to the
601  * query.
602  */
603 static isc_result_t
604 mysql_allnodes(const char *zone, void *driverarg, void *dbdata,
605 	       dns_sdlzallnodes_t *allnodes)
606 {
607 	isc_result_t result;
608 	MYSQL_RES *rs = NULL;
609 	MYSQL_ROW row;
610 	unsigned int fields;
611 	unsigned int j;
612 	unsigned int len;
613 	char *tmpString;
614 	char *endp;
615 	int ttl;
616 
617 	UNUSED(driverarg);
618 
619 	/* run the query and get the result set from the database. */
620 	result = mysql_get_resultset(zone, NULL, NULL, ALLNODES, dbdata, &rs);
621 	/* if we get "not implemented", send it along */
622 	if (result == ISC_R_NOTIMPLEMENTED)
623 		return result;
624 	/* if we didn't get a result set, log an err msg. */
625 	if (result != ISC_R_SUCCESS) {
626 		if (rs != NULL)
627 			mysql_free_result(rs);
628 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
629 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
630 			      "mysql driver unable to return "
631 			      "result set for all nodes query");
632 		return (ISC_R_FAILURE);
633 	}
634 
635 	result = ISC_R_NOTFOUND;
636 
637 	row = mysql_fetch_row(rs);	/* get a row from the result set */
638 	fields = mysql_num_fields(rs);	/* how many columns in result set */
639 	while (row != NULL) {
640 		if (fields < 4) {	/* gotta have at least 4 columns */
641 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
642 				      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
643 				      "mysql driver too few fields returned "
644 				      "by all nodes query");
645 		}
646 		/* convert text to int, make sure it worked right  */
647 		ttl = strtol(safeGet(row[0]), &endp, 10);
648 		if (*endp != '\0' || ttl < 0) {
649 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
650 				      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
651 				      "mysql driver ttl must be "
652 				      "a postive number");
653 		}
654 		if (fields == 4) {
655 			/* tell Bind about it. */
656 			result = dns_sdlz_putnamedrr(allnodes, safeGet(row[2]),
657 						     safeGet(row[1]), ttl,
658 						     safeGet(row[3]));
659 		} else {
660 			/*
661 			 * more than 4 fields, concatenate the last
662 			 * ones together.  figure out how long to make
663 			 * string.
664 			 */
665 			for (j=3, len=0; j < fields; j++) {
666 				len += strlen(safeGet(row[j])) + 1;
667 			}
668 			/* allocate memory, allow for NULL to term string */
669 			tmpString = isc_mem_allocate(ns_g_mctx, len + 1);
670 			if (tmpString == NULL) {	/* we need more ram. */
671 				isc_log_write(dns_lctx,
672 					      DNS_LOGCATEGORY_DATABASE,
673 					      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
674 					      "mysql driver unable "
675 					      "to allocate memory for "
676 					      "temporary string");
677 				mysql_free_result(rs);
678 				return (ISC_R_FAILURE);
679 			}
680 			/* copy this field to tmpString */
681 			strcpy(tmpString, safeGet(row[3]));
682 			/* concatonate the rest, with spaces between */
683 			for (j=4; j < fields; j++) {
684 				strcat(tmpString, " ");
685 				strcat(tmpString, safeGet(row[j]));
686 			}
687 			/* tell Bind about it. */
688 			result = dns_sdlz_putnamedrr(allnodes, safeGet(row[2]),
689 						     safeGet(row[1]),
690 						     ttl, tmpString);
691 			isc_mem_free(ns_g_mctx, tmpString);
692 		}
693 		/* if we weren't successful, log err msg */
694 		if (result != ISC_R_SUCCESS) {
695 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
696 				      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
697 				      "dns_sdlz_putnamedrr returned error. "
698 				      "Error code was: %s",
699 				      isc_result_totext(result));
700 			result = ISC_R_FAILURE;
701 			break;
702 		}
703 		/* get next row from the result set */
704 		row = mysql_fetch_row(rs);
705 	}
706 
707 	/* free result set memory */
708 	mysql_free_result(rs);
709 
710 	return result;
711 }
712 
713 /*% if the lookup function does not return SOA or NS records for the zone,
714  * use this function to get that information for Bind.
715  */
716 
717 static isc_result_t
718 mysql_authority(const char *zone, void *driverarg, void *dbdata,
719 		dns_sdlzlookup_t *lookup)
720 {
721 	isc_result_t result;
722 	MYSQL_RES *rs = NULL;
723 
724 	UNUSED(driverarg);
725 
726 	/* run the query and get the result set from the database. */
727 	result = mysql_get_resultset(zone, NULL, NULL, AUTHORITY, dbdata, &rs);
728 	/* if we get "not implemented", send it along */
729 	if (result == ISC_R_NOTIMPLEMENTED)
730 		return result;
731 	/* if we didn't get a result set, log an err msg. */
732 	if (result != ISC_R_SUCCESS) {
733 		if (rs != NULL)
734 			mysql_free_result(rs);
735 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
736 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
737 			      "mysql driver unable to return "
738 			      "result set for authority query");
739 		return (ISC_R_FAILURE);
740 	}
741 	/*
742 	 * lookup and authority result sets are processed in the same
743 	 * manner mysql_process_rs does the job for both functions.
744 	 */
745 	return mysql_process_rs(lookup, rs);
746 }
747 
748 /*% if zone is supported, lookup up a (or multiple) record(s) in it */
749 static isc_result_t
750 mysql_lookup(const char *zone, const char *name, void *driverarg,
751 	     void *dbdata, dns_sdlzlookup_t *lookup,
752 	     dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo)
753 {
754 	isc_result_t result;
755 	MYSQL_RES *rs = NULL;
756 
757 	UNUSED(driverarg);
758 	UNUSED(methods);
759 	UNUSED(clientinfo);
760 
761 	/* run the query and get the result set from the database. */
762 	result = mysql_get_resultset(zone, name, NULL, LOOKUP, dbdata, &rs);
763 	/* if we didn't get a result set, log an err msg. */
764 	if (result != ISC_R_SUCCESS) {
765 		if (rs != NULL)
766 			mysql_free_result(rs);
767 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
768 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
769 			      "mysql driver unable to return "
770 			      "result set for lookup query");
771 		return (ISC_R_FAILURE);
772 	}
773 	/*
774 	 * lookup and authority result sets are processed in the same manner
775 	 * mysql_process_rs does the job for both functions.
776 	 */
777 	return mysql_process_rs(lookup, rs);
778 }
779 
780 /*%
781  * create an instance of the driver.  Remember, only 1 copy of the driver's
782  * code is ever loaded, the driver has to remember which context it's
783  * operating in.  This is done via use of the dbdata argument which is
784  * passed into all query functions.
785  */
786 static isc_result_t
787 mysql_create(const char *dlzname, unsigned int argc, char *argv[],
788 	     void *driverarg, void **dbdata)
789 {
790 	isc_result_t result;
791 	dbinstance_t *dbi = NULL;
792 	char *tmp = NULL;
793 	char *dbname = NULL;
794 	char *host = NULL;
795 	char *user = NULL;
796 	char *pass = NULL;
797 	char *socket = NULL;
798 	int port;
799 	MYSQL *dbc;
800 	char *endp;
801 	int j;
802 	unsigned int flags = 0;
803 #if MYSQL_VERSION_ID >= 50000
804         my_bool auto_reconnect = 1;
805 #endif
806 
807 	UNUSED(driverarg);
808 	UNUSED(dlzname);
809 
810 	/* verify we have at least 4 arg's passed to the driver */
811 	if (argc < 4) {
812 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
813 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
814 			      "mysql driver requires "
815 			      "at least 4 command line args.");
816 		return (ISC_R_FAILURE);
817 	}
818 
819 	/* no more than 8 arg's should be passed to the driver */
820 	if (argc > 8) {
821 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
822 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
823 			      "mysql driver cannot accept "
824 			      "more than 7 command line args.");
825 		return (ISC_R_FAILURE);
826 	}
827 
828 	/* parse connection string and get paramters. */
829 
830 	/* get db name - required */
831 	dbname = getParameterValue(argv[1], "dbname=");
832 	if (dbname == NULL) {
833 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
834 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
835 			      "mysql driver requires a dbname parameter.");
836 		result = ISC_R_FAILURE;
837 		goto full_cleanup;
838 	}
839 
840 	/* get db port.  Not required, but must be > 0 if specified */
841 	tmp = getParameterValue(argv[1], "port=");
842 	if (tmp == NULL) {
843 		port = 0;
844 	} else {
845 		port = strtol(tmp, &endp, 10);
846 		if (*endp != '\0' || port < 0) {
847 			isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
848 				      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
849 				      "Mysql driver port "
850 				      "must be a positive number.");
851 			isc_mem_free(ns_g_mctx, tmp);
852 			result = ISC_R_FAILURE;
853 			goto full_cleanup;
854 		}
855 		isc_mem_free(ns_g_mctx, tmp);
856 	}
857 
858 	/* how many queries were passed in from config file? */
859 	switch(argc) {
860 	case 4:
861 		result = build_sqldbinstance(ns_g_mctx, NULL, NULL, NULL,
862 					     argv[2], argv[3], NULL, &dbi);
863 		break;
864 	case 5:
865 		result = build_sqldbinstance(ns_g_mctx, NULL, NULL, argv[4],
866 					     argv[2], argv[3], NULL, &dbi);
867 		break;
868 	case 6:
869 		result = build_sqldbinstance(ns_g_mctx, argv[5], NULL, argv[4],
870 					     argv[2], argv[3], NULL, &dbi);
871 		break;
872 	case 7:
873 		result = build_sqldbinstance(ns_g_mctx, argv[5],
874 					     argv[6], argv[4],
875 					     argv[2], argv[3], NULL, &dbi);
876 		break;
877 	case 8:
878 		result = build_sqldbinstance(ns_g_mctx, argv[5],
879 					     argv[6], argv[4],
880 					     argv[2], argv[3], argv[7], &dbi);
881 		break;
882 	default:
883 		/* not really needed, should shut up compiler. */
884 		result = ISC_R_FAILURE;
885 	}
886 
887 	/* unsuccessful?, log err msg and cleanup. */
888 	if (result != ISC_R_SUCCESS) {
889 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
890 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
891 			      "mysql driver could not create "
892 			      "database instance object.");
893 		result = ISC_R_FAILURE;
894 		goto cleanup;
895 	}
896 
897 	/* create and set db connection */
898 	dbi->dbconn = mysql_init(NULL);
899 
900 	/* if db connection cannot be created, log err msg and cleanup. */
901 	if (dbi->dbconn == NULL) {
902 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
903 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
904 			      "mysql driver could not allocate "
905 			      "memory for database connection");
906 		result = ISC_R_FAILURE;
907 		goto full_cleanup;
908 	}
909 
910 	tmp = getParameterValue(argv[1], "compress=");
911 	if (tmp != NULL) {
912 		if (strcasecmp(tmp, "true") == 0)
913 			flags = CLIENT_COMPRESS;
914 		isc_mem_free(ns_g_mctx, tmp);
915 	}
916 
917 	tmp = getParameterValue(argv[1], "ssl=");
918 	if (tmp != NULL) {
919 		if (strcasecmp(tmp, "true") == 0)
920 			flags = flags | CLIENT_SSL;
921 		isc_mem_free(ns_g_mctx, tmp);
922 	}
923 
924 	tmp = getParameterValue(argv[1], "space=");
925 	if (tmp != NULL) {
926 		if (strcasecmp(tmp, "ignore") == 0)
927 			flags = flags | CLIENT_IGNORE_SPACE;
928 		isc_mem_free(ns_g_mctx, tmp);
929 	}
930 
931 	dbc = NULL;
932 	host = getParameterValue(argv[1], "host=");
933 	user = getParameterValue(argv[1], "user=");
934 	pass = getParameterValue(argv[1], "pass=");
935 	socket = getParameterValue(argv[1], "socket=");
936 
937 #if MYSQL_VERSION_ID >= 50000
938 	/* enable automatic reconnection. */
939         if (mysql_options((MYSQL *) dbi->dbconn, MYSQL_OPT_RECONNECT,
940 			  &auto_reconnect) != 0) {
941 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
942 			      DNS_LOGMODULE_DLZ, ISC_LOG_WARNING,
943 			      "mysql driver failed to set "
944 			      "MYSQL_OPT_RECONNECT option, continuing");
945 	}
946 #endif
947 
948 	for (j=0; dbc == NULL && j < 4; j++)
949 		dbc = mysql_real_connect((MYSQL *) dbi->dbconn, host,
950 					 user, pass, dbname, port, socket,
951 					 flags);
952 
953 	/* let user know if we couldn't connect. */
954 	if (dbc == NULL) {
955 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
956 			      DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
957 			      "mysql driver failed to create "
958 			      "database connection after 4 attempts");
959 		result = ISC_R_FAILURE;
960 		goto full_cleanup;
961 	}
962 
963 	/* return db connection via dbdata */
964 	*dbdata = dbi;
965 
966 	result = ISC_R_SUCCESS;
967 	goto cleanup;
968 
969  full_cleanup:
970 
971 	if (dbi != NULL)
972 		destroy_sqldbinstance(dbi);
973 
974  cleanup:
975 
976 	if (dbname != NULL)
977 		isc_mem_free(ns_g_mctx, dbname);
978 	if (host != NULL)
979 		isc_mem_free(ns_g_mctx, host);
980 	if (user != NULL)
981 		isc_mem_free(ns_g_mctx, user);
982 	if (pass != NULL)
983 		isc_mem_free(ns_g_mctx, pass);
984 	if (socket != NULL)
985 		isc_mem_free(ns_g_mctx, socket);
986 
987 
988 	return result;
989 }
990 
991 /*%
992  * destroy the driver.  Remember, only 1 copy of the driver's
993  * code is ever loaded, the driver has to remember which context it's
994  * operating in.  This is done via use of the dbdata argument.
995  * so we really only need to clean it up since we are not using driverarg.
996  */
997 
998 static void
999 mysql_destroy(void *driverarg, void *dbdata)
1000 {
1001 	dbinstance_t *dbi;
1002 
1003 	UNUSED(driverarg);
1004 
1005 	dbi = (dbinstance_t *) dbdata;
1006 
1007 	/* release DB connection */
1008 	if (dbi->dbconn != NULL)
1009 		mysql_close((MYSQL *) dbi->dbconn);
1010 
1011 	/* destroy DB instance */
1012 	destroy_sqldbinstance(dbi);
1013 }
1014 
1015 /* pointers to all our runtime methods. */
1016 /* this is used during driver registration */
1017 /* i.e. in dlz_mysql_init below. */
1018 static dns_sdlzmethods_t dlz_mysql_methods = {
1019 	mysql_create,
1020 	mysql_destroy,
1021 	mysql_findzone,
1022 	mysql_lookup,
1023 	mysql_authority,
1024 	mysql_allnodes,
1025 	mysql_allowzonexfr,
1026 	NULL,
1027 	NULL,
1028 	NULL,
1029 	NULL,
1030 	NULL,
1031 	NULL,
1032 	NULL,
1033 };
1034 
1035 /*%
1036  * Wrapper around dns_sdlzregister().
1037  */
1038 isc_result_t
1039 dlz_mysql_init(void) {
1040 	isc_result_t result;
1041 
1042 	/*
1043 	 * Write debugging message to log
1044 	 */
1045 	isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1046 		      DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
1047 		      "Registering DLZ mysql driver.");
1048 
1049 	/* Driver is always threadsafe.  Because of the way MySQL handles
1050          * threads the MySQL driver can only be used when bind is run single
1051          * threaded.  Using MySQL with Bind running multi-threaded is not
1052          * allowed.  When using the MySQL driver "-n1" should always be
1053          * passed to Bind to guarantee single threaded operation.
1054 	 */
1055 	result = dns_sdlzregister("mysql", &dlz_mysql_methods, NULL,
1056 				  DNS_SDLZFLAG_RELATIVEOWNER |
1057 				  DNS_SDLZFLAG_RELATIVERDATA |
1058 				  DNS_SDLZFLAG_THREADSAFE,
1059 				  ns_g_mctx, &dlz_mysql);
1060 	/* if we can't register the driver, there are big problems. */
1061 	if (result != ISC_R_SUCCESS) {
1062 		UNEXPECTED_ERROR(__FILE__, __LINE__,
1063 				 "dns_sdlzregister() failed: %s",
1064 				 isc_result_totext(result));
1065 		result = ISC_R_UNEXPECTED;
1066 	}
1067 
1068 
1069 	return result;
1070 }
1071 
1072 /*%
1073  * Wrapper around dns_sdlzunregister().
1074  */
1075 void
1076 dlz_mysql_clear(void) {
1077 
1078 	/*
1079 	 * Write debugging message to log
1080 	 */
1081 	isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
1082 		      DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
1083 		      "Unregistering DLZ mysql driver.");
1084 
1085 	/* unregister the driver. */
1086 	if (dlz_mysql != NULL)
1087 		dns_sdlzunregister(&dlz_mysql);
1088 }
1089 
1090 #endif
1091