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