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