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