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