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 * 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 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 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 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 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 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 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 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 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 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 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 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