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