xref: /minix3/external/bsd/bind/dist/contrib/dlz/modules/mysql/dlz_mysql_dynamic.c (revision 00b67f09dd46474d133c95011a48590a8e8f94c7)
1 /*	$NetBSD: dlz_mysql_dynamic.c,v 1.1.1.3 2014/12/10 03:34:31 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  * Copyright (C) 2013  Internet Systems Consortium.
41  *
42  * Permission to use, copy, modify, and distribute this software for any
43  * purpose with or without fee is hereby granted, provided that the above
44  * copyright notice and this permission notice appear in all copies.
45  *
46  * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
47  * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
48  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
49  * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
50  * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
51  * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
52  * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
53  * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
54  */
55 
56 /*
57  * This provides the externally loadable MySQL DLZ module, without
58  * update support
59  */
60 
61 #include <stdio.h>
62 #include <string.h>
63 #include <stdarg.h>
64 #include <stdint.h>
65 #include <stdlib.h>
66 
67 #include <dlz_minimal.h>
68 #include <dlz_list.h>
69 #include <dlz_dbi.h>
70 #include <dlz_pthread.h>
71 
72 #include <mysql/mysql.h>
73 
74 #define dbc_search_limit 30
75 #define ALLNODES 1
76 #define ALLOWXFR 2
77 #define AUTHORITY 3
78 #define FINDZONE 4
79 #define COUNTZONE 5
80 #define LOOKUP 6
81 
82 #define safeGet(in) in == NULL ? "" : in
83 
84 /*%
85  * Structure to hold everthing needed by this "instance" of the MySQL
86  * module remember, the module code is only loaded once, but may have
87  * many separate instances.
88  */
89 typedef struct {
90 #if PTHREADS
91 	db_list_t    *db; /*%< handle to a list of DB */
92 	int dbcount;
93 #else
94 	dbinstance_t *db; /*%< handle to DB */
95 #endif
96 
97 	unsigned int flags;
98 	char *dbname;
99 	char *host;
100 	char *user;
101 	char *pass;
102 	char *socket;
103 	int port;
104 
105 	/* Helper functions from the dlz_dlopen driver */
106 	log_t *log;
107 	dns_sdlz_putrr_t *putrr;
108 	dns_sdlz_putnamedrr_t *putnamedrr;
109 	dns_dlz_writeablezone_t *writeable_zone;
110 } mysql_instance_t;
111 
112 /* forward references */
113 isc_result_t
114 dlz_findzonedb(void *dbdata, const char *name,
115 	       dns_clientinfomethods_t *methods,
116 	       dns_clientinfo_t *clientinfo);
117 
118 void
119 dlz_destroy(void *dbdata);
120 
121 static void
122 b9_add_helper(mysql_instance_t *db, const char *helper_name, void *ptr);
123 
124 /*
125  * Private methods
126  */
127 
128 void
mysql_destroy(dbinstance_t * db)129 mysql_destroy(dbinstance_t *db) {
130 	/* release DB connection */
131 	if (db->dbconn != NULL)
132 		mysql_close((MYSQL *) db->dbconn);
133 
134 	/* destroy DB instance */
135 	destroy_dbinstance(db);
136 }
137 
138 #if PTHREADS
139 /*%
140  * Properly cleans up a list of database instances.
141  * This function is only used when the module is compiled for
142  * multithreaded operation.
143  */
144 static void
mysql_destroy_dblist(db_list_t * dblist)145 mysql_destroy_dblist(db_list_t *dblist) {
146 	dbinstance_t *ndbi = NULL;
147 	dbinstance_t *dbi = NULL;
148 
149 	ndbi = DLZ_LIST_HEAD(*dblist);
150 	while (ndbi != NULL) {
151 		dbi = ndbi;
152 		ndbi = DLZ_LIST_NEXT(dbi, link);
153 
154 		mysql_destroy(dbi);
155 	}
156 
157 	/* release memory for the list structure */
158 	free(dblist);
159 }
160 
161 /*%
162  * Loops through the list of DB instances, attempting to lock
163  * on the mutex.  If successful, the DBI is reserved for use
164  * and the thread can perform queries against the database.
165  * If the lock fails, the next one in the list is tried.
166  * looping continues until a lock is obtained, or until
167  * the list has been searched dbc_search_limit times.
168  * This function is only used when the module is compiled for
169  * multithreaded operation.
170  */
171 static dbinstance_t *
mysql_find_avail_conn(mysql_instance_t * mysql)172 mysql_find_avail_conn(mysql_instance_t *mysql) {
173 	dbinstance_t *dbi = NULL, *head;
174 	int count = 0;
175 
176 	/* get top of list */
177 	head = dbi = DLZ_LIST_HEAD(*(mysql->db));
178 
179 	/* loop through list */
180 	while (count < dbc_search_limit) {
181 		/* try to lock on the mutex */
182 		if (dlz_mutex_trylock(&dbi->lock) == 0)
183 			return (dbi); /* success, return the DBI for use. */
184 
185 		/* not successful, keep trying */
186 		dbi = DLZ_LIST_NEXT(dbi, link);
187 
188 		/* check to see if we have gone to the top of the list. */
189 		if (dbi == NULL) {
190 			count++;
191 			dbi = head;
192 		}
193 	}
194 
195 	mysql->log(ISC_LOG_INFO,
196 		   "MySQL module unable to find available connection "
197 		   "after searching %d times", count);
198 	return (NULL);
199 }
200 #endif /* PTHREADS */
201 
202 /*%
203  * Allocates memory for a new string, and then constructs the new
204  * string by "escaping" the input string.  The new string is
205  * safe to be used in queries.  This is necessary because we cannot
206  * be sure of what types of strings are passed to us, and we don't
207  * want special characters in the string causing problems.
208  */
209 static char *
mysqldrv_escape_string(MYSQL * mysql,const char * instr)210 mysqldrv_escape_string(MYSQL *mysql, const char *instr) {
211 
212 	char *outstr;
213 	unsigned int len;
214 
215 	if (instr == NULL)
216 		return (NULL);
217 
218 	len = strlen(instr);
219 	outstr = malloc((2 * len * sizeof(char)) + 1);
220 	if (outstr == NULL)
221 		return (NULL);
222 
223 	mysql_real_escape_string(mysql, outstr, instr, len);
224 
225 	return (outstr);
226 }
227 
228 /*%
229  * This function is the real core of the module.   Zone, record
230  * and client strings are passed in (or NULL is passed if the
231  * string is not available).  The type of query we want to run
232  * is indicated by the query flag, and the dbdata object is passed
233  * passed in to.  dbdata really holds a single database instance.
234  * The function will construct and run the query, hopefully getting
235  * a result set.
236  */
237 static isc_result_t
mysql_get_resultset(const char * zone,const char * record,const char * client,unsigned int query,void * dbdata,MYSQL_RES ** rs)238 mysql_get_resultset(const char *zone, const char *record,
239 		    const char *client, unsigned int query,
240 		    void *dbdata, MYSQL_RES **rs)
241 {
242 	isc_result_t result;
243 	dbinstance_t *dbi = NULL;
244 	mysql_instance_t *db = (mysql_instance_t *)dbdata;
245 	char *querystring = NULL;
246 	unsigned int i = 0;
247 	unsigned int j = 0;
248 	int qres = 0;
249 
250 #if PTHREADS
251 	/* find an available DBI from the list */
252 	dbi = mysql_find_avail_conn(db);
253 #else /* PTHREADS */
254 	/*
255 	 * only 1 DBI - no need to lock instance lock either
256 	 * only 1 thread in the whole process, no possible contention.
257 	 */
258 	dbi = (dbinstance_t *)(db->db);
259 #endif /* PTHREADS */
260 
261 	if (dbi == NULL) {
262 		result = ISC_R_FAILURE;
263 		goto cleanup;
264 	}
265 
266 	/* what type of query are we going to run? */
267 	switch(query) {
268 	case ALLNODES:
269 		if (dbi->allnodes_q == NULL) {
270 			result = ISC_R_NOTIMPLEMENTED;
271 			goto cleanup;
272 		}
273 		break;
274 	case ALLOWXFR:
275 		if (dbi->allowxfr_q == NULL) {
276 			result = ISC_R_NOTIMPLEMENTED;
277 			goto cleanup;
278 		}
279 		break;
280 	case AUTHORITY:
281 		if (dbi->authority_q == NULL) {
282 			result = ISC_R_NOTIMPLEMENTED;
283 			goto cleanup;
284 		}
285 		break;
286 	case FINDZONE:
287 		if (dbi->findzone_q == NULL) {
288 			db->log(ISC_LOG_DEBUG(2),
289 				"No query specified for findzone.  "
290 				"Findzone requires a query");
291 			result = ISC_R_FAILURE;
292 			goto cleanup;
293 		}
294 		break;
295 	case COUNTZONE:
296 		if (dbi->countzone_q == NULL) {
297 			result = ISC_R_NOTIMPLEMENTED;
298 			goto cleanup;
299 		}
300 		break;
301 	case LOOKUP:
302 		if (dbi->lookup_q == NULL) {
303 			db->log(ISC_LOG_DEBUG(2),
304 				"No query specified for lookup.  "
305 				"Lookup requires a query");
306 			result = ISC_R_FAILURE;
307 			goto cleanup;
308 		}
309 		break;
310 	default:
311 		db->log(ISC_LOG_ERROR,
312 			"Incorrect query flag passed to "
313 			"mysql_get_resultset");
314 		result = ISC_R_UNEXPECTED;
315 		goto cleanup;
316 	}
317 
318 
319 	if (zone != NULL) {
320 		if (dbi->zone != NULL)
321 			free(dbi->zone);
322 
323 		dbi->zone = mysqldrv_escape_string((MYSQL *) dbi->dbconn,
324 						   zone);
325 		if (dbi->zone == NULL) {
326 			result = ISC_R_NOMEMORY;
327 			goto cleanup;
328 		}
329 	} else
330 		dbi->zone = NULL;
331 
332 	if (record != NULL) {
333 		if (dbi->record != NULL)
334 			free(dbi->record);
335 
336 		dbi->record = mysqldrv_escape_string((MYSQL *) dbi->dbconn,
337 						     record);
338 		if (dbi->record == NULL) {
339 			result = ISC_R_NOMEMORY;
340 			goto cleanup;
341 		}
342 	} else
343 		dbi->record = NULL;
344 
345 	if (client != NULL) {
346 		if (dbi->client != NULL)
347 			free(dbi->client);
348 
349 		dbi->client = mysqldrv_escape_string((MYSQL *) dbi->dbconn,
350 						     client);
351 		if (dbi->client == NULL) {
352 			result = ISC_R_NOMEMORY;
353 			goto cleanup;
354 		}
355 	} else
356 		dbi->client = NULL;
357 
358 	/*
359 	 * what type of query are we going to run?  this time we build
360 	 * the actual query to run.
361 	 */
362 	switch(query) {
363 	case ALLNODES:
364 		querystring = build_querystring(dbi->allnodes_q);
365 		break;
366 	case ALLOWXFR:
367 		querystring = build_querystring(dbi->allowxfr_q);
368 		break;
369 	case AUTHORITY:
370 		querystring = build_querystring(dbi->authority_q);
371 		break;
372 	case FINDZONE:
373 		querystring = build_querystring(dbi->findzone_q);
374 		break;
375 	case COUNTZONE:
376 		querystring = build_querystring(dbi->countzone_q);
377 		break;
378 	case LOOKUP:
379 		querystring = build_querystring(dbi->lookup_q);
380 		break;
381 	default:
382 		db->log(ISC_LOG_ERROR,
383 			"Incorrect query flag passed to "
384 			"mysql_get_resultset");
385 		result = ISC_R_UNEXPECTED; goto cleanup;
386 	}
387 
388 	if (querystring == NULL) {
389 		result = ISC_R_NOMEMORY;
390 		goto cleanup;
391 	}
392 
393 	/* output the full query string when debugging */
394 	db->log(ISC_LOG_DEBUG(1), "\nQuery String: %s\n", querystring);
395 
396 	/* attempt query up to 3 times. */
397 	for (i = 0; i < 3; i++) {
398 		qres = mysql_query((MYSQL *) dbi->dbconn, querystring);
399 		if (qres == 0)
400 			break;
401 		for (j = 0; j < 4; j++)
402 		     if (mysql_ping((MYSQL *) dbi->dbconn) == 0)
403 			     break;
404 	}
405 
406 	if (qres == 0) {
407 		result = ISC_R_SUCCESS;
408 		if (query != COUNTZONE) {
409 			*rs = mysql_store_result((MYSQL *) dbi->dbconn);
410 			if (*rs == NULL)
411 				result = ISC_R_FAILURE;
412 		}
413 	} else
414 		result = ISC_R_FAILURE;
415 
416  cleanup:
417 	if (dbi == NULL)
418 		return (ISC_R_FAILURE);
419 
420 	if (dbi->zone != NULL) {
421 		free(dbi->zone);
422 		dbi->zone = NULL;
423 	}
424 	if (dbi->record != NULL) {
425 		free(dbi->record);
426 		dbi->record = NULL;
427 	}
428 	if (dbi->client != NULL) {
429 		free(dbi->client);
430 		dbi->client = NULL;
431 	}
432 
433 	/* release the lock so another thread can use this dbi */
434 	(void) dlz_mutex_unlock(&dbi->lock);
435 
436 	if (querystring != NULL)
437 		free(querystring);
438 
439 	return (result);
440 }
441 
442 /*%
443  * The processing of result sets for lookup and authority are
444  * exactly the same.  So that functionality has been moved
445  * into this function to minimize code.
446  */
447 static isc_result_t
mysql_process_rs(mysql_instance_t * db,dns_sdlzlookup_t * lookup,MYSQL_RES * rs)448 mysql_process_rs(mysql_instance_t *db, dns_sdlzlookup_t *lookup,
449 		 MYSQL_RES *rs)
450 {
451 	isc_result_t result = ISC_R_NOTFOUND;
452 	MYSQL_ROW row;
453 	unsigned int fields;
454 	unsigned int j;
455 	char *tmpString;
456 	char *endp;
457 	int ttl;
458 
459 	fields = mysql_num_fields(rs);	/* how many columns in result set */
460 	row = mysql_fetch_row(rs);	/* get a row from the result set */
461 	while (row != NULL) {
462 		unsigned int len = 0;
463 
464 		switch(fields) {
465 		case 1:
466 			/*
467 			 * one column in rs, it's the data field.  use
468 			 * default type of A record, and default TTL
469 			 * of 86400
470 			 */
471 			result = db->putrr(lookup, "a", 86400, safeGet(row[0]));
472 			break;
473 		case 2:
474 			/*
475 			 * two columns, data field, and data type.
476 			 * use default TTL of 86400.
477 			 */
478 			result = db->putrr(lookup, safeGet(row[0]), 86400,
479 					   safeGet(row[1]));
480 			break;
481 		case 3:
482 			/*
483 			 * three columns, all data no defaults.
484 			 * convert text to int, make sure it worked
485 			 * right.
486 			 */
487 			ttl = strtol(safeGet(row[0]), &endp, 10);
488 			if (*endp != '\0' || ttl < 0) {
489 				db->log(ISC_LOG_ERROR,
490 					"MySQL module ttl must be "
491 					"a postive number");
492 				return (ISC_R_FAILURE);
493 			}
494 
495 			result = db->putrr(lookup, safeGet(row[1]), ttl,
496 						safeGet(row[2]));
497 			break;
498 		default:
499 			/*
500 			 * more than 3 fields, concatenate the last
501 			 * ones together.  figure out how long to make
502 			 * string.
503 			 */
504 			for (j = 2; j < fields; j++)
505 				len += strlen(safeGet(row[j])) + 1;
506 
507 			/*
508 			 * allocate string memory, allow for NULL to
509 			 * term string
510 			 */
511 			tmpString = malloc(len + 1);
512 			if (tmpString == NULL) {
513 				db->log(ISC_LOG_ERROR,
514 					"MySQL module unable to allocate "
515 					"memory for temporary string");
516 				mysql_free_result(rs);
517 				return (ISC_R_FAILURE);
518 			}
519 
520 			strcpy(tmpString, safeGet(row[2]));
521 			for (j = 3; j < fields; j++) {
522 				strcat(tmpString, " ");
523 				strcat(tmpString, safeGet(row[j]));
524 			}
525 
526 			ttl = strtol(safeGet(row[0]), &endp, 10);
527 			if (*endp != '\0' || ttl < 0) {
528 				db->log(ISC_LOG_ERROR,
529 					"MySQL module ttl must be "
530 					"a postive number");
531 				return (ISC_R_FAILURE);
532 			}
533 
534 			result = db->putrr(lookup, safeGet(row[1]),
535 					   ttl, tmpString);
536 			free(tmpString);
537 		}
538 
539 		if (result != ISC_R_SUCCESS) {
540 			mysql_free_result(rs);
541 			db->log(ISC_LOG_ERROR,
542 				"putrr returned error: %d", result);
543 			return (ISC_R_FAILURE);
544 		}
545 
546 		row = mysql_fetch_row(rs);
547 	}
548 
549 	mysql_free_result(rs);
550 	return (result);
551 }
552 
553 /*
554  * DLZ methods
555  */
556 
557 /*% determine if the zone is supported by (in) the database */
558 isc_result_t
dlz_findzonedb(void * dbdata,const char * name,dns_clientinfomethods_t * methods,dns_clientinfo_t * clientinfo)559 dlz_findzonedb(void *dbdata, const char *name,
560 	       dns_clientinfomethods_t *methods,
561 	       dns_clientinfo_t *clientinfo)
562 {
563 	isc_result_t result;
564 	MYSQL_RES *rs = NULL;
565 	my_ulonglong rows;
566 	mysql_instance_t *db = (mysql_instance_t *)dbdata;
567 
568 	UNUSED(methods);
569 	UNUSED(clientinfo);
570 
571 	result = mysql_get_resultset(name, NULL, NULL, FINDZONE, dbdata, &rs);
572 	if (result != ISC_R_SUCCESS || rs == NULL) {
573 		if (rs != NULL)
574 			mysql_free_result(rs);
575 
576 		db->log(ISC_LOG_ERROR,
577 			"MySQL module unable to return "
578 			"result set for findzone query");
579 
580 		return (ISC_R_FAILURE);
581 	}
582 
583 	/*
584 	 * if we returned any rows, the zone is supported.
585 	 */
586 	rows = mysql_num_rows(rs);
587 	mysql_free_result(rs);
588 	if (rows > 0) {
589 		mysql_get_resultset(name, NULL, NULL, COUNTZONE, dbdata, NULL);
590 		return (ISC_R_SUCCESS);
591 	}
592 
593 	return (ISC_R_NOTFOUND);
594 }
595 
596 /*% Determine if the client is allowed to perform a zone transfer */
597 isc_result_t
dlz_allowzonexfr(void * dbdata,const char * name,const char * client)598 dlz_allowzonexfr(void *dbdata, const char *name, const char *client) {
599 	isc_result_t result;
600 	mysql_instance_t *db = (mysql_instance_t *)dbdata;
601 	MYSQL_RES *rs = NULL;
602 	my_ulonglong rows;
603 
604 	/* first check if the zone is supported by the database. */
605 	result = dlz_findzonedb(dbdata, name, NULL, NULL);
606 	if (result != ISC_R_SUCCESS)
607 		return (ISC_R_NOTFOUND);
608 
609 	/*
610 	 * if we get to this point we know the zone is supported by
611 	 * the database the only questions now are is the zone
612 	 * transfer is allowed for this client and did the config file
613 	 * have an allow zone xfr query.
614 	 */
615 	result = mysql_get_resultset(name, NULL, client, ALLOWXFR,
616 				     dbdata, &rs);
617 	if (result == ISC_R_NOTIMPLEMENTED)
618 		return (result);
619 
620 	if (result != ISC_R_SUCCESS || rs == NULL) {
621 		if (rs != NULL)
622 			mysql_free_result(rs);
623 		db->log(ISC_LOG_ERROR,
624 			"MySQL module unable to return "
625 			"result set for allow xfr query");
626 		return (ISC_R_FAILURE);
627 	}
628 
629 	/*
630 	 * count how many rows in result set; if we returned any,
631 	 * zone xfr is allowed.
632 	 */
633 	rows = mysql_num_rows(rs);
634 	mysql_free_result(rs);
635 	if (rows > 0)
636 		return (ISC_R_SUCCESS);
637 
638 	return (ISC_R_NOPERM);
639 }
640 
641 /*%
642  * If the client is allowed to perform a zone transfer, the next order of
643  * business is to get all the nodes in the zone, so bind can respond to the
644  * query.
645  */
646 isc_result_t
dlz_allnodes(const char * zone,void * dbdata,dns_sdlzallnodes_t * allnodes)647 dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) {
648 	isc_result_t result;
649 	mysql_instance_t *db = (mysql_instance_t *)dbdata;
650 	MYSQL_RES *rs = NULL;
651 	MYSQL_ROW row;
652 	unsigned int fields;
653 	unsigned int j;
654 	char *tmpString;
655 	char *endp;
656 	int ttl;
657 
658 	result = mysql_get_resultset(zone, NULL, NULL, ALLNODES, dbdata, &rs);
659 	if (result == ISC_R_NOTIMPLEMENTED)
660 		return (result);
661 
662 	/* if we didn't get a result set, log an err msg. */
663 	if (result != ISC_R_SUCCESS) {
664 		db->log(ISC_LOG_ERROR,
665 			"MySQL module unable to return "
666 			"result set for all nodes query");
667 		goto cleanup;
668 	}
669 
670 	result = ISC_R_NOTFOUND;
671 
672 	fields = mysql_num_fields(rs);	/* how many columns in result set */
673 	row = mysql_fetch_row(rs);	/* get a row from the result set */
674 	while (row != NULL) {
675 		if (fields < 4) {
676 			db->log(ISC_LOG_ERROR,
677 				"MySQL module too few fields returned "
678 				"by all nodes query");
679 			result = ISC_R_FAILURE;
680 			goto cleanup;
681 		}
682 
683 		ttl = strtol(safeGet(row[0]), &endp, 10);
684 		if (*endp != '\0' || ttl < 0) {
685 			db->log(ISC_LOG_ERROR,
686 				"MySQL module ttl must be "
687 				"a postive number");
688 			result = ISC_R_FAILURE;
689 			goto cleanup;
690 		}
691 
692 		if (fields == 4) {
693 			result = db->putnamedrr(allnodes, safeGet(row[2]),
694 						safeGet(row[1]), ttl,
695 						safeGet(row[3]));
696 		} else {
697 			unsigned int len = 0;
698 
699 			/*
700 			 * more than 4 fields, concatenate the last
701 			 * ones together.
702 			 */
703 			for (j = 3; j < fields; j++)
704 				len += strlen(safeGet(row[j])) + 1;
705 
706 			tmpString = malloc(len + 1);
707 			if (tmpString == NULL) {
708 				db->log(ISC_LOG_ERROR,
709 					"MySQL module unable to allocate "
710 					"memory for temporary string");
711 				result = ISC_R_FAILURE;
712 				goto cleanup;
713 			}
714 
715 			strcpy(tmpString, safeGet(row[3]));
716 			for (j = 4; j < fields; j++) {
717 				strcat(tmpString, " ");
718 				strcat(tmpString, safeGet(row[j]));
719 			}
720 
721 			result = db->putnamedrr(allnodes, safeGet(row[2]),
722 						safeGet(row[1]),
723 						ttl, tmpString);
724 			free(tmpString);
725 		}
726 
727 		if (result != ISC_R_SUCCESS) {
728 			db->log(ISC_LOG_ERROR,
729 				"putnamedrr returned error: %s", result);
730 			result = ISC_R_FAILURE;
731 			break;
732 		}
733 
734 		row = mysql_fetch_row(rs);
735 	}
736 
737  cleanup:
738 	if (rs != NULL)
739 		mysql_free_result(rs);
740 
741 	return (result);
742 }
743 
744 /*%
745  * If the lookup function does not return SOA or NS records for the zone,
746  * use this function to get that information for named.
747  */
748 isc_result_t
dlz_authority(const char * zone,void * dbdata,dns_sdlzlookup_t * lookup)749 dlz_authority(const char *zone, void *dbdata, dns_sdlzlookup_t *lookup) {
750 	isc_result_t result;
751 	MYSQL_RES *rs = NULL;
752 	mysql_instance_t *db = (mysql_instance_t *)dbdata;
753 
754 	result = mysql_get_resultset(zone, NULL, NULL, AUTHORITY, dbdata, &rs);
755 	if (result == ISC_R_NOTIMPLEMENTED)
756 		return (result);
757 
758 	if (result != ISC_R_SUCCESS) {
759 		if (rs != NULL)
760 			mysql_free_result(rs);
761 		db->log(ISC_LOG_ERROR,
762 			"MySQL module unable to return "
763 			"result set for authority query");
764 		return (ISC_R_FAILURE);
765 	}
766 
767 	/*
768 	 * lookup and authority result sets are processed in the same
769 	 * manner: mysql_process_rs does the job for both functions.
770 	 */
771 	return (mysql_process_rs(db, lookup, rs));
772 }
773 
774 /*% If zone is supported, lookup up a (or multiple) record(s) in it */
775 isc_result_t
dlz_lookup(const char * zone,const char * name,void * dbdata,dns_sdlzlookup_t * lookup,dns_clientinfomethods_t * methods,dns_clientinfo_t * clientinfo)776 dlz_lookup(const char *zone, const char *name,
777 	   void *dbdata, dns_sdlzlookup_t *lookup,
778 	   dns_clientinfomethods_t *methods,
779 	   dns_clientinfo_t *clientinfo)
780 {
781 	isc_result_t result;
782 	MYSQL_RES *rs = NULL;
783 	mysql_instance_t *db = (mysql_instance_t *)dbdata;
784 
785 	UNUSED(methods);
786 	UNUSED(clientinfo);
787 
788 	result = mysql_get_resultset(zone, name, NULL, LOOKUP, dbdata, &rs);
789 
790 	/* if we didn't get a result set, log an err msg. */
791 	if (result != ISC_R_SUCCESS) {
792 		if (rs != NULL)
793 			mysql_free_result(rs);
794 		db->log(ISC_LOG_ERROR,
795 			"MySQL module unable to return "
796 			"result set for lookup query");
797 		return (ISC_R_FAILURE);
798 	}
799 
800 	/*
801 	 * lookup and authority result sets are processed in the same
802 	 * manner: mysql_process_rs does the job for both functions.
803 	 */
804 	return (mysql_process_rs(db, lookup, rs));
805 }
806 
807 /*%
808  * Create an instance of the module.
809  */
810 isc_result_t
dlz_create(const char * dlzname,unsigned int argc,char * argv[],void ** dbdata,...)811 dlz_create(const char *dlzname, unsigned int argc, char *argv[],
812 	   void **dbdata, ...)
813 {
814 	isc_result_t result = ISC_R_FAILURE;
815 	mysql_instance_t *mysql = NULL;
816 	dbinstance_t *dbi = NULL;
817 	MYSQL *dbc;
818 	char *tmp = NULL;
819 	char *endp;
820 	int j;
821 	const char *helper_name;
822 #if MYSQL_VERSION_ID >= 50000
823         my_bool auto_reconnect = 1;
824 #endif
825 #if PTHREADS
826 	int dbcount;
827 	int i;
828 #endif /* PTHREADS */
829 	va_list ap;
830 
831 	UNUSED(dlzname);
832 
833 	/* allocate memory for MySQL instance */
834 	mysql = calloc(1, sizeof(mysql_instance_t));
835 	if (mysql == NULL)
836 		return (ISC_R_NOMEMORY);
837 	memset(mysql, 0, sizeof(mysql_instance_t));
838 
839 	/* Fill in the helper functions */
840 	va_start(ap, dbdata);
841 	while ((helper_name = va_arg(ap, const char*)) != NULL)
842 		b9_add_helper(mysql, helper_name, va_arg(ap, void*));
843 	va_end(ap);
844 
845 #if PTHREADS
846 	/* if debugging, let user know we are multithreaded. */
847 	mysql->log(ISC_LOG_DEBUG(1), "MySQL module running multithreaded");
848 #else /* PTHREADS */
849 	/* if debugging, let user know we are single threaded. */
850 	mysql->log(ISC_LOG_DEBUG(1), "MySQL module running single threaded");
851 #endif /* PTHREADS */
852 
853 	/* verify we have at least 4 arg's passed to the module */
854 	if (argc < 4) {
855 		mysql->log(ISC_LOG_ERROR,
856 			   "MySQL module requires "
857 			   "at least 4 command line args.");
858 		return (ISC_R_FAILURE);
859 	}
860 
861 	/* no more than 8 arg's should be passed to the module */
862 	if (argc > 8) {
863 		mysql->log(ISC_LOG_ERROR,
864 			   "MySQL module cannot accept "
865 			   "more than 7 command line args.");
866 		return (ISC_R_FAILURE);
867 	}
868 
869 	/* get db name - required */
870 	mysql->dbname = get_parameter_value(argv[1], "dbname=");
871 	if (mysql->dbname == NULL) {
872 		mysql->log(ISC_LOG_ERROR,
873 			   "MySQL module requires a dbname parameter.");
874 		result = ISC_R_FAILURE;
875 		goto cleanup;
876 	}
877 
878 	/* get db port.  Not required, but must be > 0 if specified */
879 	tmp = get_parameter_value(argv[1], "port=");
880 	if (tmp == NULL)
881 		mysql->port = 0;
882 	else {
883 		mysql->port = strtol(tmp, &endp, 10);
884 		if (*endp != '\0' || mysql->port < 0) {
885 			mysql->log(ISC_LOG_ERROR,
886 				   "Mysql module: port "
887 				   "must be a positive number.");
888 			free(tmp);
889 			result = ISC_R_FAILURE;
890 			goto cleanup;
891 		}
892 		free(tmp);
893 	}
894 
895 	mysql->host = get_parameter_value(argv[1], "host=");
896 	mysql->user = get_parameter_value(argv[1], "user=");
897 	mysql->pass = get_parameter_value(argv[1], "pass=");
898 	mysql->socket = get_parameter_value(argv[1], "socket=");
899 
900 	mysql->flags = CLIENT_REMEMBER_OPTIONS;
901 
902 	tmp = get_parameter_value(argv[1], "compress=");
903 	if (tmp != NULL) {
904 		if (strcasecmp(tmp, "true") == 0)
905 			mysql->flags |= CLIENT_COMPRESS;
906 		free(tmp);
907 	}
908 
909 	tmp = get_parameter_value(argv[1], "ssl=");
910 	if (tmp != NULL) {
911 		if (strcasecmp(tmp, "true") == 0)
912 			mysql->flags |= CLIENT_SSL;
913 		free(tmp);
914 	}
915 
916 	tmp = get_parameter_value(argv[1], "space=");
917 	if (tmp != NULL) {
918 		if (strcasecmp(tmp, "ignore") == 0)
919 			mysql->flags |= CLIENT_IGNORE_SPACE;
920 		free(tmp);
921 	}
922 
923 #if PTHREADS
924 	/* multithreaded build can have multiple DB connections */
925 	tmp = get_parameter_value(argv[1], "threads=");
926 	if (tmp == NULL)
927 		dbcount = 1;
928 	else {
929 		dbcount = strtol(tmp, &endp, 10);
930 		if (*endp != '\0' || dbcount < 1) {
931 			mysql->log(ISC_LOG_ERROR,
932 				   "MySQL database connection count "
933 				   "must be positive.");
934 			free(tmp);
935 			result = ISC_R_FAILURE;
936 			goto cleanup;
937 		}
938 		free(tmp);
939 	}
940 
941 	/* allocate memory for database connection list */
942 	mysql->db = calloc(1, sizeof(db_list_t));
943 	if (mysql->db == NULL) {
944 		result = ISC_R_NOMEMORY;
945 		goto cleanup;
946 	}
947 
948 	/* initialize DB connection list */
949 	DLZ_LIST_INIT(*(mysql->db));
950 
951 	/*
952 	 * create the appropriate number of database instances (DBI)
953 	 * append each new DBI to the end of the list
954 	 */
955 	for (i = 0; i < dbcount; i++) {
956 #endif /* PTHREADS */
957 		switch(argc) {
958 		case 4:
959 			result = build_dbinstance(NULL, NULL, NULL,
960 						  argv[2], argv[3], NULL,
961 						  &dbi, mysql->log);
962 			break;
963 		case 5:
964 			result = build_dbinstance(NULL, NULL, argv[4],
965 						  argv[2], argv[3], NULL,
966 						  &dbi, mysql->log);
967 			break;
968 		case 6:
969 			result = build_dbinstance(argv[5], NULL, argv[4],
970 						  argv[2], argv[3], NULL,
971 						  &dbi, mysql->log);
972 			break;
973 		case 7:
974 			result = build_dbinstance(argv[5], argv[6], argv[4],
975 						  argv[2], argv[3], NULL,
976 						  &dbi, mysql->log);
977 			break;
978 		case 8:
979 			result = build_dbinstance(argv[5], argv[6], argv[4],
980 						  argv[2], argv[3], argv[7],
981 						  &dbi, mysql->log);
982 			break;
983 		default:
984 			result = ISC_R_FAILURE;
985 		}
986 
987 
988 		if (result != ISC_R_SUCCESS) {
989 			mysql->log(ISC_LOG_ERROR,
990 				   "MySQL module could not create "
991 				   "database instance object.");
992 			result = ISC_R_FAILURE;
993 			goto cleanup;
994 		}
995 
996 #if PTHREADS
997 		/* when multithreaded, build a list of DBI's */
998 		DLZ_LINK_INIT(dbi, link);
999 		DLZ_LIST_APPEND(*(mysql->db), dbi, link);
1000 #else
1001 		/*
1002 		 * when single threaded, hold onto the one connection
1003 		 * instance.
1004 		 */
1005 		mysql->db = dbi;
1006 #endif
1007 
1008 		/* create and set db connection */
1009 		dbi->dbconn = mysql_init(NULL);
1010 		if (dbi->dbconn == NULL) {
1011 			mysql->log(ISC_LOG_ERROR,
1012 				   "MySQL module could not allocate "
1013 				   "memory for database connection");
1014 			result = ISC_R_FAILURE;
1015 			goto cleanup;
1016 		}
1017 
1018 		dbc = NULL;
1019 
1020 #if MYSQL_VERSION_ID >= 50000
1021 		/* enable automatic reconnection. */
1022 		if (mysql_options((MYSQL *) dbi->dbconn, MYSQL_OPT_RECONNECT,
1023 				  &auto_reconnect) != 0) {
1024 			mysql->log(ISC_LOG_WARNING,
1025 				   "MySQL module failed to set "
1026 				   "MYSQL_OPT_RECONNECT option, continuing");
1027 		}
1028 #endif
1029 
1030 		for (j = 0; dbc == NULL && j < 4; j++) {
1031 			dbc = mysql_real_connect((MYSQL *) dbi->dbconn,
1032 						 mysql->host, mysql->user,
1033 						 mysql->pass, mysql->dbname,
1034 						 mysql->port, mysql->socket,
1035 						 mysql->flags);
1036 			if (dbc == NULL)
1037 				mysql->log(ISC_LOG_ERROR,
1038 					   "MySQL connection failed: %s",
1039 					   mysql_error((MYSQL *) dbi->dbconn));
1040 		}
1041 
1042 		if (dbc == NULL) {
1043 			mysql->log(ISC_LOG_ERROR,
1044 				   "MySQL module failed to create "
1045 				   "database connection after 4 attempts");
1046 			result = ISC_R_FAILURE;
1047 			goto cleanup;
1048 		}
1049 
1050 #if PTHREADS
1051 		/* set DBI = null for next loop through. */
1052 		dbi = NULL;
1053 	}
1054 #endif /* PTHREADS */
1055 
1056 	*dbdata = mysql;
1057 
1058 	return (ISC_R_SUCCESS);
1059 
1060  cleanup:
1061 	dlz_destroy(mysql);
1062 
1063 	return (result);
1064 }
1065 
1066 /*%
1067  * Destroy the module.
1068  */
1069 void
dlz_destroy(void * dbdata)1070 dlz_destroy(void *dbdata) {
1071 	mysql_instance_t *db = (mysql_instance_t *)dbdata;
1072 #if PTHREADS
1073 	/* cleanup the list of DBI's */
1074 	if (db->db != NULL)
1075 		mysql_destroy_dblist((db_list_t *)(db->db));
1076 #else /* PTHREADS */
1077 	mysql_destroy(db);
1078 #endif /* PTHREADS */
1079 
1080 	if (db->dbname != NULL)
1081 		free(db->dbname);
1082 	if (db->host != NULL)
1083 		free(db->host);
1084 	if (db->user != NULL)
1085 		free(db->user);
1086 	if (db->pass != NULL)
1087 		free(db->pass);
1088 	if (db->socket != NULL)
1089 		free(db->socket);
1090 }
1091 
1092 /*
1093  * Return the version of the API
1094  */
1095 int
dlz_version(unsigned int * flags)1096 dlz_version(unsigned int *flags) {
1097 	*flags |= (DNS_SDLZFLAG_RELATIVEOWNER |
1098 		   DNS_SDLZFLAG_RELATIVERDATA |
1099 		   DNS_SDLZFLAG_THREADSAFE);
1100 	return (DLZ_DLOPEN_VERSION);
1101 }
1102 
1103 /*
1104  * Register a helper function from the bind9 dlz_dlopen driver
1105  */
1106 static void
b9_add_helper(mysql_instance_t * db,const char * helper_name,void * ptr)1107 b9_add_helper(mysql_instance_t *db, const char *helper_name, void *ptr) {
1108 	if (strcmp(helper_name, "log") == 0)
1109 		db->log = (log_t *)ptr;
1110 	if (strcmp(helper_name, "putrr") == 0)
1111 		db->putrr = (dns_sdlz_putrr_t *)ptr;
1112 	if (strcmp(helper_name, "putnamedrr") == 0)
1113 		db->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr;
1114 	if (strcmp(helper_name, "writeable_zone") == 0)
1115 		db->writeable_zone = (dns_dlz_writeablezone_t *)ptr;
1116 }
1117