1707 lines
38 KiB
C
1707 lines
38 KiB
C
/*
|
|
* Copyright (C) 2014 Maui Systems Ltd, Scotland, contact@maui-systems.co.uk.
|
|
*
|
|
* 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 MAUI SYSTEMS LTD DISCLAIMS ALL
|
|
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL MAUI SYSTEMS LTD 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.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2011,2014 Internet Systems Consortium, Inc. ("ISC")
|
|
*
|
|
* Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH
|
|
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
* AND FITNESS. IN NO EVENT SHALL ISC 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.
|
|
*/
|
|
|
|
/*
|
|
* BIND 9 DLZ MySQL module with support for dynamic DNS (DDNS)
|
|
*
|
|
* Adapted from code contributed by Marty Lee, Maui Systems Ltd.
|
|
*
|
|
* See README for database schema and usage details.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <unistd.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <netinet/in.h>
|
|
#include <inttypes.h>
|
|
#include <pthread.h>
|
|
#include <netdb.h>
|
|
#include <ifaddrs.h>
|
|
|
|
#include <mysql/mysql.h>
|
|
#include <mysql/errmsg.h>
|
|
|
|
#include <dlz_minimal.h>
|
|
#include <dlz_list.h>
|
|
#include <dlz_pthread.h>
|
|
|
|
/*
|
|
* The SQL queries that will be used for lookups and updates are defined
|
|
* here. They will be processed into queries by the build_query()
|
|
* function.
|
|
*
|
|
* NOTE: Despite appearances, these do NOT use printf-style formatting.
|
|
* "%s", with no modifiers, is the only supported directive.
|
|
*/
|
|
|
|
/*
|
|
* Get the NS RRset for a zone
|
|
* Arguments: zone-name
|
|
*/
|
|
#define Q_GETNS \
|
|
"SELECT d.data FROM ZoneData d, Zones z " \
|
|
"WHERE UPPER(d.type) = 'NS' AND LOWER(z.domain) = LOWER('%s') " \
|
|
"AND z.id = d.zone_id"
|
|
|
|
/*
|
|
* Get a list of zones (ignoring writable or not)
|
|
* Arguments: (none)
|
|
*/
|
|
#define Q_GETZONES "SELECT LOWER(domain), serial FROM Zones"
|
|
|
|
/*
|
|
* Find a specific zone
|
|
* Arguments: zone-name
|
|
*/
|
|
#define Q_FINDZONE \
|
|
"SELECT id FROM Zones WHERE LOWER(domain) = LOWER('%s')"
|
|
|
|
/*
|
|
* Get SOA data from zone apex
|
|
* Arguments: zone-name
|
|
*/
|
|
#define Q_GETSOA \
|
|
"SELECT host, admin, serial, refresh, retry, expire, minimum, ttl " \
|
|
"FROM Zones WHERE LOWER(domain) = LOWER('%s')"
|
|
|
|
/*
|
|
* Get other data from zone apex
|
|
* Arguments: zone-name, zone-name (repeated)
|
|
*/
|
|
#define Q_GETAPEX \
|
|
"SELECT d.type, d.data, d.ttl FROM ZoneData d, Zones z " \
|
|
"WHERE LOWER(z.domain) = LOWER('%s') AND z.id = d.zone_id " \
|
|
"AND LOWER(d.name) IN (LOWER('%s'), '', '@') "\
|
|
"ORDER BY UPPER(d.type) ASC"
|
|
|
|
/*
|
|
* Get data from non-apex nodes
|
|
* Arguments: zone-name, node-name (relative to zone name)
|
|
*/
|
|
#define Q_GETNODE \
|
|
"SELECT d.type, d.data, d.ttl FROM ZoneData d, Zones z " \
|
|
"WHERE LOWER(z.domain) = LOWER('%s') AND z.id = d.zone_id " \
|
|
"AND LOWER(d.name) = LOWER('%s') " \
|
|
"ORDER BY UPPER(d.type) ASC"
|
|
|
|
/*
|
|
* Get all data from a zone, for AXFR
|
|
* Arguments: zone-name
|
|
*/
|
|
#define Q_GETALL \
|
|
"SELECT d.name, d.type, d.data, d.ttl FROM ZoneData d, Zones z " \
|
|
"WHERE LOWER(z.domain) = LOWER('%s') AND z.id = d.zone_id"
|
|
|
|
/*
|
|
* Get SOA serial number for a zone.
|
|
* Arguments: zone-name
|
|
*/
|
|
#define Q_GETSERIAL \
|
|
"SELECT serial FROM Zones WHERE domain = '%s'"
|
|
|
|
/*
|
|
* Determine whether a zone is writeable, and if so, retrieve zone_id
|
|
* Arguments: zone-name
|
|
*/
|
|
#define Q_WRITEABLE \
|
|
"SELECT id FROM Zones WHERE " \
|
|
"LOWER(domain) = LOWER('%s') AND writeable = 1"
|
|
|
|
/*
|
|
* Insert data into zone (other than SOA)
|
|
* Arguments: zone-id (from Q_WRITEABLE), node-name (relative to zone-name),
|
|
* rrtype, rdata text, TTL (in text format)
|
|
*/
|
|
#define I_DATA \
|
|
"INSERT INTO ZoneData (zone_id, name, type, data, ttl) " \
|
|
"VALUES (%s, LOWER('%s'), UPPER('%s'), '%s', %s)"
|
|
|
|
/*
|
|
* Update SOA serial number for a zone
|
|
* Arguments: new serial number (in text format), zone-id (from Q_WRITEABLE)
|
|
*/
|
|
#define U_SERIAL \
|
|
"UPDATE Zones SET serial = %s WHERE id = %s"
|
|
|
|
/*
|
|
* Delete a specific record (non-SOA) from a zone
|
|
*
|
|
* Arguments: node-name (relative to zone-name), zone-id (from Q_WRITEABLE),
|
|
* rrtype, rdata text, TTL (in text format).
|
|
*/
|
|
#define D_RECORD \
|
|
"DELETE FROM ZoneData WHERE zone_id = %s AND " \
|
|
"LOWER(name) = LOWER('%s') AND UPPER(type) = UPPER('%s') AND " \
|
|
"data = '%s' AND ttl = %s"
|
|
|
|
/*
|
|
* Delete an entire rrset from a zone
|
|
* Arguments: node-name (relative to zone-name), zone-id (from Q_WRITEABLE),
|
|
* rrtype.
|
|
*/
|
|
#define D_RRSET \
|
|
"DELETE FROM ZoneData WHERE zone_id = %s AND " \
|
|
"LOWER(name) = LOWER('%s') AND UPPER(type) = UPPER('%s')"
|
|
|
|
#ifdef WIN32
|
|
#define STRTOK_R(a, b, c) strtok_s(a, b, c)
|
|
#elif defined(_REENTRANT)
|
|
#define STRTOK_R(a, b, c) strtok_r(a, b, c)
|
|
#else
|
|
#define STRTOK_R(a, b, c) strtok(a, b)
|
|
#endif
|
|
|
|
/*
|
|
* Number of concurrent database connections we support
|
|
* - equivalent to maxmium number of concurrent transactions
|
|
* that can be 'in-flight' + 1
|
|
*/
|
|
#define MAX_DBI 16
|
|
|
|
typedef struct mysql_record {
|
|
char zone[255];
|
|
char name[100];
|
|
char type[10];
|
|
char data[200];
|
|
char ttl[10];
|
|
} mysql_record_t;
|
|
|
|
typedef struct mysql_instance {
|
|
int id;
|
|
MYSQL *sock;
|
|
int connected;
|
|
dlz_mutex_t mutex;
|
|
} mysql_instance_t;
|
|
|
|
typedef struct mysql_transaction mysql_transaction_t;
|
|
struct mysql_transaction {
|
|
char *zone;
|
|
char *zone_id;
|
|
mysql_instance_t *dbi;
|
|
mysql_transaction_t *next;
|
|
};
|
|
|
|
typedef struct mysql_data {
|
|
int debug;
|
|
|
|
/*
|
|
* Database connection details
|
|
*/
|
|
char *db_name;
|
|
char *db_host;
|
|
char *db_user;
|
|
char *db_pass;
|
|
|
|
/*
|
|
* Database structures
|
|
*/
|
|
mysql_instance_t db[MAX_DBI];
|
|
|
|
/*
|
|
* Transactions
|
|
*/
|
|
mysql_transaction_t *transactions;
|
|
|
|
/*
|
|
* Mutex for transactions
|
|
*/
|
|
dlz_mutex_t tx_mutex;
|
|
|
|
/* 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_data_t;
|
|
|
|
typedef struct mysql_arg mysql_arg_t;
|
|
typedef DLZ_LIST(mysql_arg_t) mysql_arglist_t;
|
|
struct mysql_arg {
|
|
char *arg;
|
|
DLZ_LINK(mysql_arg_t) link;
|
|
};
|
|
|
|
static const char *modname = "dlz_mysqldyn";
|
|
|
|
/*
|
|
* Local functions
|
|
*/
|
|
static isc_boolean_t
|
|
db_connect(mysql_data_t *state, mysql_instance_t *dbi) {
|
|
MYSQL *conn;
|
|
/*
|
|
* Make sure this thread has been through 'init'
|
|
*/
|
|
mysql_thread_init();
|
|
|
|
if (dbi->connected)
|
|
return (ISC_TRUE);
|
|
|
|
if (state->log != NULL)
|
|
state->log(ISC_LOG_INFO, "%s: init connection %d ",
|
|
modname, dbi->id);
|
|
|
|
conn = mysql_real_connect(dbi->sock, state->db_host,
|
|
state->db_user, state->db_pass,
|
|
state->db_name, 0, NULL,
|
|
CLIENT_REMEMBER_OPTIONS);
|
|
if (conn == NULL) {
|
|
if (state->log != NULL)
|
|
state->log(ISC_LOG_ERROR,
|
|
"%s: database connection failed: %s",
|
|
modname, mysql_error(dbi->sock));
|
|
|
|
dlz_mutex_unlock(&dbi->mutex);
|
|
return (ISC_FALSE);
|
|
}
|
|
|
|
dbi->connected = 1;
|
|
return (ISC_TRUE);
|
|
}
|
|
|
|
static mysql_instance_t *
|
|
get_dbi(mysql_data_t *state) {
|
|
int i;
|
|
|
|
/*
|
|
* Find an available dbi
|
|
*/
|
|
for (i = 0; i < MAX_DBI; i++) {
|
|
if (dlz_mutex_trylock(&state->db[i].mutex) == 0)
|
|
break;
|
|
}
|
|
|
|
if (i == MAX_DBI) {
|
|
if (state->debug && state->log != NULL)
|
|
state->log(ISC_LOG_ERROR,
|
|
"%s: No available connections", modname);
|
|
return (NULL);
|
|
}
|
|
return (&state->db[i]);
|
|
}
|
|
|
|
/*
|
|
* Allocate memory and store an escaped, sanitized version
|
|
* of string 'original'
|
|
*/
|
|
static char *
|
|
sanitize(mysql_instance_t *dbi, const char *original) {
|
|
char *s;
|
|
|
|
if (original == NULL)
|
|
return (NULL);
|
|
|
|
s = (char *) malloc((strlen(original) * 2) + 1);
|
|
if (s != NULL) {
|
|
memset(s, 0, (strlen(original) * 2) + 1);
|
|
|
|
mysql_real_escape_string(dbi->sock, s, original,
|
|
strlen(original));
|
|
}
|
|
|
|
return (s);
|
|
}
|
|
|
|
/*
|
|
* Append the string pointed to by 's' to the argument list 'arglist',
|
|
* and add the string length to the running total pointed to by 'len'.
|
|
*/
|
|
static isc_result_t
|
|
additem(mysql_arglist_t *arglist, char **s, size_t *len) {
|
|
mysql_arg_t *item;
|
|
|
|
item = malloc(sizeof(*item));
|
|
if (item == NULL)
|
|
return (ISC_R_NOMEMORY);
|
|
|
|
DLZ_LINK_INIT(item, link);
|
|
item->arg = *s;
|
|
*len += strlen(*s);
|
|
DLZ_LIST_APPEND(*arglist, item, link);
|
|
*s = NULL;
|
|
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
* Construct a query string using a variable number of arguments, and
|
|
* save it into newly allocated memory.
|
|
*
|
|
* NOTE: 'command' resembles a printf-style format string, but ONLY
|
|
* supports the "%s" directive with no modifiers of any kind.
|
|
*
|
|
* If 'dbi' is NULL, we attempt to get a temporary database connection;
|
|
* otherwise we use the existing one.
|
|
*/
|
|
static char *
|
|
build_query(mysql_data_t *state, mysql_instance_t *dbi,
|
|
const char *command, ...)
|
|
{
|
|
isc_result_t result;
|
|
isc_boolean_t localdbi = ISC_FALSE;
|
|
mysql_arglist_t arglist;
|
|
mysql_arg_t *item;
|
|
char *p, *q, *tmp = NULL, *querystr = NULL;
|
|
char *query = NULL;
|
|
size_t len = 0;
|
|
va_list ap1;
|
|
|
|
/* Get a DB instance if needed */
|
|
if (dbi == NULL) {
|
|
dbi = get_dbi(state);
|
|
if (dbi == NULL)
|
|
return (NULL);
|
|
localdbi = ISC_TRUE;
|
|
}
|
|
|
|
/* Make sure this instance is connected */
|
|
if (!db_connect(state, dbi))
|
|
goto fail;
|
|
|
|
va_start(ap1, command);
|
|
DLZ_LIST_INIT(arglist);
|
|
q = querystr = strdup(command);
|
|
if (querystr == NULL)
|
|
goto fail;
|
|
|
|
for (;;) {
|
|
if (*q == '\0')
|
|
break;
|
|
|
|
p = strstr(q, "%s");
|
|
if (p != NULL) {
|
|
*p = '\0';
|
|
tmp = strdup(q);
|
|
if (tmp == NULL)
|
|
goto fail;
|
|
|
|
result = additem(&arglist, &tmp, &len);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto fail;
|
|
|
|
tmp = sanitize(dbi, va_arg(ap1, const char *));
|
|
if (tmp == NULL)
|
|
goto fail;
|
|
|
|
result = additem(&arglist, &tmp, &len);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto fail;
|
|
|
|
q = p + 2;
|
|
} else {
|
|
tmp = strdup(q);
|
|
if (tmp == NULL)
|
|
goto fail;
|
|
|
|
result = additem(&arglist, &tmp, &len);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto fail;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (len == 0)
|
|
goto fail;
|
|
|
|
query = malloc(len + 1);
|
|
if (query == NULL)
|
|
goto fail;
|
|
|
|
*query = '\0';
|
|
for (item = DLZ_LIST_HEAD(arglist);
|
|
item != NULL;
|
|
item = DLZ_LIST_NEXT(item, link))
|
|
if (item->arg != NULL)
|
|
strcat(query, item->arg);
|
|
|
|
fail:
|
|
va_end(ap1);
|
|
|
|
for (item = DLZ_LIST_HEAD(arglist);
|
|
item != NULL;
|
|
item = DLZ_LIST_NEXT(item, link))
|
|
{
|
|
if (item->arg != NULL)
|
|
free(item->arg);
|
|
free(item);
|
|
}
|
|
|
|
if (tmp != NULL)
|
|
free(tmp);
|
|
if (querystr != NULL)
|
|
free (querystr);
|
|
|
|
if (dbi != NULL && localdbi)
|
|
dlz_mutex_unlock(&dbi->mutex);
|
|
|
|
return (query);
|
|
}
|
|
|
|
/* Does this name end in a dot? */
|
|
static isc_boolean_t
|
|
isrelative(const char *s) {
|
|
if (s == NULL || s[strlen(s) - 1] == '.')
|
|
return (ISC_FALSE);
|
|
return (ISC_TRUE);
|
|
}
|
|
|
|
/* Return a dot if 's' doesn't already end with one */
|
|
static inline const char *
|
|
dot(const char *s) {
|
|
return (isrelative(s) ? "." : "");
|
|
}
|
|
|
|
/*
|
|
* Generate a full hostname from a (presumably relative) name 'name'
|
|
* and a zone name 'zone'; store the result in 'dest' (which must have
|
|
* enough space).
|
|
*/
|
|
static void
|
|
fqhn(const char *name, const char *zone, char *dest) {
|
|
if (dest == NULL)
|
|
return;
|
|
|
|
if (strlen(name) == 0 || strcmp(name, "@") == 0)
|
|
sprintf(dest, "%s%s", zone, dot(zone));
|
|
else {
|
|
if (isrelative(name))
|
|
sprintf(dest, "%s.%s%s", name, zone, dot(zone));
|
|
else
|
|
strcpy(dest, name);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Names are stored in relative form in ZoneData; this function
|
|
* removes labels matching 'zone' from the end of 'name'.
|
|
*/
|
|
static char *
|
|
relname(const char *name, const char *zone) {
|
|
size_t nlen, zlen;
|
|
const char *p;
|
|
char *new;
|
|
|
|
new = (char *) malloc(strlen(name) + 1);
|
|
if (new == NULL)
|
|
return (NULL);
|
|
|
|
nlen = strlen(name);
|
|
zlen = strlen(zone);
|
|
|
|
if (nlen < zlen) {
|
|
strcpy(new, name);
|
|
return (new);
|
|
} else if (nlen == zlen || strcasecmp(name, zone) == 0) {
|
|
strcpy(new, "@");
|
|
return (new);
|
|
}
|
|
|
|
p = name + nlen - zlen;
|
|
if (strcasecmp(p, zone) != 0 &&
|
|
(zone[zlen - 1] != '.' ||
|
|
strncasecmp(p, zone, zlen - 1) != 0))
|
|
{
|
|
strcpy(new, name);
|
|
return (new);
|
|
}
|
|
|
|
strncpy(new, name, nlen - zlen);
|
|
new[nlen - zlen - 1] = '\0';
|
|
return (new);
|
|
}
|
|
|
|
static isc_result_t
|
|
validate_txn(mysql_data_t *state, mysql_transaction_t *txn) {
|
|
isc_result_t result = ISC_R_FAILURE;
|
|
mysql_transaction_t *txp;
|
|
|
|
dlz_mutex_lock(&state->tx_mutex);
|
|
for (txp = state->transactions; txp != NULL; txp = txp->next) {
|
|
if (txn == txp) {
|
|
result = ISC_R_SUCCESS;
|
|
break;
|
|
}
|
|
}
|
|
dlz_mutex_unlock(&state->tx_mutex);
|
|
|
|
if (result != ISC_R_SUCCESS && state->log != NULL)
|
|
state->log(ISC_LOG_ERROR, "%s: invalid txn %x", modname, txn);
|
|
|
|
return (result);
|
|
}
|
|
|
|
static isc_result_t
|
|
db_execute(mysql_data_t *state, mysql_instance_t *dbi, const char *query) {
|
|
int ret;
|
|
|
|
/* Make sure this instance is connected. */
|
|
if (!db_connect(state, dbi))
|
|
return (ISC_R_FAILURE);
|
|
|
|
ret = mysql_real_query(dbi->sock, query, strlen(query));
|
|
if (ret != 0) {
|
|
if (state->debug && state->log != NULL)
|
|
state->log(ISC_LOG_ERROR,
|
|
"%s: query '%s' failed: %s",
|
|
modname, query, mysql_error(dbi->sock));
|
|
return (ISC_R_FAILURE);
|
|
}
|
|
|
|
if (state->debug && state->log != NULL)
|
|
state->log(ISC_LOG_INFO, "%s: execute(%d) %s",
|
|
modname, dbi->id, query);
|
|
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
static MYSQL_RES *
|
|
db_query(mysql_data_t *state, mysql_instance_t *dbi, const char *query) {
|
|
isc_result_t result;
|
|
isc_boolean_t localdbi = ISC_FALSE;
|
|
MYSQL_RES *res = NULL;
|
|
|
|
if (query == NULL)
|
|
return (NULL);
|
|
|
|
/* Get a DB instance if needed */
|
|
if (dbi == NULL) {
|
|
dbi = get_dbi(state);
|
|
if (dbi == NULL)
|
|
return (NULL);
|
|
localdbi = ISC_TRUE;
|
|
}
|
|
|
|
/* Make sure this instance is connected */
|
|
if (!db_connect(state, dbi))
|
|
goto fail;
|
|
|
|
result = db_execute(state, dbi, query);
|
|
if (result != ISC_R_SUCCESS)
|
|
goto fail;
|
|
|
|
res = mysql_store_result(dbi->sock);
|
|
if (res == NULL) {
|
|
if (state->log != NULL)
|
|
state->log(ISC_LOG_ERROR,
|
|
"%s: unable to store result: %s",
|
|
modname, mysql_error(dbi->sock));
|
|
goto fail;
|
|
}
|
|
|
|
if (state->debug && state->log != NULL)
|
|
state->log(ISC_LOG_INFO,
|
|
"%s: query(%d) returned %d rows",
|
|
modname, dbi->id, mysql_num_rows(res));
|
|
|
|
fail:
|
|
if (dbi != NULL && localdbi)
|
|
dlz_mutex_unlock(&dbi->mutex);
|
|
return (res);
|
|
}
|
|
|
|
/*
|
|
* Generate a DNS NOTIFY packet:
|
|
* 12 bytes header
|
|
* Question (1)
|
|
* strlen(zone) +2
|
|
* 2 bytes qtype
|
|
* 2 bytes qclass
|
|
*
|
|
* -> 18 bytes + strlen(zone)
|
|
*
|
|
* N.B. Need to be mindful of byte ordering; using htons to map 16bit
|
|
* values to the 'on the wire' packet values.
|
|
*/
|
|
static unsigned char *
|
|
make_notify(const char *zone, int *packetlen) {
|
|
int i, j;
|
|
unsigned char *packet = (unsigned char *) malloc(strlen(zone) + 18);
|
|
|
|
if (packet == NULL)
|
|
return (NULL);
|
|
|
|
*packetlen = strlen(zone) + 18;
|
|
memset(packet, 0, *packetlen);
|
|
|
|
/* Random query ID */
|
|
i = rand();
|
|
packet[0] = htons(i) & 0xff;
|
|
packet[1] = htons(i) >> 8;
|
|
|
|
/* Flags (OpCode '4' in bits 14-11), Auth Answer set in bit 10 */
|
|
i = 0x2400;
|
|
packet[2] = htons(i) & 0xff;
|
|
packet[3] = htons(i) >> 8;
|
|
|
|
/* QD Count */
|
|
i = 0x1;
|
|
packet[4] = htons(i) & 0xff;
|
|
packet[5] = htons(i) >> 8;
|
|
|
|
/* Question */
|
|
packet[12] = '.';
|
|
memcpy(&packet[13], zone, strlen(zone));
|
|
packet[13 + strlen(zone)] = 0;
|
|
|
|
/* Make the question into labels */
|
|
j = 12;
|
|
while (packet[j]) {
|
|
for (i = j + 1; packet[i] != '\0' && packet[i] != '.'; i++);
|
|
packet[j] = i - j - 1;
|
|
j = i;
|
|
}
|
|
|
|
/* Question type */
|
|
i = 6;
|
|
packet[j + 1] = htons(i) & 0xff;
|
|
packet[j + 2] = htons(i) >> 8;
|
|
|
|
/* Queston class */
|
|
i = 1;
|
|
packet[j + 3] = htons(i) & 0xff;
|
|
packet[j + 4] = htons(i) >> 8;
|
|
|
|
return (packet);
|
|
}
|
|
|
|
static void
|
|
send_notify(struct sockaddr_in *addr, const unsigned char *p, const int plen) {
|
|
int s;
|
|
|
|
addr->sin_family = AF_INET;
|
|
addr->sin_port = htons(53);
|
|
|
|
if ((s = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
|
|
return;
|
|
|
|
sendto(s, p, plen, 0, (struct sockaddr *)addr, sizeof(*addr));
|
|
close(s);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Generate and send a DNS NOTIFY packet
|
|
*/
|
|
static void
|
|
notify(mysql_data_t *state, const char *zone, int sn) {
|
|
MYSQL_RES *res;
|
|
MYSQL_ROW row;
|
|
char *query;
|
|
unsigned char *packet;
|
|
int packetlen;
|
|
struct ifaddrs *ifap, *ifa;
|
|
char zaddr[INET_ADDRSTRLEN];
|
|
void *addrp = NULL;
|
|
|
|
/* Get the name servers from the NS rrset */
|
|
query = build_query(state, NULL, Q_GETNS, zone);
|
|
res = db_query(state, NULL, query);
|
|
free (query);
|
|
if (res == NULL)
|
|
return;
|
|
|
|
/* Create a DNS NOTIFY packet */
|
|
packet = make_notify(zone, &packetlen);
|
|
if (packet == NULL) {
|
|
mysql_free_result(res);
|
|
return;
|
|
}
|
|
|
|
/* Get a list of our own addresses */
|
|
if (getifaddrs(&ifap) < 0)
|
|
ifap = NULL;
|
|
|
|
/* Tell each nameserver of the update */
|
|
while ((row = mysql_fetch_row(res)) != NULL) {
|
|
isc_boolean_t local = ISC_FALSE;
|
|
struct hostent *h;
|
|
struct sockaddr_in addr, *sin;
|
|
|
|
/*
|
|
* Put nameserver rdata through gethostbyname as it
|
|
* might be an IP address or a hostname. (XXX: switch
|
|
* this to inet_pton/getaddrinfo.)
|
|
*/
|
|
h = gethostbyname(row[0]);
|
|
if (h == NULL)
|
|
continue;
|
|
|
|
memcpy(&addr.sin_addr, h->h_addr, h->h_length);
|
|
addrp = &addr.sin_addr;
|
|
|
|
/* Get the address for the nameserver into a string */
|
|
inet_ntop(AF_INET, addrp, zaddr, INET_ADDRSTRLEN);
|
|
for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
|
|
char ifaddr[INET_ADDRSTRLEN];
|
|
|
|
if (ifa->ifa_addr->sa_family != AF_INET)
|
|
continue;
|
|
|
|
/* Get local address into a string */
|
|
sin = (struct sockaddr_in *) ifa->ifa_addr;
|
|
addrp = &sin->sin_addr;
|
|
inet_ntop(AF_INET, addrp, ifaddr, INET_ADDRSTRLEN);
|
|
|
|
/* See if nameserver address matches this one */
|
|
if (strcmp(ifaddr, zaddr) == 0)
|
|
local = ISC_TRUE;
|
|
}
|
|
|
|
if (!local) {
|
|
if (state->log != NULL)
|
|
state->log(ISC_LOG_INFO,
|
|
"%s: notify %s zone %s serial %d",
|
|
modname, row[0], zone, sn);
|
|
send_notify(&addr, packet, packetlen);
|
|
}
|
|
}
|
|
|
|
mysql_free_result(res);
|
|
free(packet);
|
|
if (ifap != NULL)
|
|
freeifaddrs(ifap);
|
|
}
|
|
|
|
/*
|
|
* Constructs a mysql_record_t structure from 'rdatastr', to be
|
|
* used in the dlz_{add,sub,del}rdataset functions below.
|
|
*/
|
|
static mysql_record_t *
|
|
makerecord(mysql_data_t *state, const char *name, const char *rdatastr) {
|
|
mysql_record_t *new_record;
|
|
char *real_name, *dclass, *type, *data, *ttlstr, *buf;
|
|
dns_ttl_t ttlvalue;
|
|
|
|
new_record = (mysql_record_t *)
|
|
malloc(sizeof(mysql_record_t));
|
|
|
|
if (new_record == NULL) {
|
|
if (state->log != NULL)
|
|
state->log(ISC_LOG_ERROR,
|
|
"%s: makerecord - unable to malloc",
|
|
modname);
|
|
return (NULL);
|
|
}
|
|
|
|
buf = strdup(rdatastr);
|
|
if (buf == NULL) {
|
|
if (state->log != NULL)
|
|
state->log(ISC_LOG_ERROR,
|
|
"%s: makerecord - unable to malloc",
|
|
modname);
|
|
free(new_record);
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* The format is:
|
|
* FULLNAME\tTTL\tDCLASS\tTYPE\tDATA
|
|
*
|
|
* The DATA field is space separated, and is in the data format
|
|
* for the type used by dig
|
|
*/
|
|
real_name = STRTOK_R(buf, "\t", &saveptr);
|
|
if (real_name == NULL)
|
|
goto error;
|
|
|
|
ttlstr = STRTOK_R(NULL, "\t", &saveptr);
|
|
if (ttlstr == NULL || sscanf(ttlstr, "%d", &ttlvalue) != 1)
|
|
goto error;
|
|
|
|
dclass = STRTOK_R(NULL, "\t", &saveptr);
|
|
if (dclass == NULL)
|
|
goto error;
|
|
|
|
type = STRTOK_R(NULL, "\t", &saveptr);
|
|
if (type == NULL)
|
|
goto error;
|
|
|
|
data = STRTOK_R(NULL, "\t", &saveptr);
|
|
if (data == NULL)
|
|
goto error;
|
|
|
|
strcpy(new_record->name, name);
|
|
strcpy(new_record->type, type);
|
|
strcpy(new_record->data, data);
|
|
sprintf(new_record->ttl, "%d", ttlvalue);
|
|
|
|
free(buf);
|
|
return (new_record);
|
|
|
|
error:
|
|
free(buf);
|
|
free(new_record);
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Remember a helper function from the bind9 dlz_dlopen driver
|
|
*/
|
|
static void
|
|
b9_add_helper(mysql_data_t *state, const char *helper_name, void *ptr) {
|
|
if (strcmp(helper_name, "log") == 0)
|
|
state->log = (log_t *)ptr;
|
|
if (strcmp(helper_name, "putrr") == 0)
|
|
state->putrr = (dns_sdlz_putrr_t *)ptr;
|
|
if (strcmp(helper_name, "putnamedrr") == 0)
|
|
state->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr;
|
|
if (strcmp(helper_name, "writeable_zone") == 0)
|
|
state->writeable_zone = (dns_dlz_writeablezone_t *)ptr;
|
|
}
|
|
|
|
/*
|
|
* DLZ API functions
|
|
*/
|
|
|
|
/*
|
|
* Return the version of the API
|
|
*/
|
|
int
|
|
dlz_version(unsigned int *flags) {
|
|
UNUSED(flags);
|
|
*flags |= DNS_SDLZFLAG_THREADSAFE;
|
|
return (DLZ_DLOPEN_VERSION);
|
|
}
|
|
|
|
/*
|
|
* Called to initialize the driver
|
|
*/
|
|
isc_result_t
|
|
dlz_create(const char *dlzname, unsigned int argc, char *argv[],
|
|
void **dbdata, ...)
|
|
{
|
|
mysql_data_t *state;
|
|
const char *helper_name;
|
|
va_list ap;
|
|
int n;
|
|
|
|
UNUSED(dlzname);
|
|
|
|
state = calloc(1, sizeof(mysql_data_t));
|
|
if (state == NULL)
|
|
return (ISC_R_NOMEMORY);
|
|
|
|
dlz_mutex_init(&state->tx_mutex, NULL);
|
|
state->transactions = NULL;
|
|
|
|
/* Fill in the helper functions */
|
|
va_start(ap, dbdata);
|
|
while ((helper_name = va_arg(ap, const char *)) != NULL)
|
|
b9_add_helper(state, helper_name, va_arg(ap, void *));
|
|
va_end(ap);
|
|
|
|
if (state->log != NULL)
|
|
state->log(ISC_LOG_INFO, "loading %s module", modname);
|
|
|
|
if ((argc < 2) || (argc > 6)) {
|
|
if (state->log != NULL)
|
|
state->log(ISC_LOG_ERROR,
|
|
"%s: missing args <dbname> "
|
|
"[<dbhost> [<user> <pass>]]", modname);
|
|
dlz_destroy(state);
|
|
return (ISC_R_FAILURE);
|
|
}
|
|
|
|
state->db_name = strdup(argv[1]);
|
|
if (argc > 2) {
|
|
state->db_host = strdup(argv[2]);
|
|
if (argc > 4) {
|
|
state->db_user = strdup(argv[3]);
|
|
state->db_pass = strdup(argv[4]);
|
|
} else {
|
|
state->db_user = strdup("bind");
|
|
state->db_pass = strdup("");
|
|
}
|
|
} else {
|
|
state->db_host = strdup("localhost");
|
|
state->db_user = strdup("bind");
|
|
state->db_pass = strdup("");
|
|
}
|
|
|
|
if (state->log != NULL)
|
|
state->log(ISC_LOG_INFO,
|
|
"%s: DB=%s, Host=%s, User=%s",
|
|
modname, state->db_name,
|
|
state->db_host, state->db_user);
|
|
|
|
/*
|
|
* Assign the 'state' to dbdata so we get it in our callbacks
|
|
*/
|
|
|
|
|
|
dlz_mutex_lock(&state->tx_mutex);
|
|
|
|
/*
|
|
* Populate DB instances
|
|
*/
|
|
if (mysql_thread_safe()) {
|
|
for (n = 0; n < MAX_DBI; n++) {
|
|
my_bool opt = 1;
|
|
dlz_mutex_init(&state->db[n].mutex, NULL);
|
|
dlz_mutex_lock(&state->db[n].mutex);
|
|
state->db[n].id = n;
|
|
state->db[n].connected = 0;
|
|
state->db[n].sock = mysql_init(NULL);
|
|
mysql_options(state->db[n].sock,
|
|
MYSQL_READ_DEFAULT_GROUP,
|
|
modname);
|
|
mysql_options(state->db[n].sock,
|
|
MYSQL_OPT_RECONNECT, &opt);
|
|
dlz_mutex_unlock(&state->db[n].mutex);
|
|
}
|
|
|
|
*dbdata = state;
|
|
dlz_mutex_unlock(&state->tx_mutex);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
free(state->db_name);
|
|
free(state->db_host);
|
|
free(state->db_user);
|
|
free(state->db_pass);
|
|
dlz_mutex_destroy(&state->tx_mutex);
|
|
free(state);
|
|
return (ISC_R_FAILURE);
|
|
}
|
|
|
|
/*
|
|
* Shut down the backend
|
|
*/
|
|
void
|
|
dlz_destroy(void *dbdata) {
|
|
mysql_data_t *state = (mysql_data_t *)dbdata;
|
|
int i;
|
|
|
|
if (state->debug && state->log != NULL)
|
|
state->log(ISC_LOG_INFO, "%s: shutting down", modname);
|
|
|
|
for (i = 0; i < MAX_DBI; i++) {
|
|
if (state->db[i].sock) {
|
|
mysql_close(state->db[i].sock);
|
|
state->db[i].sock=NULL;
|
|
}
|
|
}
|
|
free(state->db_name);
|
|
free(state->db_host);
|
|
free(state->db_user);
|
|
free(state->db_pass);
|
|
dlz_mutex_destroy(&state->tx_mutex);
|
|
free(state);
|
|
}
|
|
|
|
/*
|
|
* See if we handle a given zone
|
|
*/
|
|
isc_result_t
|
|
dlz_findzonedb(void *dbdata, const char *name,
|
|
dns_clientinfomethods_t *methods,
|
|
dns_clientinfo_t *clientinfo)
|
|
{
|
|
isc_result_t result = ISC_R_SUCCESS;
|
|
mysql_data_t *state = (mysql_data_t *)dbdata;
|
|
MYSQL_RES *res;
|
|
char *query;
|
|
|
|
/* Query the Zones table to see if this zone is present */
|
|
query = build_query(state, NULL, Q_FINDZONE, name);
|
|
|
|
if (query == NULL)
|
|
return (ISC_R_NOMEMORY);
|
|
|
|
res = db_query(state, NULL, query);
|
|
if (res == NULL)
|
|
return (ISC_R_FAILURE);
|
|
|
|
if (mysql_num_rows(res) == 0)
|
|
result = ISC_R_NOTFOUND;
|
|
|
|
mysql_free_result(res);
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Perform a database lookup
|
|
*/
|
|
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_data_t *state = (mysql_data_t *)dbdata;
|
|
isc_boolean_t found = ISC_FALSE;
|
|
char *real_name;
|
|
MYSQL_RES *res;
|
|
MYSQL_ROW row;
|
|
char *query;
|
|
mysql_transaction_t *txn = NULL;
|
|
mysql_instance_t *dbi = NULL;
|
|
|
|
if (state->putrr == NULL) {
|
|
if (state->log != NULL)
|
|
state->log(ISC_LOG_ERROR,
|
|
"%s: dlz_lookup - no putrr", modname);
|
|
return (ISC_R_NOTIMPLEMENTED);
|
|
}
|
|
|
|
/* Are we okay to try to find the txn version? */
|
|
if (clientinfo != NULL &&
|
|
clientinfo->version >= DNS_CLIENTINFO_VERSION) {
|
|
txn = (mysql_transaction_t *) clientinfo->dbversion;
|
|
if (txn != NULL && validate_txn(state, txn) == ISC_R_SUCCESS)
|
|
dbi = txn->dbi;
|
|
if (dbi != NULL) {
|
|
state->log(ISC_LOG_DEBUG(1),
|
|
"%s: lookup in live transaction %p, DBI %p",
|
|
modname, txn, dbi);
|
|
}
|
|
}
|
|
|
|
if (strcmp(name, "@") == 0) {
|
|
real_name = (char *) malloc(strlen(zone) + 1);
|
|
if (real_name == NULL)
|
|
return (ISC_R_NOMEMORY);
|
|
strcpy(real_name, zone);
|
|
} else {
|
|
real_name = (char *) malloc(strlen(name) + 1);
|
|
if (real_name == NULL)
|
|
return (ISC_R_NOMEMORY);
|
|
strcpy(real_name, name);
|
|
}
|
|
|
|
if (strcmp(real_name, zone) == 0) {
|
|
/*
|
|
* Get the Zones table data for use in the SOA:
|
|
* zone admin serial refresh retry expire min
|
|
*/
|
|
query = build_query(state, dbi, Q_GETSOA, zone);
|
|
if (query == NULL) {
|
|
free(real_name);
|
|
return (ISC_R_NOMEMORY);
|
|
}
|
|
|
|
res = db_query(state, dbi, query);
|
|
free (query);
|
|
|
|
if (res == NULL) {
|
|
free(real_name);
|
|
return (ISC_R_NOTFOUND);
|
|
}
|
|
|
|
while ((row = mysql_fetch_row(res)) != NULL) {
|
|
char host[1024], admin[1024], data[1024];
|
|
int ttl;
|
|
|
|
sscanf(row[7], "%d", &ttl);
|
|
fqhn(row[0], zone, host);
|
|
fqhn(row[1], zone, admin);
|
|
|
|
/* zone admin serial refresh retry expire min */
|
|
sprintf(data, "%s%s %s%s %s %s %s %s %s",
|
|
host, dot(host), admin, dot(admin),
|
|
row[2], row[3], row[4], row[5], row[6]);
|
|
|
|
result = state->putrr(lookup, "soa", ttl, data);
|
|
if (result != ISC_R_SUCCESS) {
|
|
free(real_name);
|
|
mysql_free_result(res);
|
|
return (result);
|
|
}
|
|
}
|
|
|
|
mysql_free_result(res);
|
|
|
|
/*
|
|
* Now we'll get the rest of the apex data
|
|
*/
|
|
query = build_query(state, dbi, Q_GETAPEX, zone, real_name);
|
|
} else
|
|
query = build_query(state, dbi, Q_GETNODE, zone, real_name);
|
|
|
|
res = db_query(state, dbi, query);
|
|
free(query);
|
|
|
|
if (res == NULL) {
|
|
free(real_name);
|
|
return (ISC_R_NOTFOUND);
|
|
}
|
|
|
|
while ((row = mysql_fetch_row(res)) != NULL) {
|
|
int ttl;
|
|
sscanf(row[2], "%d", &ttl);
|
|
result = state->putrr(lookup, row[0], ttl, row[1]);
|
|
if (result != ISC_R_SUCCESS) {
|
|
free(real_name);
|
|
mysql_free_result(res);
|
|
return (result);
|
|
}
|
|
|
|
found = ISC_TRUE;
|
|
}
|
|
|
|
if (state->debug && state->log != NULL)
|
|
state->log(ISC_LOG_INFO,
|
|
"%s: dlz_lookup %s/%s/%s - (%d rows)",
|
|
modname, name, real_name, zone,
|
|
mysql_num_rows(res));
|
|
|
|
mysql_free_result(res);
|
|
free(real_name);
|
|
|
|
if (!found)
|
|
return (ISC_R_NOTFOUND);
|
|
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
* See if a zone transfer is allowed
|
|
*/
|
|
isc_result_t
|
|
dlz_allowzonexfr(void *dbdata, const char *name, const char *client) {
|
|
mysql_data_t *state = (mysql_data_t *)dbdata;
|
|
|
|
if (state->debug && state->log != NULL)
|
|
state->log(ISC_LOG_INFO,
|
|
"dlz_allowzonexfr: %s %s", name, client);
|
|
|
|
/* Just say yes for all our zones */
|
|
return (dlz_findzonedb(dbdata, name, NULL, NULL));
|
|
}
|
|
|
|
/*
|
|
* Perform a zone transfer
|
|
*/
|
|
isc_result_t
|
|
dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) {
|
|
isc_result_t result = ISC_R_SUCCESS;
|
|
mysql_data_t *state = (mysql_data_t *)dbdata;
|
|
MYSQL_RES *res;
|
|
MYSQL_ROW row;
|
|
char *query;
|
|
|
|
UNUSED(zone);
|
|
|
|
if (state->debug && (state->log != NULL))
|
|
state->log(ISC_LOG_INFO, "dlz_allnodes: %s", zone);
|
|
|
|
if (state->putnamedrr == NULL)
|
|
return (ISC_R_NOTIMPLEMENTED);
|
|
|
|
/*
|
|
* Get all the ZoneData for this zone
|
|
*/
|
|
query = build_query(state, NULL, Q_GETALL, zone);
|
|
if (query == NULL)
|
|
return (ISC_R_NOMEMORY);
|
|
|
|
res = db_query(state, NULL, query);
|
|
free(query);
|
|
if (res == NULL)
|
|
return (ISC_R_NOTFOUND);
|
|
|
|
while ((row = mysql_fetch_row(res)) != NULL) {
|
|
char hostname[1024];
|
|
int ttl;
|
|
|
|
sscanf(row[3], "%d", &ttl);
|
|
fqhn(row[0], zone, hostname);
|
|
result = state->putnamedrr(allnodes, hostname,
|
|
row[1], ttl, row[2]);
|
|
if (result != ISC_R_SUCCESS)
|
|
break;
|
|
}
|
|
|
|
mysql_free_result(res);
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* Start a transaction
|
|
*/
|
|
isc_result_t
|
|
dlz_newversion(const char *zone, void *dbdata, void **versionp) {
|
|
isc_result_t result = ISC_R_FAILURE;
|
|
mysql_data_t *state = (mysql_data_t *) dbdata;
|
|
MYSQL_RES *res;
|
|
MYSQL_ROW row;
|
|
char *query;
|
|
char zone_id[16];
|
|
mysql_transaction_t *txn = NULL, *newtx = NULL;
|
|
|
|
/*
|
|
* Check Zone is writable
|
|
*/
|
|
query = build_query(state, NULL, Q_WRITEABLE, zone);
|
|
if (query == NULL)
|
|
return (ISC_R_NOMEMORY);
|
|
|
|
res = db_query(state, NULL, query);
|
|
free(query);
|
|
if (res == NULL)
|
|
return (ISC_R_FAILURE);
|
|
|
|
if ((row = mysql_fetch_row(res)) == NULL) {
|
|
mysql_free_result(res);
|
|
return (ISC_R_FAILURE);
|
|
}
|
|
|
|
strcpy(zone_id, row[0]);
|
|
mysql_free_result(res);
|
|
|
|
/*
|
|
* See if we already have a transaction for this zone
|
|
*/
|
|
dlz_mutex_lock(&state->tx_mutex);
|
|
for (txn = state->transactions; txn != NULL; txn = txn->next) {
|
|
if (strcmp(txn->zone, zone) == 0) {
|
|
if (state->log != NULL)
|
|
state->log(ISC_LOG_ERROR,
|
|
"%s: transaction already "
|
|
"started for zone %s", modname, zone);
|
|
dlz_mutex_unlock(&state->tx_mutex);
|
|
return (ISC_R_FAILURE);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Create new transaction
|
|
*/
|
|
newtx = (mysql_transaction_t *)
|
|
malloc(sizeof(mysql_transaction_t));
|
|
newtx->zone = strdup(zone);
|
|
newtx->zone_id = strdup(zone_id);
|
|
newtx->dbi = get_dbi(state);
|
|
newtx->next = NULL;
|
|
|
|
if (newtx->dbi == NULL) {
|
|
result = ISC_R_FAILURE;
|
|
goto cleanup;
|
|
}
|
|
|
|
result = db_execute(state, newtx->dbi, "START TRANSACTION");
|
|
if (result != ISC_R_SUCCESS) {
|
|
dlz_mutex_unlock(&newtx->dbi->mutex);
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* Add this tx to front of list
|
|
*/
|
|
newtx->next = state->transactions;
|
|
state->transactions = newtx;
|
|
|
|
if (state->debug && (state->log != NULL))
|
|
state->log(ISC_LOG_INFO, "%s: New tx %x", modname, newtx);
|
|
|
|
cleanup:
|
|
dlz_mutex_unlock(&state->tx_mutex);
|
|
if (result == ISC_R_SUCCESS) {
|
|
*versionp = (void *) newtx;
|
|
} else {
|
|
dlz_mutex_unlock(&state->tx_mutex);
|
|
free(newtx->zone);
|
|
free(newtx->zone_id);
|
|
free(newtx);
|
|
}
|
|
|
|
return (result);
|
|
}
|
|
|
|
/*
|
|
* End a transaction
|
|
*/
|
|
void
|
|
dlz_closeversion(const char *zone, isc_boolean_t commit,
|
|
void *dbdata, void **versionp)
|
|
{
|
|
isc_result_t result;
|
|
mysql_data_t *state = (mysql_data_t *)dbdata;
|
|
mysql_transaction_t *txn = (mysql_transaction_t *)*versionp;
|
|
mysql_transaction_t *txp;
|
|
char *query;
|
|
MYSQL_RES *res;
|
|
MYSQL_ROW row;
|
|
|
|
/*
|
|
* Find the transaction
|
|
*/
|
|
dlz_mutex_lock(&state->tx_mutex);
|
|
if (state->transactions == txn) {
|
|
/* Tx is first in list; remove it. */
|
|
state->transactions = txn->next;
|
|
} else {
|
|
txp = state->transactions;
|
|
while (txp != NULL) {
|
|
if (txp->next != NULL) {
|
|
if (txp->next == txn) {
|
|
txp->next = txn->next;
|
|
break;
|
|
}
|
|
}
|
|
if (txp == txn) {
|
|
txp = txn->next;
|
|
break;
|
|
}
|
|
txp = txp->next;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Tidy up
|
|
*/
|
|
dlz_mutex_unlock(&state->tx_mutex);
|
|
*versionp = NULL;
|
|
|
|
if (commit) {
|
|
int oldsn = 0, newsn = 0;
|
|
|
|
/*
|
|
* Find out the serial number of the zone out with the
|
|
* transaction so we can see if it has incremented or not
|
|
*/
|
|
query = build_query(state, txn->dbi, Q_GETSERIAL, zone);
|
|
if (query == NULL && state->log != NULL) {
|
|
state->log(ISC_LOG_ERROR,
|
|
"%s: unable to commit transaction %x "
|
|
"on zone %s: no memory",
|
|
modname, txn, zone);
|
|
return;
|
|
}
|
|
|
|
res = db_query(state, txn->dbi, query);
|
|
if (res != NULL) {
|
|
while ((row = mysql_fetch_row(res)) != NULL)
|
|
sscanf(row[0], "%d", &oldsn);
|
|
mysql_free_result(res);
|
|
}
|
|
|
|
/*
|
|
* Commit the transaction to the database
|
|
*/
|
|
result = db_execute(state, txn->dbi, "COMMIT");
|
|
if (result != ISC_R_SUCCESS && state->log != NULL) {
|
|
state->log(ISC_LOG_INFO,
|
|
"%s: (%x) commit transaction on zone %s",
|
|
modname, txn, zone);
|
|
return;
|
|
}
|
|
|
|
if (state->debug && state->log != NULL)
|
|
state->log(ISC_LOG_INFO,
|
|
"%s: (%x) committing transaction "
|
|
"on zone %s",
|
|
modname, txn, zone);
|
|
|
|
/*
|
|
* Now get the serial number again
|
|
*/
|
|
query = build_query(state, txn->dbi, Q_GETSERIAL, zone);
|
|
res = db_query(state, txn->dbi, query);
|
|
free(query);
|
|
|
|
if (res != NULL) {
|
|
while ((row = mysql_fetch_row(res)) != NULL)
|
|
sscanf(row[0], "%d", &newsn);
|
|
mysql_free_result(res);
|
|
}
|
|
|
|
/*
|
|
* Look to see if serial numbers have changed
|
|
*/
|
|
if (newsn > oldsn)
|
|
notify(state, zone, newsn);
|
|
} else {
|
|
result = db_execute(state, txn->dbi, "ROLLBACK");
|
|
if (state->debug && (state->log != NULL))
|
|
state->log(ISC_LOG_INFO,
|
|
"%s: (%x) roll back transaction on zone %s",
|
|
modname, txn, zone);
|
|
}
|
|
|
|
/*
|
|
* Unlock the mutex for this txn
|
|
*/
|
|
dlz_mutex_unlock(&txn->dbi->mutex);
|
|
|
|
/*
|
|
* Free up other structures
|
|
*/
|
|
free(txn->zone);
|
|
free(txn->zone_id);
|
|
free(txn);
|
|
}
|
|
|
|
/*
|
|
* Configure a writeable zone
|
|
*/
|
|
#if DLZ_DLOPEN_VERSION < 3
|
|
isc_result_t
|
|
dlz_configure(dns_view_t *view, void *dbdata)
|
|
#else /* DLZ_DLOPEN_VERSION >= 3 */
|
|
isc_result_t
|
|
dlz_configure(dns_view_t *view, dns_dlzdb_t *dlzdb, void *dbdata)
|
|
#endif /* DLZ_DLOPEN_VERSION */
|
|
{
|
|
mysql_data_t *state = (mysql_data_t *)dbdata;
|
|
isc_result_t result;
|
|
MYSQL_RES *res;
|
|
MYSQL_ROW row;
|
|
int count;
|
|
|
|
/*
|
|
* Seed PRNG (used by Notify code)
|
|
*/
|
|
srand(getpid());
|
|
|
|
if (state->debug && state->log != NULL)
|
|
state->log(ISC_LOG_INFO, "%s: dlz_confgure", modname);
|
|
|
|
if (state->writeable_zone == NULL) {
|
|
if (state->log != NULL)
|
|
state->log(ISC_LOG_ERROR,
|
|
"%s: no writeable_zone method available",
|
|
modname);
|
|
return (ISC_R_FAILURE);
|
|
}
|
|
|
|
/*
|
|
* Get a list of Zones (ignore writeable column at this point)
|
|
*/
|
|
res = db_query(state, NULL, Q_GETZONES);
|
|
if (res == NULL)
|
|
return (ISC_R_FAILURE);
|
|
|
|
count = 0;
|
|
while ((row = mysql_fetch_row(res)) != NULL) {
|
|
int sn;
|
|
sscanf(row[1], "%d", &sn);
|
|
notify(state, row[0], sn);
|
|
result = state->writeable_zone(view,
|
|
#if DLZ_DLOPEN_VERSION >= 3
|
|
dlzdb,
|
|
#endif
|
|
row[0]);
|
|
if (result != ISC_R_SUCCESS) {
|
|
if (state->log != NULL)
|
|
state->log(ISC_LOG_ERROR,
|
|
"%s: failed to configure zone %s",
|
|
modname, row[0]);
|
|
mysql_free_result(res);
|
|
return (result);
|
|
}
|
|
count++;
|
|
}
|
|
mysql_free_result(res);
|
|
|
|
if (state->debug && state->log != NULL)
|
|
state->log(ISC_LOG_INFO,
|
|
"%s: configured %d zones", modname, count);
|
|
return (ISC_R_SUCCESS);
|
|
}
|
|
|
|
/*
|
|
* Authorize a zone update
|
|
*/
|
|
isc_boolean_t
|
|
dlz_ssumatch(const char *signer, const char *name, const char *tcpaddr,
|
|
const char *type, const char *key, uint32_t keydatalen,
|
|
unsigned char *keydata, void *dbdata)
|
|
{
|
|
mysql_data_t *state = (mysql_data_t *)dbdata;
|
|
|
|
UNUSED(tcpaddr);
|
|
UNUSED(type);
|
|
UNUSED(keydatalen);
|
|
UNUSED(keydata);
|
|
UNUSED(key);
|
|
|
|
if (state->debug && state->log != NULL)
|
|
state->log(ISC_LOG_INFO,
|
|
"%s: allowing update of %s by key %s",
|
|
modname, name, signer);
|
|
return (ISC_TRUE);
|
|
}
|
|
|
|
isc_result_t
|
|
dlz_addrdataset(const char *name, const char *rdatastr,
|
|
void *dbdata, void *version)
|
|
{
|
|
mysql_data_t *state = (mysql_data_t *)dbdata;
|
|
mysql_transaction_t *txn = (mysql_transaction_t *)version;
|
|
char *new_name, *query;
|
|
mysql_record_t *record;
|
|
isc_result_t result;
|
|
|
|
if (txn == NULL)
|
|
return (ISC_R_FAILURE);
|
|
|
|
new_name = relname(name, txn->zone);
|
|
if (new_name == NULL)
|
|
return (ISC_R_NOMEMORY);
|
|
|
|
if (state->debug && (state->log != NULL))
|
|
state->log(ISC_LOG_INFO, "%s: add (%x) %s (as %s) %s",
|
|
modname, version, name, new_name, rdatastr);
|
|
|
|
record = makerecord(state, new_name, rdatastr);
|
|
free(new_name);
|
|
if (record == NULL)
|
|
return (ISC_R_FAILURE);
|
|
|
|
/* Write out data to database */
|
|
if (strcasecmp(record->type, "SOA") != 0) {
|
|
query = build_query(state, txn->dbi, I_DATA,
|
|
txn->zone_id, record->name,
|
|
record->type, record->data,
|
|
record->ttl);
|
|
if (query == NULL) {
|
|
result = ISC_R_FAILURE;
|
|
goto cleanup;
|
|
}
|
|
result = db_execute(state, txn->dbi, query);
|
|
free(query);
|
|
} else {
|
|
/*
|
|
* This is an SOA record, so we update: it must exist,
|
|
* or we wouldn't have gotten this far.
|
|
* SOA: zone admin serial refresh retry expire min
|
|
*/
|
|
char sn[32];
|
|
sscanf(record->data, "%*s %*s %31s %*s %*s %*s %*s", sn);
|
|
query = build_query(state, txn->dbi, U_SERIAL, sn,
|
|
txn->zone_id);
|
|
if (query == NULL) {
|
|
result = ISC_R_FAILURE;
|
|
goto cleanup;
|
|
}
|
|
result = db_execute(state, txn->dbi, query);
|
|
free(query);
|
|
}
|
|
|
|
cleanup:
|
|
free(record);
|
|
return (result);
|
|
}
|
|
|
|
isc_result_t
|
|
dlz_subrdataset(const char *name, const char *rdatastr,
|
|
void *dbdata, void *version)
|
|
{
|
|
mysql_data_t *state = (mysql_data_t *)dbdata;
|
|
mysql_transaction_t *txn = (mysql_transaction_t *)version;
|
|
char *new_name, *query;
|
|
mysql_record_t *record;
|
|
isc_result_t result;
|
|
|
|
if (txn == NULL)
|
|
return (ISC_R_FAILURE);
|
|
|
|
new_name = relname(name, txn->zone);
|
|
if (new_name == NULL)
|
|
return (ISC_R_NOMEMORY);
|
|
|
|
if (state->debug && (state->log != NULL))
|
|
state->log(ISC_LOG_INFO, "%s: sub (%x) %s %s",
|
|
modname, version, name, rdatastr);
|
|
|
|
record = makerecord(state, new_name, rdatastr);
|
|
free(new_name);
|
|
if (record == NULL)
|
|
return (ISC_R_FAILURE);
|
|
/*
|
|
* If 'type' isn't 'SOA', delete the records
|
|
*/
|
|
if (strcasecmp(record->type, "SOA") == 0)
|
|
result = ISC_R_SUCCESS;
|
|
else {
|
|
query = build_query(state, txn->dbi, D_RECORD,
|
|
txn->zone_id, record->name,
|
|
record->type, record->data,
|
|
record->ttl);
|
|
if (query == NULL) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
result = db_execute(state, txn->dbi, query);
|
|
free(query);
|
|
}
|
|
|
|
cleanup:
|
|
free(record);
|
|
return (result);
|
|
}
|
|
|
|
isc_result_t
|
|
dlz_delrdataset(const char *name, const char *type,
|
|
void *dbdata, void *version)
|
|
{
|
|
mysql_data_t *state = (mysql_data_t *)dbdata;
|
|
mysql_transaction_t *txn = (mysql_transaction_t *)version;
|
|
char *new_name, *query;
|
|
isc_result_t result;
|
|
|
|
if (txn == NULL)
|
|
return (ISC_R_FAILURE);
|
|
|
|
new_name = relname(name, txn->zone);
|
|
if (new_name == NULL)
|
|
return (ISC_R_NOMEMORY);
|
|
|
|
if (state->debug && (state->log != NULL))
|
|
state->log(ISC_LOG_INFO, "%s: del (%x) %s %s",
|
|
modname, version, name, type);
|
|
|
|
query = build_query(state, txn->dbi, D_RRSET,
|
|
txn->zone_id, new_name, type);
|
|
if (query == NULL) {
|
|
result = ISC_R_NOMEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
result = db_execute(state, txn->dbi, query);
|
|
free(query);
|
|
|
|
cleanup:
|
|
free(new_name);
|
|
return (result);
|
|
}
|