dlz_mysql_driver.c [plain text]
#ifdef DLZ_MYSQL
#include <config.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dns/log.h>
#include <dns/sdlz.h>
#include <dns/result.h>
#include <isc/mem.h>
#include <isc/platform.h>
#include <isc/print.h>
#include <isc/result.h>
#include <isc/string.h>
#include <isc/util.h>
#include <named/globals.h>
#include <dlz/sdlz_helper.h>
#include <dlz/dlz_mysql_driver.h>
#include <mysql.h>
static dns_sdlzimplementation_t *dlz_mysql = NULL;
#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
static char *
mysqldrv_escape_string(MYSQL *mysql, const char *instr) {
char *outstr;
unsigned int len;
if (instr == NULL)
return NULL;
len = strlen(instr);
outstr = isc_mem_allocate(ns_g_mctx ,(2 * len * sizeof(char)) + 1);
if (outstr == NULL)
return NULL;
mysql_real_escape_string(mysql, outstr, instr, len);
return outstr;
}
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;
char *querystring = NULL;
unsigned int i = 0;
unsigned int j = 0;
int qres = 0;
if (query != COUNTZONE)
REQUIRE(*rs == NULL);
else
REQUIRE(rs == NULL);
dbi = (dbinstance_t *) dbdata;
if (dbi == NULL) {
result = ISC_R_FAILURE;
goto cleanup;
}
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) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, 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) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
"No query specified for lookup. "
"Lookup requires a query");
result = ISC_R_FAILURE;
goto cleanup;
}
break;
default:
UNEXPECTED_ERROR(__FILE__, __LINE__,
"Incorrect query flag passed to "
"mysql_get_resultset");
result = ISC_R_UNEXPECTED;
goto cleanup;
}
if (zone != NULL) {
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) {
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) {
dbi->client = mysqldrv_escape_string((MYSQL *) dbi->dbconn,
client);
if (dbi->client == NULL) {
result = ISC_R_NOMEMORY;
goto cleanup;
}
} else {
dbi->client = NULL;
}
switch(query) {
case ALLNODES:
querystring = build_querystring(ns_g_mctx, dbi->allnodes_q);
break;
case ALLOWXFR:
querystring = build_querystring(ns_g_mctx, dbi->allowxfr_q);
break;
case AUTHORITY:
querystring = build_querystring(ns_g_mctx, dbi->authority_q);
break;
case FINDZONE:
querystring = build_querystring(ns_g_mctx, dbi->findzone_q);
break;
case COUNTZONE:
querystring = build_querystring(ns_g_mctx, dbi->countzone_q);
break;
case LOOKUP:
querystring = build_querystring(ns_g_mctx, dbi->lookup_q);
break;
default:
UNEXPECTED_ERROR(__FILE__, __LINE__,
"Incorrect query flag passed to "
"mysql_get_resultset");
result = ISC_R_UNEXPECTED;
goto cleanup;
}
if (querystring == NULL) {
result = ISC_R_NOMEMORY;
goto cleanup;
}
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(1),
"\nQuery String: %s\n", querystring);
for (i=0; i < 3; i++) {
qres = mysql_query((MYSQL *) dbi->dbconn, querystring);
if (qres == 0)
break;
for (j=0; mysql_ping((MYSQL *) dbi->dbconn) != 0 && j < 4; j++)
;
}
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 == NULL)
return ISC_R_FAILURE;
if (dbi->zone != NULL)
isc_mem_free(ns_g_mctx, dbi->zone);
if (dbi->record != NULL)
isc_mem_free(ns_g_mctx, dbi->record);
if (dbi->client != NULL)
isc_mem_free(ns_g_mctx, dbi->client);
if (querystring != NULL)
isc_mem_free(ns_g_mctx, querystring);
return result;
}
static isc_result_t
mysql_process_rs(dns_sdlzlookup_t *lookup, MYSQL_RES *rs)
{
isc_result_t result = ISC_R_NOTFOUND;
MYSQL_ROW row;
unsigned int fields;
unsigned int j;
unsigned int len;
char *tmpString;
char *endp;
int ttl;
row = mysql_fetch_row(rs);
fields = mysql_num_fields(rs);
while (row != NULL) {
switch(fields) {
case 1:
result = dns_sdlz_putrr(lookup, "a", 86400,
safeGet(row[0]));
break;
case 2:
result = dns_sdlz_putrr(lookup, safeGet(row[0]), 86400,
safeGet(row[1]));
break;
case 3:
ttl = strtol(safeGet(row[0]), &endp, 10);
if (*endp != '\0' || ttl < 0) {
isc_log_write(dns_lctx,
DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
"mysql driver ttl must be "
"a postive number");
}
result = dns_sdlz_putrr(lookup, safeGet(row[1]), ttl,
safeGet(row[2]));
break;
default:
for (j=2, len=0; j < fields; j++) {
len += strlen(safeGet(row[j])) + 1;
}
tmpString = isc_mem_allocate(ns_g_mctx, len + 1);
if (tmpString == NULL) {
isc_log_write(dns_lctx,
DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
"mysql driver 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) {
isc_log_write(dns_lctx,
DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
"mysql driver ttl must be "
"a postive number");
}
result = dns_sdlz_putrr(lookup, safeGet(row[1]),
ttl, tmpString);
isc_mem_free(ns_g_mctx, tmpString);
}
if (result != ISC_R_SUCCESS) {
mysql_free_result(rs);
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
"dns_sdlz_putrr returned error. "
"Error code was: %s",
isc_result_totext(result));
return (ISC_R_FAILURE);
}
row = mysql_fetch_row(rs);
}
mysql_free_result(rs);
return result;
}
static isc_result_t
mysql_findzone(void *driverarg, void *dbdata, const char *name,
dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo)
{
isc_result_t result;
MYSQL_RES *rs = NULL;
my_ulonglong rows;
UNUSED(driverarg);
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);
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
"mysql driver unable to return "
"result set for findzone query");
return (ISC_R_FAILURE);
}
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);
}
static isc_result_t
mysql_allowzonexfr(void *driverarg, void *dbdata, const char *name,
const char *client)
{
isc_result_t result;
MYSQL_RES *rs = NULL;
my_ulonglong rows;
UNUSED(driverarg);
result = mysql_findzone(driverarg, dbdata, name, NULL, NULL);
if (result != ISC_R_SUCCESS)
return (ISC_R_NOTFOUND);
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);
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
"mysql driver unable to return "
"result set for allow xfr query");
return (ISC_R_FAILURE);
}
rows = mysql_num_rows(rs);
mysql_free_result(rs);
if (rows > 0)
return (ISC_R_SUCCESS);
return (ISC_R_NOPERM);
}
static isc_result_t
mysql_allnodes(const char *zone, void *driverarg, void *dbdata,
dns_sdlzallnodes_t *allnodes)
{
isc_result_t result;
MYSQL_RES *rs = NULL;
MYSQL_ROW row;
unsigned int fields;
unsigned int j;
unsigned int len;
char *tmpString;
char *endp;
int ttl;
UNUSED(driverarg);
result = mysql_get_resultset(zone, NULL, NULL, ALLNODES, dbdata, &rs);
if (result == ISC_R_NOTIMPLEMENTED)
return result;
if (result != ISC_R_SUCCESS) {
if (rs != NULL)
mysql_free_result(rs);
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
"mysql driver unable to return "
"result set for all nodes query");
return (ISC_R_FAILURE);
}
result = ISC_R_NOTFOUND;
row = mysql_fetch_row(rs);
fields = mysql_num_fields(rs);
while (row != NULL) {
if (fields < 4) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
"mysql driver too few fields returned "
"by all nodes query");
}
ttl = strtol(safeGet(row[0]), &endp, 10);
if (*endp != '\0' || ttl < 0) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
"mysql driver ttl must be "
"a postive number");
}
if (fields == 4) {
result = dns_sdlz_putnamedrr(allnodes, safeGet(row[2]),
safeGet(row[1]), ttl,
safeGet(row[3]));
} else {
for (j=3, len=0; j < fields; j++) {
len += strlen(safeGet(row[j])) + 1;
}
tmpString = isc_mem_allocate(ns_g_mctx, len + 1);
if (tmpString == NULL) {
isc_log_write(dns_lctx,
DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
"mysql driver unable "
"to allocate memory for "
"temporary string");
mysql_free_result(rs);
return (ISC_R_FAILURE);
}
strcpy(tmpString, safeGet(row[3]));
for (j=4; j < fields; j++) {
strcat(tmpString, " ");
strcat(tmpString, safeGet(row[j]));
}
result = dns_sdlz_putnamedrr(allnodes, safeGet(row[2]),
safeGet(row[1]),
ttl, tmpString);
isc_mem_free(ns_g_mctx, tmpString);
}
if (result != ISC_R_SUCCESS) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
"dns_sdlz_putnamedrr returned error. "
"Error code was: %s",
isc_result_totext(result));
result = ISC_R_FAILURE;
break;
}
row = mysql_fetch_row(rs);
}
mysql_free_result(rs);
return result;
}
static isc_result_t
mysql_authority(const char *zone, void *driverarg, void *dbdata,
dns_sdlzlookup_t *lookup)
{
isc_result_t result;
MYSQL_RES *rs = NULL;
UNUSED(driverarg);
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);
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
"mysql driver unable to return "
"result set for authority query");
return (ISC_R_FAILURE);
}
return mysql_process_rs(lookup, rs);
}
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)
{
isc_result_t result;
MYSQL_RES *rs = NULL;
UNUSED(driverarg);
UNUSED(methods);
UNUSED(clientinfo);
result = mysql_get_resultset(zone, name, NULL, LOOKUP, dbdata, &rs);
if (result != ISC_R_SUCCESS) {
if (rs != NULL)
mysql_free_result(rs);
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
"mysql driver unable to return "
"result set for lookup query");
return (ISC_R_FAILURE);
}
return mysql_process_rs(lookup, rs);
}
static isc_result_t
mysql_create(const char *dlzname, unsigned int argc, char *argv[],
void *driverarg, void **dbdata)
{
isc_result_t result;
dbinstance_t *dbi = NULL;
char *tmp = NULL;
char *dbname = NULL;
char *host = NULL;
char *user = NULL;
char *pass = NULL;
char *socket = NULL;
int port;
MYSQL *dbc;
char *endp;
int j;
unsigned int flags = 0;
#if MYSQL_VERSION_ID >= 50000
my_bool auto_reconnect = 1;
#endif
UNUSED(driverarg);
UNUSED(dlzname);
if (argc < 4) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
"mysql driver requires "
"at least 4 command line args.");
return (ISC_R_FAILURE);
}
if (argc > 8) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
"mysql driver cannot accept "
"more than 7 command line args.");
return (ISC_R_FAILURE);
}
dbname = getParameterValue(argv[1], "dbname=");
if (dbname == NULL) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
"mysql driver requires a dbname parameter.");
result = ISC_R_FAILURE;
goto full_cleanup;
}
tmp = getParameterValue(argv[1], "port=");
if (tmp == NULL) {
port = 0;
} else {
port = strtol(tmp, &endp, 10);
if (*endp != '\0' || port < 0) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
"Mysql driver port "
"must be a positive number.");
isc_mem_free(ns_g_mctx, tmp);
result = ISC_R_FAILURE;
goto full_cleanup;
}
isc_mem_free(ns_g_mctx, tmp);
}
switch(argc) {
case 4:
result = build_sqldbinstance(ns_g_mctx, NULL, NULL, NULL,
argv[2], argv[3], NULL, &dbi);
break;
case 5:
result = build_sqldbinstance(ns_g_mctx, NULL, NULL, argv[4],
argv[2], argv[3], NULL, &dbi);
break;
case 6:
result = build_sqldbinstance(ns_g_mctx, argv[5], NULL, argv[4],
argv[2], argv[3], NULL, &dbi);
break;
case 7:
result = build_sqldbinstance(ns_g_mctx, argv[5],
argv[6], argv[4],
argv[2], argv[3], NULL, &dbi);
break;
case 8:
result = build_sqldbinstance(ns_g_mctx, argv[5],
argv[6], argv[4],
argv[2], argv[3], argv[7], &dbi);
break;
default:
result = ISC_R_FAILURE;
}
if (result != ISC_R_SUCCESS) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
"mysql driver could not create "
"database instance object.");
result = ISC_R_FAILURE;
goto cleanup;
}
dbi->dbconn = mysql_init(NULL);
if (dbi->dbconn == NULL) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
"mysql driver could not allocate "
"memory for database connection");
result = ISC_R_FAILURE;
goto full_cleanup;
}
tmp = getParameterValue(argv[1], "compress=");
if (tmp != NULL) {
if (strcasecmp(tmp, "true") == 0)
flags = CLIENT_COMPRESS;
isc_mem_free(ns_g_mctx, tmp);
}
tmp = getParameterValue(argv[1], "ssl=");
if (tmp != NULL) {
if (strcasecmp(tmp, "true") == 0)
flags = flags | CLIENT_SSL;
isc_mem_free(ns_g_mctx, tmp);
}
tmp = getParameterValue(argv[1], "space=");
if (tmp != NULL) {
if (strcasecmp(tmp, "ignore") == 0)
flags = flags | CLIENT_IGNORE_SPACE;
isc_mem_free(ns_g_mctx, tmp);
}
dbc = NULL;
host = getParameterValue(argv[1], "host=");
user = getParameterValue(argv[1], "user=");
pass = getParameterValue(argv[1], "pass=");
socket = getParameterValue(argv[1], "socket=");
#if MYSQL_VERSION_ID >= 50000
if (mysql_options((MYSQL *) dbi->dbconn, MYSQL_OPT_RECONNECT,
&auto_reconnect) != 0) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_WARNING,
"mysql driver failed to set "
"MYSQL_OPT_RECONNECT option, continuing");
}
#endif
for (j=0; dbc == NULL && j < 4; j++)
dbc = mysql_real_connect((MYSQL *) dbi->dbconn, host,
user, pass, dbname, port, socket,
flags);
if (dbc == NULL) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_ERROR,
"mysql driver failed to create "
"database connection after 4 attempts");
result = ISC_R_FAILURE;
goto full_cleanup;
}
*dbdata = dbi;
result = ISC_R_SUCCESS;
goto cleanup;
full_cleanup:
if (dbi != NULL)
destroy_sqldbinstance(dbi);
cleanup:
if (dbname != NULL)
isc_mem_free(ns_g_mctx, dbname);
if (host != NULL)
isc_mem_free(ns_g_mctx, host);
if (user != NULL)
isc_mem_free(ns_g_mctx, user);
if (pass != NULL)
isc_mem_free(ns_g_mctx, pass);
if (socket != NULL)
isc_mem_free(ns_g_mctx, socket);
return result;
}
static void
mysql_destroy(void *driverarg, void *dbdata)
{
dbinstance_t *dbi;
UNUSED(driverarg);
dbi = (dbinstance_t *) dbdata;
if (dbi->dbconn != NULL)
mysql_close((MYSQL *) dbi->dbconn);
destroy_sqldbinstance(dbi);
}
static dns_sdlzmethods_t dlz_mysql_methods = {
mysql_create,
mysql_destroy,
mysql_findzone,
mysql_lookup,
mysql_authority,
mysql_allnodes,
mysql_allowzonexfr,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
};
isc_result_t
dlz_mysql_init(void) {
isc_result_t result;
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
"Registering DLZ mysql driver.");
result = dns_sdlzregister("mysql", &dlz_mysql_methods, NULL,
DNS_SDLZFLAG_RELATIVEOWNER |
DNS_SDLZFLAG_RELATIVERDATA |
DNS_SDLZFLAG_THREADSAFE,
ns_g_mctx, &dlz_mysql);
if (result != ISC_R_SUCCESS) {
UNEXPECTED_ERROR(__FILE__, __LINE__,
"dns_sdlzregister() failed: %s",
isc_result_totext(result));
result = ISC_R_UNEXPECTED;
}
return result;
}
void
dlz_mysql_clear(void) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(2),
"Unregistering DLZ mysql driver.");
if (dlz_mysql != NULL)
dns_sdlzunregister(&dlz_mysql);
}
#endif