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