dlz_mysql_dynamic.c [plain text]
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdlib.h>
#include <dlz_minimal.h>
#include <dlz_list.h>
#include <dlz_dbi.h>
#include <dlz_pthread.h>
#include <mysql/mysql.h>
#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
typedef struct {
#if PTHREADS
db_list_t *db;
int dbcount;
#else
dbinstance_t *db;
#endif
unsigned int flags;
char *dbname;
char *host;
char *user;
char *pass;
char *socket;
int port;
log_t *log;
dns_sdlz_putrr_t *putrr;
dns_sdlz_putnamedrr_t *putnamedrr;
dns_dlz_writeablezone_t *writeable_zone;
} mysql_instance_t;
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);
void
mysql_destroy(dbinstance_t *db) {
if (db->dbconn != NULL)
mysql_close((MYSQL *) db->dbconn);
destroy_dbinstance(db);
}
#if PTHREADS
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);
}
free(dblist);
}
static dbinstance_t *
mysql_find_avail_conn(mysql_instance_t *mysql) {
dbinstance_t *dbi = NULL, *head;
int count = 0;
head = dbi = DLZ_LIST_HEAD(*(mysql->db));
while (count < dbc_search_limit) {
if (dlz_mutex_trylock(&dbi->lock) == 0)
return (dbi);
dbi = DLZ_LIST_NEXT(dbi, link);
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
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);
}
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
dbi = mysql_find_avail_conn(db);
#else
dbi = (dbinstance_t *)(db->db);
#endif
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) {
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;
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;
}
db->log(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; 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 == NULL)
return (ISC_R_FAILURE);
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;
}
(void) dlz_mutex_unlock(&dbi->lock);
if (querystring != NULL)
free(querystring);
return (result);
}
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);
row = mysql_fetch_row(rs);
while (row != NULL) {
unsigned int len = 0;
switch(fields) {
case 1:
result = db->putrr(lookup, "a", 86400, safeGet(row[0]));
break;
case 2:
result = db->putrr(lookup, safeGet(row[0]), 86400,
safeGet(row[1]));
break;
case 3:
ttl = strtol(safeGet(row[0]), &endp, 10);
if (*endp != '\0' || ttl < 0) {
db->log(ISC_LOG_ERROR,
"MySQL module ttl must be "
"a postive number");
return (ISC_R_FAILURE);
}
result = db->putrr(lookup, safeGet(row[1]), ttl,
safeGet(row[2]));
break;
default:
for (j = 2; 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");
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 postive number");
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);
}
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);
}
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);
}
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;
result = dlz_findzonedb(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);
db->log(ISC_LOG_ERROR,
"MySQL module 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);
}
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 (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);
row = mysql_fetch_row(rs);
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 postive 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;
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);
}
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);
}
return (mysql_process_rs(db, lookup, rs));
}
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 (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);
}
return (mysql_process_rs(db, lookup, rs));
}
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 MYSQL_VERSION_ID >= 50000
my_bool auto_reconnect = 1;
#endif
#if PTHREADS
int dbcount;
int i;
#endif
va_list ap;
UNUSED(dlzname);
mysql = calloc(1, sizeof(mysql_instance_t));
if (mysql == NULL)
return (ISC_R_NOMEMORY);
memset(mysql, 0, sizeof(mysql_instance_t));
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
mysql->log(ISC_LOG_DEBUG(1), "MySQL module running multithreaded");
#else
mysql->log(ISC_LOG_DEBUG(1), "MySQL module running single threaded");
#endif
if (argc < 4) {
mysql->log(ISC_LOG_ERROR,
"MySQL module requires "
"at least 4 command line args.");
return (ISC_R_FAILURE);
}
if (argc > 8) {
mysql->log(ISC_LOG_ERROR,
"MySQL module cannot accept "
"more than 7 command line args.");
return (ISC_R_FAILURE);
}
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;
}
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
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);
}
mysql->db = calloc(1, sizeof(db_list_t));
if (mysql->db == NULL) {
result = ISC_R_NOMEMORY;
goto cleanup;
}
DLZ_LIST_INIT(*(mysql->db));
for (i = 0; i < dbcount; i++) {
#endif
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
DLZ_LINK_INIT(dbi, link);
DLZ_LIST_APPEND(*(mysql->db), dbi, link);
#else
mysql->db = dbi;
#endif
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;
#if MYSQL_VERSION_ID >= 50000
if (mysql_options((MYSQL *) dbi->dbconn, MYSQL_OPT_RECONNECT,
&auto_reconnect) != 0) {
mysql->log(ISC_LOG_WARNING,
"MySQL module failed to set "
"MYSQL_OPT_RECONNECT option, continuing");
}
#endif
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
dbi = NULL;
}
#endif
*dbdata = mysql;
return (ISC_R_SUCCESS);
cleanup:
dlz_destroy(mysql);
return (result);
}
void
dlz_destroy(void *dbdata) {
mysql_instance_t *db = (mysql_instance_t *)dbdata;
#if PTHREADS
if (db->db != NULL)
mysql_destroy_dblist((db_list_t *)(db->db));
#else
mysql_destroy(db);
#endif
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);
}
int
dlz_version(unsigned int *flags) {
*flags |= (DNS_SDLZFLAG_RELATIVEOWNER |
DNS_SDLZFLAG_RELATIVERDATA |
DNS_SDLZFLAG_THREADSAFE);
return (DLZ_DLOPEN_VERSION);
}
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;
}