/* * Copyright (C) Internet Systems Consortium, Inc. ("ISC") * * SPDX-License-Identifier: MPL-2.0 and ISC * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, you can obtain one at https://mozilla.org/MPL/2.0/. */ /* * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. * * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was * conceived and contributed by Rob Butler. * * Permission to use, copy, modify, and distribute this software for any purpose * with or without fee is hereby granted, provided that the above copyright * notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ /* * This provides the externally loadable MySQL DLZ module, without * update support */ #include #include #include #include #include #include #include #include #include #if !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 80000 typedef bool my_bool; #endif /* !defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 80000 */ #define dbc_search_limit 30 #define ALLNODES 1 #define ALLOWXFR 2 #define AUTHORITY 3 #define FINDZONE 4 #define COUNTZONE 5 #define LOOKUP 6 #define safeGet(in) in == NULL ? "" : in /*% * Structure to hold everything needed by this "instance" of the MySQL * module remember, the module code is only loaded once, but may have * many separate instances. */ typedef struct { #if PTHREADS db_list_t *db; /*%< handle to a list of DB */ int dbcount; #else /* if PTHREADS */ dbinstance_t *db; /*%< handle to DB */ #endif /* if PTHREADS */ unsigned int flags; char *dbname; char *host; char *user; char *pass; char *socket; int port; /* Helper functions from the dlz_dlopen driver */ log_t *log; dns_sdlz_putrr_t *putrr; dns_sdlz_putnamedrr_t *putnamedrr; dns_dlz_writeablezone_t *writeable_zone; } mysql_instance_t; /* forward references */ isc_result_t dlz_findzonedb(void *dbdata, const char *name, dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo); void dlz_destroy(void *dbdata); static void b9_add_helper(mysql_instance_t *db, const char *helper_name, void *ptr); /* * Private methods */ void mysql_destroy(dbinstance_t *db) { /* release DB connection */ if (db->dbconn != NULL) { mysql_close((MYSQL *)db->dbconn); } /* destroy DB instance */ destroy_dbinstance(db); } #if PTHREADS /*% * Properly cleans up a list of database instances. * This function is only used when the module is compiled for * multithreaded operation. */ static void mysql_destroy_dblist(db_list_t *dblist) { dbinstance_t *ndbi = NULL; dbinstance_t *dbi = NULL; ndbi = DLZ_LIST_HEAD(*dblist); while (ndbi != NULL) { dbi = ndbi; ndbi = DLZ_LIST_NEXT(dbi, link); mysql_destroy(dbi); } /* release memory for the list structure */ free(dblist); } /*% * Loops through the list of DB instances, attempting to lock * on the mutex. If successful, the DBI is reserved for use * and the thread can perform queries against the database. * If the lock fails, the next one in the list is tried. * looping continues until a lock is obtained, or until * the list has been searched dbc_search_limit times. * This function is only used when the module is compiled for * multithreaded operation. */ static dbinstance_t * mysql_find_avail_conn(mysql_instance_t *mysql) { dbinstance_t *dbi = NULL, *head; int count = 0; /* get top of list */ head = dbi = DLZ_LIST_HEAD(*(mysql->db)); /* loop through list */ while (count < dbc_search_limit) { /* try to lock on the mutex */ if (dlz_mutex_trylock(&dbi->lock) == 0) { return (dbi); /* success, return the DBI for use. */ } /* not successful, keep trying */ dbi = DLZ_LIST_NEXT(dbi, link); /* check to see if we have gone to the top of the list. */ if (dbi == NULL) { count++; dbi = head; } } mysql->log(ISC_LOG_INFO, "MySQL module unable to find available connection " "after searching %d times", count); return (NULL); } #endif /* PTHREADS */ /*% * Allocates memory for a new string, and then constructs the new * string by "escaping" the input string. The new string is * safe to be used in queries. This is necessary because we cannot * be sure of what types of strings are passed to us, and we don't * want special characters in the string causing problems. */ static char * mysqldrv_escape_string(MYSQL *mysql, const char *instr) { char *outstr; unsigned int len; if (instr == NULL) { return (NULL); } len = strlen(instr); outstr = malloc((2 * len * sizeof(char)) + 1); if (outstr == NULL) { return (NULL); } mysql_real_escape_string(mysql, outstr, instr, len); return (outstr); } /*% * This function is the real core of the module. Zone, record * and client strings are passed in (or NULL is passed if the * string is not available). The type of query we want to run * is indicated by the query flag, and the dbdata object is passed * passed in to. dbdata really holds a single database instance. * The function will construct and run the query, hopefully getting * a result set. */ static isc_result_t mysql_get_resultset(const char *zone, const char *record, const char *client, unsigned int query, void *dbdata, MYSQL_RES **rs) { isc_result_t result; dbinstance_t *dbi = NULL; mysql_instance_t *db = (mysql_instance_t *)dbdata; char *querystring = NULL; unsigned int i = 0; unsigned int j = 0; int qres = 0; #if PTHREADS /* find an available DBI from the list */ dbi = mysql_find_avail_conn(db); #else /* PTHREADS */ /* * only 1 DBI - no need to lock instance lock either * only 1 thread in the whole process, no possible contention. */ dbi = (dbinstance_t *)(db->db); #endif /* PTHREADS */ if (dbi == NULL) { return (ISC_R_FAILURE); } /* what type of query are we going to run? */ switch (query) { case ALLNODES: if (dbi->allnodes_q == NULL) { result = ISC_R_NOTIMPLEMENTED; goto cleanup; } break; case ALLOWXFR: if (dbi->allowxfr_q == NULL) { result = ISC_R_NOTIMPLEMENTED; goto cleanup; } break; case AUTHORITY: if (dbi->authority_q == NULL) { result = ISC_R_NOTIMPLEMENTED; goto cleanup; } break; case FINDZONE: if (dbi->findzone_q == NULL) { db->log(ISC_LOG_DEBUG(2), "No query specified for " "findzone. " "Findzone requires a query"); result = ISC_R_FAILURE; goto cleanup; } break; case COUNTZONE: if (dbi->countzone_q == NULL) { result = ISC_R_NOTIMPLEMENTED; goto cleanup; } break; case LOOKUP: if (dbi->lookup_q == NULL) { db->log(ISC_LOG_DEBUG(2), "No query specified for " "lookup. " "Lookup requires a query"); result = ISC_R_FAILURE; goto cleanup; } break; default: db->log(ISC_LOG_ERROR, "Incorrect query flag passed to " "mysql_get_resultset"); result = ISC_R_UNEXPECTED; goto cleanup; } if (zone != NULL) { if (dbi->zone != NULL) { free(dbi->zone); } dbi->zone = mysqldrv_escape_string((MYSQL *)dbi->dbconn, zone); if (dbi->zone == NULL) { result = ISC_R_NOMEMORY; goto cleanup; } } else { dbi->zone = NULL; } if (record != NULL) { if (dbi->record != NULL) { free(dbi->record); } dbi->record = mysqldrv_escape_string((MYSQL *)dbi->dbconn, record); if (dbi->record == NULL) { result = ISC_R_NOMEMORY; goto cleanup; } } else { dbi->record = NULL; } if (client != NULL) { if (dbi->client != NULL) { free(dbi->client); } dbi->client = mysqldrv_escape_string((MYSQL *)dbi->dbconn, client); if (dbi->client == NULL) { result = ISC_R_NOMEMORY; goto cleanup; } } else { dbi->client = NULL; } /* * what type of query are we going to run? this time we build * the actual query to run. */ switch (query) { case ALLNODES: querystring = build_querystring(dbi->allnodes_q); break; case ALLOWXFR: querystring = build_querystring(dbi->allowxfr_q); break; case AUTHORITY: querystring = build_querystring(dbi->authority_q); break; case FINDZONE: querystring = build_querystring(dbi->findzone_q); break; case COUNTZONE: querystring = build_querystring(dbi->countzone_q); break; case LOOKUP: querystring = build_querystring(dbi->lookup_q); break; default: db->log(ISC_LOG_ERROR, "Incorrect query flag passed to " "mysql_get_resultset"); result = ISC_R_UNEXPECTED; goto cleanup; } if (querystring == NULL) { result = ISC_R_NOMEMORY; goto cleanup; } /* output the full query string when debugging */ db->log(ISC_LOG_DEBUG(1), "\nQuery String: %s\n", querystring); /* attempt query up to 3 times. */ for (i = 0; i < 3; i++) { qres = mysql_query((MYSQL *)dbi->dbconn, querystring); if (qres == 0) { break; } for (j = 0; j < 4; j++) { if (mysql_ping((MYSQL *)dbi->dbconn) == 0) { break; } } } if (qres == 0) { result = ISC_R_SUCCESS; if (query != COUNTZONE) { *rs = mysql_store_result((MYSQL *)dbi->dbconn); if (*rs == NULL) { result = ISC_R_FAILURE; } } } else { result = ISC_R_FAILURE; } cleanup: if (dbi->zone != NULL) { free(dbi->zone); dbi->zone = NULL; } if (dbi->record != NULL) { free(dbi->record); dbi->record = NULL; } if (dbi->client != NULL) { free(dbi->client); dbi->client = NULL; } /* release the lock so another thread can use this dbi */ (void)dlz_mutex_unlock(&dbi->lock); if (querystring != NULL) { free(querystring); } return (result); } /*% * The processing of result sets for lookup and authority are * exactly the same. So that functionality has been moved * into this function to minimize code. */ static isc_result_t mysql_process_rs(mysql_instance_t *db, dns_sdlzlookup_t *lookup, MYSQL_RES *rs) { isc_result_t result = ISC_R_NOTFOUND; MYSQL_ROW row; unsigned int fields; unsigned int j; char *tmpString; char *endp; int ttl; fields = mysql_num_fields(rs); /* how many columns in result set */ row = mysql_fetch_row(rs); /* get a row from the result set */ while (row != NULL) { unsigned int len = 0; switch (fields) { case 1: /* * one column in rs, it's the data field. use * default type of A record, and default TTL * of 86400 */ result = db->putrr(lookup, "a", 86400, safeGet(row[0])); break; case 2: /* * two columns, data field, and data type. * use default TTL of 86400. */ result = db->putrr(lookup, safeGet(row[0]), 86400, safeGet(row[1])); break; case 3: /* * three columns, all data no defaults. * convert text to int, make sure it worked * right. */ ttl = strtol(safeGet(row[0]), &endp, 10); if (*endp != '\0' || ttl < 0) { db->log(ISC_LOG_ERROR, "MySQL module ttl must " "be " "a positive number"); return (ISC_R_FAILURE); } result = db->putrr(lookup, safeGet(row[1]), ttl, safeGet(row[2])); break; default: /* * more than 3 fields, concatenate the last * ones together. figure out how long to make * string. */ for (j = 2; j < fields; j++) { len += strlen(safeGet(row[j])) + 1; } /* * allocate string memory, allow for NULL to * term string */ tmpString = malloc(len + 1); if (tmpString == NULL) { db->log(ISC_LOG_ERROR, "MySQL module unable to " "allocate " "memory for temporary " "string"); mysql_free_result(rs); return (ISC_R_FAILURE); } strcpy(tmpString, safeGet(row[2])); for (j = 3; j < fields; j++) { strcat(tmpString, " "); strcat(tmpString, safeGet(row[j])); } ttl = strtol(safeGet(row[0]), &endp, 10); if (*endp != '\0' || ttl < 0) { db->log(ISC_LOG_ERROR, "MySQL module ttl must " "be " "a positive number"); free(tmpString); return (ISC_R_FAILURE); } result = db->putrr(lookup, safeGet(row[1]), ttl, tmpString); free(tmpString); } if (result != ISC_R_SUCCESS) { mysql_free_result(rs); db->log(ISC_LOG_ERROR, "putrr returned error: %d", result); return (ISC_R_FAILURE); } row = mysql_fetch_row(rs); } mysql_free_result(rs); return (result); } /* * DLZ methods */ /*% determine if the zone is supported by (in) the database */ isc_result_t dlz_findzonedb(void *dbdata, const char *name, dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo) { isc_result_t result; MYSQL_RES *rs = NULL; my_ulonglong rows; mysql_instance_t *db = (mysql_instance_t *)dbdata; UNUSED(methods); UNUSED(clientinfo); result = mysql_get_resultset(name, NULL, NULL, FINDZONE, dbdata, &rs); if (result != ISC_R_SUCCESS || rs == NULL) { if (rs != NULL) { mysql_free_result(rs); } db->log(ISC_LOG_ERROR, "MySQL module unable to return " "result set for findzone query"); return (ISC_R_FAILURE); } /* * if we returned any rows, the zone is supported. */ rows = mysql_num_rows(rs); mysql_free_result(rs); if (rows > 0) { mysql_get_resultset(name, NULL, NULL, COUNTZONE, dbdata, NULL); return (ISC_R_SUCCESS); } return (ISC_R_NOTFOUND); } /*% Determine if the client is allowed to perform a zone transfer */ isc_result_t dlz_allowzonexfr(void *dbdata, const char *name, const char *client) { isc_result_t result; mysql_instance_t *db = (mysql_instance_t *)dbdata; MYSQL_RES *rs = NULL; my_ulonglong rows; /* first check if the zone is supported by the database. */ result = dlz_findzonedb(dbdata, name, NULL, NULL); if (result != ISC_R_SUCCESS) { return (ISC_R_NOTFOUND); } /* * if we get to this point we know the zone is supported by * the database the only questions now are is the zone * transfer is allowed for this client and did the config file * have an allow zone xfr query. */ result = mysql_get_resultset(name, NULL, client, ALLOWXFR, dbdata, &rs); if (result == ISC_R_NOTIMPLEMENTED) { return (result); } if (result != ISC_R_SUCCESS || rs == NULL) { if (rs != NULL) { mysql_free_result(rs); } db->log(ISC_LOG_ERROR, "MySQL module unable to return " "result set for allow xfr query"); return (ISC_R_FAILURE); } /* * count how many rows in result set; if we returned any, * zone xfr is allowed. */ rows = mysql_num_rows(rs); mysql_free_result(rs); if (rows > 0) { return (ISC_R_SUCCESS); } return (ISC_R_NOPERM); } /*% * If the client is allowed to perform a zone transfer, the next order of * business is to get all the nodes in the zone, so bind can respond to the * query. */ isc_result_t dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) { isc_result_t result; mysql_instance_t *db = (mysql_instance_t *)dbdata; MYSQL_RES *rs = NULL; MYSQL_ROW row; unsigned int fields; unsigned int j; char *tmpString; char *endp; int ttl; result = mysql_get_resultset(zone, NULL, NULL, ALLNODES, dbdata, &rs); if (result == ISC_R_NOTIMPLEMENTED) { return (result); } /* if we didn't get a result set, log an err msg. */ if (result != ISC_R_SUCCESS) { db->log(ISC_LOG_ERROR, "MySQL module unable to return " "result set for all nodes query"); goto cleanup; } result = ISC_R_NOTFOUND; fields = mysql_num_fields(rs); /* how many columns in result set */ row = mysql_fetch_row(rs); /* get a row from the result set */ while (row != NULL) { if (fields < 4) { db->log(ISC_LOG_ERROR, "MySQL module too few fields " "returned " "by all nodes query"); result = ISC_R_FAILURE; goto cleanup; } ttl = strtol(safeGet(row[0]), &endp, 10); if (*endp != '\0' || ttl < 0) { db->log(ISC_LOG_ERROR, "MySQL module ttl must be " "a positive number"); result = ISC_R_FAILURE; goto cleanup; } if (fields == 4) { result = db->putnamedrr(allnodes, safeGet(row[2]), safeGet(row[1]), ttl, safeGet(row[3])); } else { unsigned int len = 0; /* * more than 4 fields, concatenate the last * ones together. */ for (j = 3; j < fields; j++) { len += strlen(safeGet(row[j])) + 1; } tmpString = malloc(len + 1); if (tmpString == NULL) { db->log(ISC_LOG_ERROR, "MySQL module unable to " "allocate " "memory for temporary " "string"); result = ISC_R_FAILURE; goto cleanup; } strcpy(tmpString, safeGet(row[3])); for (j = 4; j < fields; j++) { strcat(tmpString, " "); strcat(tmpString, safeGet(row[j])); } result = db->putnamedrr(allnodes, safeGet(row[2]), safeGet(row[1]), ttl, tmpString); free(tmpString); } if (result != ISC_R_SUCCESS) { db->log(ISC_LOG_ERROR, "putnamedrr returned error: %s", result); result = ISC_R_FAILURE; break; } row = mysql_fetch_row(rs); } cleanup: if (rs != NULL) { mysql_free_result(rs); } return (result); } /*% * If the lookup function does not return SOA or NS records for the zone, * use this function to get that information for named. */ isc_result_t dlz_authority(const char *zone, void *dbdata, dns_sdlzlookup_t *lookup) { isc_result_t result; MYSQL_RES *rs = NULL; mysql_instance_t *db = (mysql_instance_t *)dbdata; result = mysql_get_resultset(zone, NULL, NULL, AUTHORITY, dbdata, &rs); if (result == ISC_R_NOTIMPLEMENTED) { return (result); } if (result != ISC_R_SUCCESS) { if (rs != NULL) { mysql_free_result(rs); } db->log(ISC_LOG_ERROR, "MySQL module unable to return " "result set for authority query"); return (ISC_R_FAILURE); } /* * lookup and authority result sets are processed in the same * manner: mysql_process_rs does the job for both functions. */ return (mysql_process_rs(db, lookup, rs)); } /*% If zone is supported, lookup up a (or multiple) record(s) in it */ isc_result_t dlz_lookup(const char *zone, const char *name, void *dbdata, dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo) { isc_result_t result; MYSQL_RES *rs = NULL; mysql_instance_t *db = (mysql_instance_t *)dbdata; UNUSED(methods); UNUSED(clientinfo); result = mysql_get_resultset(zone, name, NULL, LOOKUP, dbdata, &rs); /* if we didn't get a result set, log an err msg. */ if (result != ISC_R_SUCCESS) { if (rs != NULL) { mysql_free_result(rs); } db->log(ISC_LOG_ERROR, "MySQL module unable to return " "result set for lookup query"); return (ISC_R_FAILURE); } /* * lookup and authority result sets are processed in the same * manner: mysql_process_rs does the job for both functions. */ return (mysql_process_rs(db, lookup, rs)); } /*% * Create an instance of the module. */ isc_result_t dlz_create(const char *dlzname, unsigned int argc, char *argv[], void **dbdata, ...) { isc_result_t result = ISC_R_FAILURE; mysql_instance_t *mysql = NULL; dbinstance_t *dbi = NULL; MYSQL *dbc; char *tmp = NULL; char *endp; int j; const char *helper_name; #if PTHREADS int dbcount; int i; #endif /* PTHREADS */ va_list ap; UNUSED(dlzname); /* allocate memory for MySQL instance */ mysql = calloc(1, sizeof(mysql_instance_t)); if (mysql == NULL) { return (ISC_R_NOMEMORY); } memset(mysql, 0, sizeof(mysql_instance_t)); /* Fill in the helper functions */ va_start(ap, dbdata); while ((helper_name = va_arg(ap, const char *)) != NULL) { b9_add_helper(mysql, helper_name, va_arg(ap, void *)); } va_end(ap); #if PTHREADS /* if debugging, let user know we are multithreaded. */ mysql->log(ISC_LOG_DEBUG(1), "MySQL module running multithreaded"); #else /* PTHREADS */ /* if debugging, let user know we are single threaded. */ mysql->log(ISC_LOG_DEBUG(1), "MySQL module running single threaded"); #endif /* PTHREADS */ /* verify we have at least 4 arg's passed to the module */ if (argc < 4) { mysql->log(ISC_LOG_ERROR, "MySQL module requires " "at least 4 command line args."); return (ISC_R_FAILURE); } /* no more than 8 arg's should be passed to the module */ if (argc > 8) { mysql->log(ISC_LOG_ERROR, "MySQL module cannot accept " "more than 7 command line args."); return (ISC_R_FAILURE); } /* get db name - required */ mysql->dbname = get_parameter_value(argv[1], "dbname="); if (mysql->dbname == NULL) { mysql->log(ISC_LOG_ERROR, "MySQL module requires a dbname " "parameter."); result = ISC_R_FAILURE; goto cleanup; } /* get db port. Not required, but must be > 0 if specified */ tmp = get_parameter_value(argv[1], "port="); if (tmp == NULL) { mysql->port = 0; } else { mysql->port = strtol(tmp, &endp, 10); if (*endp != '\0' || mysql->port < 0) { mysql->log(ISC_LOG_ERROR, "Mysql module: port " "must be a positive number."); free(tmp); result = ISC_R_FAILURE; goto cleanup; } free(tmp); } mysql->host = get_parameter_value(argv[1], "host="); mysql->user = get_parameter_value(argv[1], "user="); mysql->pass = get_parameter_value(argv[1], "pass="); mysql->socket = get_parameter_value(argv[1], "socket="); mysql->flags = CLIENT_REMEMBER_OPTIONS; tmp = get_parameter_value(argv[1], "compress="); if (tmp != NULL) { if (strcasecmp(tmp, "true") == 0) { mysql->flags |= CLIENT_COMPRESS; } free(tmp); } tmp = get_parameter_value(argv[1], "ssl="); if (tmp != NULL) { if (strcasecmp(tmp, "true") == 0) { mysql->flags |= CLIENT_SSL; } free(tmp); } tmp = get_parameter_value(argv[1], "space="); if (tmp != NULL) { if (strcasecmp(tmp, "ignore") == 0) { mysql->flags |= CLIENT_IGNORE_SPACE; } free(tmp); } #if PTHREADS /* multithreaded build can have multiple DB connections */ tmp = get_parameter_value(argv[1], "threads="); if (tmp == NULL) { dbcount = 1; } else { dbcount = strtol(tmp, &endp, 10); if (*endp != '\0' || dbcount < 1) { mysql->log(ISC_LOG_ERROR, "MySQL database connection " "count " "must be positive."); free(tmp); result = ISC_R_FAILURE; goto cleanup; } free(tmp); } /* allocate memory for database connection list */ mysql->db = calloc(1, sizeof(db_list_t)); if (mysql->db == NULL) { result = ISC_R_NOMEMORY; goto cleanup; } /* initialize DB connection list */ DLZ_LIST_INIT(*(mysql->db)); /* * create the appropriate number of database instances (DBI) * append each new DBI to the end of the list */ for (i = 0; i < dbcount; i++) { #endif /* PTHREADS */ switch (argc) { case 4: result = build_dbinstance(NULL, NULL, NULL, argv[2], argv[3], NULL, &dbi, mysql->log); break; case 5: result = build_dbinstance(NULL, NULL, argv[4], argv[2], argv[3], NULL, &dbi, mysql->log); break; case 6: result = build_dbinstance(argv[5], NULL, argv[4], argv[2], argv[3], NULL, &dbi, mysql->log); break; case 7: result = build_dbinstance(argv[5], argv[6], argv[4], argv[2], argv[3], NULL, &dbi, mysql->log); break; case 8: result = build_dbinstance(argv[5], argv[6], argv[4], argv[2], argv[3], argv[7], &dbi, mysql->log); break; default: result = ISC_R_FAILURE; } if (result != ISC_R_SUCCESS) { mysql->log(ISC_LOG_ERROR, "MySQL module could not " "create " "database instance object."); result = ISC_R_FAILURE; goto cleanup; } #if PTHREADS /* when multithreaded, build a list of DBI's */ DLZ_LINK_INIT(dbi, link); DLZ_LIST_APPEND(*(mysql->db), dbi, link); #else /* if PTHREADS */ /* * when single threaded, hold onto the one connection * instance. */ mysql->db = dbi; #endif /* if PTHREADS */ /* create and set db connection */ dbi->dbconn = mysql_init(NULL); if (dbi->dbconn == NULL) { mysql->log(ISC_LOG_ERROR, "MySQL module could not " "allocate " "memory for database " "connection"); result = ISC_R_FAILURE; goto cleanup; } dbc = NULL; /* enable automatic reconnection. */ if (mysql_options((MYSQL *)dbi->dbconn, MYSQL_OPT_RECONNECT, &(my_bool){ 1 }) != 0) { mysql->log(ISC_LOG_WARNING, "MySQL module failed to " "set " "MYSQL_OPT_RECONNECT " "option, continuing"); } for (j = 0; dbc == NULL && j < 4; j++) { dbc = mysql_real_connect( (MYSQL *)dbi->dbconn, mysql->host, mysql->user, mysql->pass, mysql->dbname, mysql->port, mysql->socket, mysql->flags); if (dbc == NULL) { mysql->log(ISC_LOG_ERROR, "MySQL connection failed: %s", mysql_error((MYSQL *)dbi->dbconn)); } } if (dbc == NULL) { mysql->log(ISC_LOG_ERROR, "MySQL module failed to " "create " "database connection after 4 " "attempts"); result = ISC_R_FAILURE; goto cleanup; } #if PTHREADS /* set DBI = null for next loop through. */ dbi = NULL; } #endif /* PTHREADS */ *dbdata = mysql; return (ISC_R_SUCCESS); cleanup: dlz_destroy(mysql); return (result); } /*% * Destroy the module. */ void dlz_destroy(void *dbdata) { mysql_instance_t *db = (mysql_instance_t *)dbdata; #if PTHREADS /* cleanup the list of DBI's */ if (db->db != NULL) { mysql_destroy_dblist((db_list_t *)(db->db)); } #else /* PTHREADS */ mysql_destroy(db); #endif /* PTHREADS */ if (db->dbname != NULL) { free(db->dbname); } if (db->host != NULL) { free(db->host); } if (db->user != NULL) { free(db->user); } if (db->pass != NULL) { free(db->pass); } if (db->socket != NULL) { free(db->socket); } } /* * Return the version of the API */ int dlz_version(unsigned int *flags) { *flags |= (DNS_SDLZFLAG_RELATIVEOWNER | DNS_SDLZFLAG_RELATIVERDATA | DNS_SDLZFLAG_THREADSAFE); return (DLZ_DLOPEN_VERSION); } /* * Register a helper function from the bind9 dlz_dlopen driver */ static void b9_add_helper(mysql_instance_t *db, const char *helper_name, void *ptr) { if (strcmp(helper_name, "log") == 0) { db->log = (log_t *)ptr; } if (strcmp(helper_name, "putrr") == 0) { db->putrr = (dns_sdlz_putrr_t *)ptr; } if (strcmp(helper_name, "putnamedrr") == 0) { db->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr; } if (strcmp(helper_name, "writeable_zone") == 0) { db->writeable_zone = (dns_dlz_writeablezone_t *)ptr; } }