1 /*
2  * Copyright (C) 2014 Maui Systems Ltd, Scotland, contact@maui-systems.co.uk.
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the
6  * above copyright notice and this permission notice appear in all
7  * copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND MAUI SYSTEMS LTD DISCLAIMS ALL
10  * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
11  * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL MAUI SYSTEMS LTD  BE
12  * LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR
13  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
14  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
15  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
16  * SOFTWARE.
17  */
18 
19 /*
20  * Copyright (C) 2011,2014  Internet Systems Consortium, Inc. ("ISC")
21  *
22  * Permission to use, copy, modify, and/or distribute this software for any
23  * purpose with or without fee is hereby granted, provided that the above
24  * copyright notice and this permission notice appear in all copies.
25  *
26  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
27  * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
28  * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
29  * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
30  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
31  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
32  * PERFORMANCE OF THIS SOFTWARE.
33  */
34 
35 /*
36  * BIND 9 DLZ MySQL module with support for dynamic DNS (DDNS)
37  *
38  * Adapted from code contributed by Marty Lee, Maui Systems Ltd.
39  *
40  * See README for database schema and usage details.
41  */
42 
43 #include <ifaddrs.h>
44 #include <inttypes.h>
45 #include <netdb.h>
46 #include <netinet/in.h>
47 #include <pthread.h>
48 #include <stdarg.h>
49 #include <stdbool.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <sys/socket.h>
54 #include <sys/types.h>
55 #include <unistd.h>
56 
57 #include <mysql/errmsg.h>
58 #include <mysql/mysql.h>
59 
60 #include <dlz_list.h>
61 #include <dlz_minimal.h>
62 #include <dlz_pthread.h>
63 
64 #if !defined(LIBMARIADB) && MYSQL_VERSION_ID >= 80000
65 typedef bool my_bool;
66 #endif /* !defined(LIBMARIADB) && MYSQL_VERSION_ID >= 80000 */
67 
68 /*
69  * The SQL queries that will be used for lookups and updates are defined
70  * here.  They will be processed into queries by the build_query()
71  * function.
72  *
73  * NOTE: Despite appearances, these do NOT use printf-style formatting.
74  * "%s", with no modifiers, is the only supported directive.
75  */
76 
77 /*
78  * Get the NS RRset for a zone
79  * Arguments: zone-name
80  */
81 #define Q_GETNS                                                         \
82 	"SELECT d.data FROM ZoneData d, Zones z "                       \
83 	"WHERE UPPER(d.type) = 'NS' AND LOWER(z.domain) = LOWER('%s') " \
84 	"AND z.id = d.zone_id"
85 
86 /*
87  * Get a list of zones (ignoring writable or not)
88  * Arguments: (none)
89  */
90 #define Q_GETZONES "SELECT LOWER(domain), serial FROM Zones"
91 
92 /*
93  * Find a specific zone
94  * Arguments: zone-name
95  */
96 #define Q_FINDZONE "SELECT id FROM Zones WHERE LOWER(domain) = LOWER('%s')"
97 
98 /*
99  * Get SOA data from zone apex
100  * Arguments: zone-name
101  */
102 #define Q_GETSOA                                                            \
103 	"SELECT host, admin, serial, refresh, retry, expire, minimum, ttl " \
104 	"FROM Zones WHERE LOWER(domain) = LOWER('%s')"
105 
106 /*
107  * Get other data from zone apex
108  * Arguments: zone-name, zone-name (repeated)
109  */
110 #define Q_GETAPEX                                                   \
111 	"SELECT d.type, d.data, d.ttl FROM ZoneData d, Zones z "    \
112 	"WHERE LOWER(z.domain) = LOWER('%s') AND z.id = d.zone_id " \
113 	"AND LOWER(d.name) IN (LOWER('%s'), '', '@') "              \
114 	"ORDER BY UPPER(d.type) ASC"
115 
116 /*
117  * Get data from non-apex nodes
118  * Arguments: zone-name, node-name (relative to zone name)
119  */
120 #define Q_GETNODE                                                   \
121 	"SELECT d.type, d.data, d.ttl FROM ZoneData d, Zones z "    \
122 	"WHERE LOWER(z.domain) = LOWER('%s') AND z.id = d.zone_id " \
123 	"AND LOWER(d.name) = LOWER('%s') "                          \
124 	"ORDER BY UPPER(d.type) ASC"
125 
126 /*
127  * Get all data from a zone, for AXFR
128  * Arguments: zone-name
129  */
130 #define Q_GETALL                                                         \
131 	"SELECT d.name, d.type, d.data, d.ttl FROM ZoneData d, Zones z " \
132 	"WHERE LOWER(z.domain) = LOWER('%s') AND z.id = d.zone_id"
133 
134 /*
135  * Get SOA serial number for a zone.
136  * Arguments: zone-name
137  */
138 #define Q_GETSERIAL "SELECT serial FROM Zones WHERE domain = '%s'"
139 
140 /*
141  * Determine whether a zone is writeable, and if so, retrieve zone_id
142  * Arguments: zone-name
143  */
144 #define Q_WRITEABLE                   \
145 	"SELECT id FROM Zones WHERE " \
146 	"LOWER(domain) = LOWER('%s') AND writeable = 1"
147 
148 /*
149  * Insert data into zone (other than SOA)
150  * Arguments: zone-id (from Q_WRITEABLE), node-name (relative to zone-name),
151  * 	      rrtype, rdata text, TTL (in text format)
152  */
153 #define I_DATA                                                   \
154 	"INSERT INTO ZoneData (zone_id, name, type, data, ttl) " \
155 	"VALUES (%s, LOWER('%s'), UPPER('%s'), '%s', %s)"
156 
157 /*
158  * Update SOA serial number for a zone
159  * Arguments: new serial number (in text format), zone-id (from Q_WRITEABLE)
160  */
161 #define U_SERIAL "UPDATE Zones SET serial = %s WHERE id = %s"
162 
163 /*
164  * Delete a specific record (non-SOA) from a zone
165  *
166  * Arguments: node-name (relative to zone-name), zone-id (from Q_WRITEABLE),
167  * 	      rrtype, rdata text, TTL (in text format).
168  */
169 #define D_RECORD                                                       \
170 	"DELETE FROM ZoneData WHERE zone_id = %s AND "                 \
171 	"LOWER(name) = LOWER('%s') AND UPPER(type) = UPPER('%s') AND " \
172 	"data = '%s' AND ttl = %s"
173 
174 /*
175  * Delete an entire rrset from a zone
176  * Arguments: node-name (relative to zone-name), zone-id (from Q_WRITEABLE),
177  * 	      rrtype.
178  */
179 #define D_RRSET                                        \
180 	"DELETE FROM ZoneData WHERE zone_id = %s AND " \
181 	"LOWER(name) = LOWER('%s') AND UPPER(type) = UPPER('%s')"
182 
183 /*
184  * Number of concurrent database connections we support
185  * - equivalent to maxmium number of concurrent transactions
186  *   that can be 'in-flight' + 1
187  */
188 #define MAX_DBI 16
189 
190 typedef struct mysql_record {
191 	char zone[255];
192 	char name[100];
193 	char type[10];
194 	char data[200];
195 	char ttl[10];
196 } mysql_record_t;
197 
198 typedef struct mysql_instance {
199 	int id;
200 	MYSQL *sock;
201 	int connected;
202 	dlz_mutex_t mutex;
203 } mysql_instance_t;
204 
205 typedef struct mysql_transaction mysql_transaction_t;
206 struct mysql_transaction {
207 	char *zone;
208 	char *zone_id;
209 	mysql_instance_t *dbi;
210 	mysql_transaction_t *next;
211 };
212 
213 typedef struct mysql_data {
214 	int debug;
215 
216 	/*
217 	 * Database connection details
218 	 */
219 	char *db_name;
220 	char *db_host;
221 	char *db_user;
222 	char *db_pass;
223 
224 	/*
225 	 * Database structures
226 	 */
227 	mysql_instance_t db[MAX_DBI];
228 
229 	/*
230 	 * Transactions
231 	 */
232 	mysql_transaction_t *transactions;
233 
234 	/*
235 	 * Mutex for transactions
236 	 */
237 	dlz_mutex_t tx_mutex;
238 
239 	/* Helper functions from the dlz_dlopen driver */
240 	log_t *log;
241 	dns_sdlz_putrr_t *putrr;
242 	dns_sdlz_putnamedrr_t *putnamedrr;
243 	dns_dlz_writeablezone_t *writeable_zone;
244 } mysql_data_t;
245 
246 typedef struct mysql_arg mysql_arg_t;
247 typedef DLZ_LIST(mysql_arg_t) mysql_arglist_t;
248 struct mysql_arg {
249 	char *arg;
250 	DLZ_LINK(mysql_arg_t) link;
251 };
252 
253 static const char *modname = "dlz_mysqldyn";
254 
255 /*
256  * Local functions
257  */
258 static bool
db_connect(mysql_data_t * state,mysql_instance_t * dbi)259 db_connect(mysql_data_t *state, mysql_instance_t *dbi) {
260 	MYSQL *conn;
261 	/*
262 	 * Make sure this thread has been through 'init'
263 	 */
264 	mysql_thread_init();
265 
266 	if (dbi->connected) {
267 		return (true);
268 	}
269 
270 	if (state->log != NULL) {
271 		state->log(ISC_LOG_INFO, "%s: init connection %d ", modname,
272 			   dbi->id);
273 	}
274 
275 	conn = mysql_real_connect(dbi->sock, state->db_host, state->db_user,
276 				  state->db_pass, state->db_name, 0, NULL,
277 				  CLIENT_REMEMBER_OPTIONS);
278 	if (conn == NULL) {
279 		if (state->log != NULL) {
280 			state->log(ISC_LOG_ERROR,
281 				   "%s: database connection failed: %s",
282 				   modname, mysql_error(dbi->sock));
283 		}
284 
285 		dlz_mutex_unlock(&dbi->mutex);
286 		return (false);
287 	}
288 
289 	dbi->connected = 1;
290 	return (true);
291 }
292 
293 static mysql_instance_t *
get_dbi(mysql_data_t * state)294 get_dbi(mysql_data_t *state) {
295 	int i;
296 
297 	/*
298 	 * Find an available dbi
299 	 */
300 	for (i = 0; i < MAX_DBI; i++) {
301 		if (dlz_mutex_trylock(&state->db[i].mutex) == 0) {
302 			break;
303 		}
304 	}
305 
306 	if (i == MAX_DBI) {
307 		if (state->debug && state->log != NULL) {
308 			state->log(ISC_LOG_ERROR,
309 				   "%s: No available connections", modname);
310 		}
311 		return (NULL);
312 	}
313 	return (&state->db[i]);
314 }
315 
316 /*
317  * Allocate memory and store an escaped, sanitized version
318  * of string 'original'
319  */
320 static char *
sanitize(mysql_instance_t * dbi,const char * original)321 sanitize(mysql_instance_t *dbi, const char *original) {
322 	char *s;
323 
324 	if (original == NULL) {
325 		return (NULL);
326 	}
327 
328 	s = (char *)malloc((strlen(original) * 2) + 1);
329 	if (s != NULL) {
330 		memset(s, 0, (strlen(original) * 2) + 1);
331 
332 		mysql_real_escape_string(dbi->sock, s, original,
333 					 strlen(original));
334 	}
335 
336 	return (s);
337 }
338 
339 /*
340  * Append the string pointed to by 's' to the argument list 'arglist',
341  * and add the string length to the running total pointed to by 'len'.
342  */
343 static isc_result_t
additem(mysql_arglist_t * arglist,char ** s,size_t * len)344 additem(mysql_arglist_t *arglist, char **s, size_t *len) {
345 	mysql_arg_t *item;
346 
347 	item = malloc(sizeof(*item));
348 	if (item == NULL) {
349 		return (ISC_R_NOMEMORY);
350 	}
351 
352 	DLZ_LINK_INIT(item, link);
353 	item->arg = *s;
354 	*len += strlen(*s);
355 	DLZ_LIST_APPEND(*arglist, item, link);
356 	*s = NULL;
357 
358 	return (ISC_R_SUCCESS);
359 }
360 
361 /*
362  * Construct a query string using a variable number of arguments, and
363  * save it into newly allocated memory.
364  *
365  * NOTE: 'command' resembles a printf-style format string, but ONLY
366  * supports the "%s" directive with no modifiers of any kind.
367  *
368  * If 'dbi' is NULL, we attempt to get a temporary database connection;
369  * otherwise we use the existing one.
370  */
371 static char *
build_query(mysql_data_t * state,mysql_instance_t * dbi,const char * command,...)372 build_query(mysql_data_t *state, mysql_instance_t *dbi, const char *command,
373 	    ...) {
374 	isc_result_t result;
375 	bool localdbi = false;
376 	mysql_arglist_t arglist;
377 	mysql_arg_t *item;
378 	char *p, *q, *tmp = NULL, *querystr = NULL;
379 	char *query = NULL;
380 	size_t len = 0;
381 	va_list ap1;
382 
383 	/* Get a DB instance if needed */
384 	if (dbi == NULL) {
385 		dbi = get_dbi(state);
386 		if (dbi == NULL) {
387 			return (NULL);
388 		}
389 		localdbi = true;
390 	}
391 
392 	/* Make sure this instance is connected */
393 	if (!db_connect(state, dbi)) {
394 		goto fail;
395 	}
396 
397 	va_start(ap1, command);
398 	DLZ_LIST_INIT(arglist);
399 	q = querystr = strdup(command);
400 	if (querystr == NULL) {
401 		goto fail;
402 	}
403 
404 	for (;;) {
405 		if (*q == '\0') {
406 			break;
407 		}
408 
409 		p = strstr(q, "%s");
410 		if (p != NULL) {
411 			*p = '\0';
412 			tmp = strdup(q);
413 			if (tmp == NULL) {
414 				goto fail;
415 			}
416 
417 			result = additem(&arglist, &tmp, &len);
418 			if (result != ISC_R_SUCCESS) {
419 				goto fail;
420 			}
421 
422 			tmp = sanitize(dbi, va_arg(ap1, const char *));
423 			if (tmp == NULL) {
424 				goto fail;
425 			}
426 
427 			result = additem(&arglist, &tmp, &len);
428 			if (result != ISC_R_SUCCESS) {
429 				goto fail;
430 			}
431 
432 			q = p + 2;
433 		} else {
434 			tmp = strdup(q);
435 			if (tmp == NULL) {
436 				goto fail;
437 			}
438 
439 			result = additem(&arglist, &tmp, &len);
440 			if (result != ISC_R_SUCCESS) {
441 				goto fail;
442 			}
443 
444 			break;
445 		}
446 	}
447 
448 	if (len == 0) {
449 		goto fail;
450 	}
451 
452 	query = malloc(len + 1);
453 	if (query == NULL) {
454 		goto fail;
455 	}
456 
457 	*query = '\0';
458 	for (item = DLZ_LIST_HEAD(arglist); item != NULL;
459 	     item = DLZ_LIST_NEXT(item, link))
460 	{
461 		if (item->arg != NULL) {
462 			strcat(query, item->arg);
463 		}
464 	}
465 
466 fail:
467 	va_end(ap1);
468 
469 	for (item = DLZ_LIST_HEAD(arglist); item != NULL;
470 	     item = DLZ_LIST_NEXT(item, link))
471 	{
472 		if (item->arg != NULL) {
473 			free(item->arg);
474 		}
475 		free(item);
476 	}
477 
478 	if (tmp != NULL) {
479 		free(tmp);
480 	}
481 	if (querystr != NULL) {
482 		free(querystr);
483 	}
484 
485 	if (dbi != NULL && localdbi) {
486 		dlz_mutex_unlock(&dbi->mutex);
487 	}
488 
489 	return (query);
490 }
491 
492 /* Does this name end in a dot? */
493 static bool
isrelative(const char * s)494 isrelative(const char *s) {
495 	if (s == NULL || s[strlen(s) - 1] == '.') {
496 		return (false);
497 	}
498 	return (true);
499 }
500 
501 /* Return a dot if 's' doesn't already end with one */
502 static inline const char *
dot(const char * s)503 dot(const char *s) {
504 	return (isrelative(s) ? "." : "");
505 }
506 
507 /*
508  * Generate a full hostname from a (presumably relative) name 'name'
509  * and a zone name 'zone'; store the result in 'dest' (which must have
510  * enough space).
511  */
512 static void
fqhn(const char * name,const char * zone,char * dest)513 fqhn(const char *name, const char *zone, char *dest) {
514 	if (dest == NULL) {
515 		return;
516 	}
517 
518 	if (strlen(name) == 0 || strcmp(name, "@") == 0) {
519 		sprintf(dest, "%s%s", zone, dot(zone));
520 	} else {
521 		if (isrelative(name)) {
522 			sprintf(dest, "%s.%s%s", name, zone, dot(zone));
523 		} else {
524 			strcpy(dest, name);
525 		}
526 	}
527 }
528 
529 /*
530  * Names are stored in relative form in ZoneData; this function
531  * removes labels matching 'zone' from the end of 'name'.
532  */
533 static char *
relname(const char * name,const char * zone)534 relname(const char *name, const char *zone) {
535 	size_t nlen, zlen;
536 	const char *p;
537 	char *new;
538 
539 	new = (char *)malloc(strlen(name) + 1);
540 	if (new == NULL) {
541 		return (NULL);
542 	}
543 
544 	nlen = strlen(name);
545 	zlen = strlen(zone);
546 
547 	if (nlen < zlen) {
548 		strcpy(new, name);
549 		return (new);
550 	} else if (nlen == zlen || strcasecmp(name, zone) == 0) {
551 		strcpy(new, "@");
552 		return (new);
553 	}
554 
555 	p = name + nlen - zlen;
556 	if (strcasecmp(p, zone) != 0 &&
557 	    (zone[zlen - 1] != '.' || strncasecmp(p, zone, zlen - 1) != 0))
558 	{
559 		strcpy(new, name);
560 		return (new);
561 	}
562 
563 	strncpy(new, name, nlen - zlen);
564 	new[nlen - zlen - 1] = '\0';
565 	return (new);
566 }
567 
568 static isc_result_t
validate_txn(mysql_data_t * state,mysql_transaction_t * txn)569 validate_txn(mysql_data_t *state, mysql_transaction_t *txn) {
570 	isc_result_t result = ISC_R_FAILURE;
571 	mysql_transaction_t *txp;
572 
573 	dlz_mutex_lock(&state->tx_mutex);
574 	for (txp = state->transactions; txp != NULL; txp = txp->next) {
575 		if (txn == txp) {
576 			result = ISC_R_SUCCESS;
577 			break;
578 		}
579 	}
580 	dlz_mutex_unlock(&state->tx_mutex);
581 
582 	if (result != ISC_R_SUCCESS && state->log != NULL) {
583 		state->log(ISC_LOG_ERROR, "%s: invalid txn %x", modname, txn);
584 	}
585 
586 	return (result);
587 }
588 
589 static isc_result_t
db_execute(mysql_data_t * state,mysql_instance_t * dbi,const char * query)590 db_execute(mysql_data_t *state, mysql_instance_t *dbi, const char *query) {
591 	int ret;
592 
593 	/* Make sure this instance is connected.  */
594 	if (!db_connect(state, dbi)) {
595 		return (ISC_R_FAILURE);
596 	}
597 
598 	ret = mysql_real_query(dbi->sock, query, strlen(query));
599 	if (ret != 0) {
600 		if (state->debug && state->log != NULL) {
601 			state->log(ISC_LOG_ERROR, "%s: query '%s' failed: %s",
602 				   modname, query, mysql_error(dbi->sock));
603 		}
604 		return (ISC_R_FAILURE);
605 	}
606 
607 	if (state->debug && state->log != NULL) {
608 		state->log(ISC_LOG_INFO, "%s: execute(%d) %s", modname, dbi->id,
609 			   query);
610 	}
611 
612 	return (ISC_R_SUCCESS);
613 }
614 
615 static MYSQL_RES *
db_query(mysql_data_t * state,mysql_instance_t * dbi,const char * query)616 db_query(mysql_data_t *state, mysql_instance_t *dbi, const char *query) {
617 	isc_result_t result;
618 	bool localdbi = false;
619 	MYSQL_RES *res = NULL;
620 
621 	if (query == NULL) {
622 		return (NULL);
623 	}
624 
625 	/* Get a DB instance if needed */
626 	if (dbi == NULL) {
627 		dbi = get_dbi(state);
628 		if (dbi == NULL) {
629 			return (NULL);
630 		}
631 		localdbi = true;
632 	}
633 
634 	/* Make sure this instance is connected */
635 	if (!db_connect(state, dbi)) {
636 		goto fail;
637 	}
638 
639 	result = db_execute(state, dbi, query);
640 	if (result != ISC_R_SUCCESS) {
641 		goto fail;
642 	}
643 
644 	res = mysql_store_result(dbi->sock);
645 	if (res == NULL) {
646 		if (state->log != NULL) {
647 			state->log(ISC_LOG_ERROR,
648 				   "%s: unable to store result: %s", modname,
649 				   mysql_error(dbi->sock));
650 		}
651 		goto fail;
652 	}
653 
654 	if (state->debug && state->log != NULL) {
655 		state->log(ISC_LOG_INFO, "%s: query(%d) returned %d rows",
656 			   modname, dbi->id, mysql_num_rows(res));
657 	}
658 
659 fail:
660 	if (dbi != NULL && localdbi) {
661 		dlz_mutex_unlock(&dbi->mutex);
662 	}
663 	return (res);
664 }
665 
666 /*
667  * Generate a DNS NOTIFY packet:
668  * 12 bytes header
669  * Question (1)
670  * 	strlen(zone) +2
671  *  2 bytes qtype
672  *  2 bytes qclass
673  *
674  * -> 18 bytes + strlen(zone)
675  *
676  * N.B. Need to be mindful of byte ordering; using htons to map 16bit
677  * values to the 'on the wire' packet values.
678  */
679 static unsigned char *
make_notify(const char * zone,int * packetlen)680 make_notify(const char *zone, int *packetlen) {
681 	int i, j;
682 	unsigned char *packet = (unsigned char *)malloc(strlen(zone) + 18);
683 
684 	if (packet == NULL) {
685 		return (NULL);
686 	}
687 
688 	*packetlen = strlen(zone) + 18;
689 	memset(packet, 0, *packetlen);
690 
691 	/* Random query ID */
692 	i = rand();
693 	packet[0] = htons(i) & 0xff;
694 	packet[1] = htons(i) >> 8;
695 
696 	/* Flags (OpCode '4' in bits 14-11), Auth Answer set in bit 10 */
697 	i = 0x2400;
698 	packet[2] = htons(i) & 0xff;
699 	packet[3] = htons(i) >> 8;
700 
701 	/* QD Count */
702 	i = 0x1;
703 	packet[4] = htons(i) & 0xff;
704 	packet[5] = htons(i) >> 8;
705 
706 	/* Question */
707 	packet[12] = '.';
708 	memmove(&packet[13], zone, strlen(zone));
709 	packet[13 + strlen(zone)] = 0;
710 
711 	/* Make the question into labels */
712 	j = 12;
713 	while (packet[j]) {
714 		for (i = j + 1; packet[i] != '\0' && packet[i] != '.'; i++)
715 			;
716 		packet[j] = i - j - 1;
717 		j = i;
718 	}
719 
720 	/* Question type */
721 	i = 6;
722 	packet[j + 1] = htons(i) & 0xff;
723 	packet[j + 2] = htons(i) >> 8;
724 
725 	/* Queston class */
726 	i = 1;
727 	packet[j + 3] = htons(i) & 0xff;
728 	packet[j + 4] = htons(i) >> 8;
729 
730 	return (packet);
731 }
732 
733 static void
send_notify(struct sockaddr_in * addr,const unsigned char * p,const int plen)734 send_notify(struct sockaddr_in *addr, const unsigned char *p, const int plen) {
735 	int s;
736 
737 	addr->sin_family = AF_INET;
738 	addr->sin_port = htons(53);
739 
740 	if ((s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) {
741 		return;
742 	}
743 
744 	sendto(s, p, plen, 0, (struct sockaddr *)addr, sizeof(*addr));
745 	close(s);
746 	return;
747 }
748 
749 /*
750  * Generate and send a DNS NOTIFY packet
751  */
752 static void
notify(mysql_data_t * state,const char * zone,int sn)753 notify(mysql_data_t *state, const char *zone, int sn) {
754 	MYSQL_RES *res;
755 	MYSQL_ROW row;
756 	char *query;
757 	unsigned char *packet;
758 	int packetlen;
759 	struct ifaddrs *ifap, *ifa;
760 	char zaddr[INET_ADDRSTRLEN];
761 	void *addrp = NULL;
762 
763 	/* Get the name servers from the NS rrset */
764 	query = build_query(state, NULL, Q_GETNS, zone);
765 	res = db_query(state, NULL, query);
766 	free(query);
767 	if (res == NULL) {
768 		return;
769 	}
770 
771 	/* Create a DNS NOTIFY packet */
772 	packet = make_notify(zone, &packetlen);
773 	if (packet == NULL) {
774 		mysql_free_result(res);
775 		return;
776 	}
777 
778 	/* Get a list of our own addresses */
779 	if (getifaddrs(&ifap) < 0) {
780 		ifap = NULL;
781 	}
782 
783 	/* Tell each nameserver of the update */
784 	while ((row = mysql_fetch_row(res)) != NULL) {
785 		bool local = false;
786 		struct hostent *h;
787 		struct sockaddr_in addr, *sin;
788 
789 		/*
790 		 * Put nameserver rdata through gethostbyname as it
791 		 * might be an IP address or a hostname. (XXX: switch
792 		 * this to inet_pton/getaddrinfo.)
793 		 */
794 		h = gethostbyname(row[0]);
795 		if (h == NULL) {
796 			continue;
797 		}
798 
799 		memmove(&addr.sin_addr, h->h_addr, h->h_length);
800 		addrp = &addr.sin_addr;
801 
802 		/* Get the address for the nameserver into a string */
803 		inet_ntop(AF_INET, addrp, zaddr, INET_ADDRSTRLEN);
804 		for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
805 			char ifaddr[INET_ADDRSTRLEN];
806 
807 			if (ifa->ifa_addr->sa_family != AF_INET) {
808 				continue;
809 			}
810 
811 			/* Get local address into a string */
812 			sin = (struct sockaddr_in *)ifa->ifa_addr;
813 			addrp = &sin->sin_addr;
814 			inet_ntop(AF_INET, addrp, ifaddr, INET_ADDRSTRLEN);
815 
816 			/* See if nameserver address matches this one */
817 			if (strcmp(ifaddr, zaddr) == 0) {
818 				local = true;
819 			}
820 		}
821 
822 		if (!local) {
823 			if (state->log != NULL) {
824 				state->log(ISC_LOG_INFO,
825 					   "%s: notify %s zone %s serial %d",
826 					   modname, row[0], zone, sn);
827 			}
828 			send_notify(&addr, packet, packetlen);
829 		}
830 	}
831 
832 	mysql_free_result(res);
833 	free(packet);
834 	if (ifap != NULL) {
835 		freeifaddrs(ifap);
836 	}
837 }
838 
839 /*
840  * Constructs a mysql_record_t structure from 'rdatastr', to be
841  * used in the dlz_{add,sub,del}rdataset functions below.
842  */
843 static mysql_record_t *
makerecord(mysql_data_t * state,const char * name,const char * rdatastr)844 makerecord(mysql_data_t *state, const char *name, const char *rdatastr) {
845 	mysql_record_t *new_record;
846 	char *real_name = NULL, *dclass = NULL, *type = NULL;
847 	char *data = NULL, *ttlstr = NULL, *buf = NULL;
848 	char *saveptr = NULL;
849 	dns_ttl_t ttlvalue;
850 
851 	new_record = (mysql_record_t *)malloc(sizeof(mysql_record_t));
852 
853 	if (new_record == NULL) {
854 		if (state->log != NULL) {
855 			state->log(ISC_LOG_ERROR,
856 				   "%s: makerecord - unable to malloc",
857 				   modname);
858 		}
859 		return (NULL);
860 	}
861 
862 	buf = strdup(rdatastr);
863 	if (buf == NULL) {
864 		if (state->log != NULL) {
865 			state->log(ISC_LOG_ERROR,
866 				   "%s: makerecord - unable to malloc",
867 				   modname);
868 		}
869 		free(new_record);
870 		return (NULL);
871 	}
872 
873 	/*
874 	 * The format is:
875 	 * FULLNAME\tTTL\tDCLASS\tTYPE\tDATA
876 	 *
877 	 * The DATA field is space separated, and is in the data format
878 	 * for the type used by dig
879 	 */
880 	real_name = strtok_r(buf, "\t", &saveptr);
881 	if (real_name == NULL) {
882 		goto error;
883 	}
884 
885 	ttlstr = strtok_r(NULL, "\t", &saveptr);
886 	if (ttlstr == NULL || sscanf(ttlstr, "%d", &ttlvalue) != 1) {
887 		goto error;
888 	}
889 
890 	dclass = strtok_r(NULL, "\t", &saveptr);
891 	if (dclass == NULL) {
892 		goto error;
893 	}
894 
895 	type = strtok_r(NULL, "\t", &saveptr);
896 	if (type == NULL) {
897 		goto error;
898 	}
899 
900 	data = strtok_r(NULL, "\t", &saveptr);
901 	if (data == NULL) {
902 		goto error;
903 	}
904 
905 	strcpy(new_record->name, name);
906 	strcpy(new_record->type, type);
907 	strcpy(new_record->data, data);
908 	sprintf(new_record->ttl, "%d", ttlvalue);
909 
910 	free(buf);
911 	return (new_record);
912 
913 error:
914 	free(buf);
915 	free(new_record);
916 	return (NULL);
917 }
918 
919 /*
920  * Remember a helper function from the bind9 dlz_dlopen driver
921  */
922 static void
b9_add_helper(mysql_data_t * state,const char * helper_name,void * ptr)923 b9_add_helper(mysql_data_t *state, const char *helper_name, void *ptr) {
924 	if (strcmp(helper_name, "log") == 0) {
925 		state->log = (log_t *)ptr;
926 	}
927 	if (strcmp(helper_name, "putrr") == 0) {
928 		state->putrr = (dns_sdlz_putrr_t *)ptr;
929 	}
930 	if (strcmp(helper_name, "putnamedrr") == 0) {
931 		state->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr;
932 	}
933 	if (strcmp(helper_name, "writeable_zone") == 0) {
934 		state->writeable_zone = (dns_dlz_writeablezone_t *)ptr;
935 	}
936 }
937 
938 /*
939  * DLZ API functions
940  */
941 
942 /*
943  * Return the version of the API
944  */
945 int
dlz_version(unsigned int * flags)946 dlz_version(unsigned int *flags) {
947 	UNUSED(flags);
948 	*flags |= DNS_SDLZFLAG_THREADSAFE;
949 	return (DLZ_DLOPEN_VERSION);
950 }
951 
952 /*
953  * Called to initialize the driver
954  */
955 isc_result_t
dlz_create(const char * dlzname,unsigned int argc,char * argv[],void ** dbdata,...)956 dlz_create(const char *dlzname, unsigned int argc, char *argv[], void **dbdata,
957 	   ...) {
958 	mysql_data_t *state;
959 	const char *helper_name;
960 	va_list ap;
961 	int n;
962 
963 	UNUSED(dlzname);
964 
965 	state = calloc(1, sizeof(mysql_data_t));
966 	if (state == NULL) {
967 		return (ISC_R_NOMEMORY);
968 	}
969 
970 	dlz_mutex_init(&state->tx_mutex, NULL);
971 	state->transactions = NULL;
972 
973 	/* Fill in the helper functions */
974 	va_start(ap, dbdata);
975 	while ((helper_name = va_arg(ap, const char *)) != NULL) {
976 		b9_add_helper(state, helper_name, va_arg(ap, void *));
977 	}
978 	va_end(ap);
979 
980 	if (state->log != NULL) {
981 		state->log(ISC_LOG_INFO, "loading %s module", modname);
982 	}
983 
984 	if ((argc < 2) || (argc > 6)) {
985 		if (state->log != NULL) {
986 			state->log(ISC_LOG_ERROR,
987 				   "%s: missing args <dbname> "
988 				   "[<dbhost> [<user> <pass>]]",
989 				   modname);
990 		}
991 		dlz_destroy(state);
992 		return (ISC_R_FAILURE);
993 	}
994 
995 	state->db_name = strdup(argv[1]);
996 	if (argc > 2) {
997 		state->db_host = strdup(argv[2]);
998 		if (argc > 4) {
999 			state->db_user = strdup(argv[3]);
1000 			state->db_pass = strdup(argv[4]);
1001 		} else {
1002 			state->db_user = strdup("bind");
1003 			state->db_pass = strdup("");
1004 		}
1005 	} else {
1006 		state->db_host = strdup("localhost");
1007 		state->db_user = strdup("bind");
1008 		state->db_pass = strdup("");
1009 	}
1010 
1011 	if (state->log != NULL) {
1012 		state->log(ISC_LOG_INFO, "%s: DB=%s, Host=%s, User=%s", modname,
1013 			   state->db_name, state->db_host, state->db_user);
1014 	}
1015 
1016 	/*
1017 	 * Assign the 'state' to dbdata so we get it in our callbacks
1018 	 */
1019 
1020 	dlz_mutex_lock(&state->tx_mutex);
1021 
1022 	/*
1023 	 * Populate DB instances
1024 	 */
1025 	if (mysql_thread_safe()) {
1026 		for (n = 0; n < MAX_DBI; n++) {
1027 			dlz_mutex_init(&state->db[n].mutex, NULL);
1028 			dlz_mutex_lock(&state->db[n].mutex);
1029 			state->db[n].id = n;
1030 			state->db[n].connected = 0;
1031 			state->db[n].sock = mysql_init(NULL);
1032 			mysql_options(state->db[n].sock,
1033 				      MYSQL_READ_DEFAULT_GROUP, modname);
1034 			mysql_options(state->db[n].sock, MYSQL_OPT_RECONNECT,
1035 				      &(my_bool){ 1 });
1036 			dlz_mutex_unlock(&state->db[n].mutex);
1037 		}
1038 
1039 		*dbdata = state;
1040 		dlz_mutex_unlock(&state->tx_mutex);
1041 		return (ISC_R_SUCCESS);
1042 	}
1043 
1044 	free(state->db_name);
1045 	free(state->db_host);
1046 	free(state->db_user);
1047 	free(state->db_pass);
1048 	dlz_mutex_destroy(&state->tx_mutex);
1049 	free(state);
1050 	return (ISC_R_FAILURE);
1051 }
1052 
1053 /*
1054  * Shut down the backend
1055  */
1056 void
dlz_destroy(void * dbdata)1057 dlz_destroy(void *dbdata) {
1058 	mysql_data_t *state = (mysql_data_t *)dbdata;
1059 	int i;
1060 
1061 	if (state->debug && state->log != NULL) {
1062 		state->log(ISC_LOG_INFO, "%s: shutting down", modname);
1063 	}
1064 
1065 	for (i = 0; i < MAX_DBI; i++) {
1066 		if (state->db[i].sock) {
1067 			mysql_close(state->db[i].sock);
1068 			state->db[i].sock = NULL;
1069 		}
1070 	}
1071 	free(state->db_name);
1072 	free(state->db_host);
1073 	free(state->db_user);
1074 	free(state->db_pass);
1075 	dlz_mutex_destroy(&state->tx_mutex);
1076 	free(state);
1077 }
1078 
1079 /*
1080  * See if we handle a given zone
1081  */
1082 isc_result_t
dlz_findzonedb(void * dbdata,const char * name,dns_clientinfomethods_t * methods,dns_clientinfo_t * clientinfo)1083 dlz_findzonedb(void *dbdata, const char *name, dns_clientinfomethods_t *methods,
1084 	       dns_clientinfo_t *clientinfo) {
1085 	isc_result_t result = ISC_R_SUCCESS;
1086 	mysql_data_t *state = (mysql_data_t *)dbdata;
1087 	MYSQL_RES *res;
1088 	char *query;
1089 
1090 	/* Query the Zones table to see if this zone is present */
1091 	query = build_query(state, NULL, Q_FINDZONE, name);
1092 
1093 	if (query == NULL) {
1094 		return (ISC_R_NOMEMORY);
1095 	}
1096 
1097 	res = db_query(state, NULL, query);
1098 	if (res == NULL) {
1099 		return (ISC_R_FAILURE);
1100 	}
1101 
1102 	if (mysql_num_rows(res) == 0) {
1103 		result = ISC_R_NOTFOUND;
1104 	}
1105 
1106 	mysql_free_result(res);
1107 	return (result);
1108 }
1109 
1110 /*
1111  * Perform a database lookup
1112  */
1113 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)1114 dlz_lookup(const char *zone, const char *name, void *dbdata,
1115 	   dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods,
1116 	   dns_clientinfo_t *clientinfo) {
1117 	isc_result_t result;
1118 	mysql_data_t *state = (mysql_data_t *)dbdata;
1119 	bool found = false;
1120 	char *real_name;
1121 	MYSQL_RES *res;
1122 	MYSQL_ROW row;
1123 	char *query;
1124 	mysql_transaction_t *txn = NULL;
1125 	mysql_instance_t *dbi = NULL;
1126 
1127 	if (state->putrr == NULL) {
1128 		if (state->log != NULL) {
1129 			state->log(ISC_LOG_ERROR, "%s: dlz_lookup - no putrr",
1130 				   modname);
1131 		}
1132 		return (ISC_R_NOTIMPLEMENTED);
1133 	}
1134 
1135 	/* Are we okay to try to find the txn version?  */
1136 	if (clientinfo != NULL && clientinfo->version >= DNS_CLIENTINFO_VERSION)
1137 	{
1138 		txn = (mysql_transaction_t *)clientinfo->dbversion;
1139 		if (txn != NULL && validate_txn(state, txn) == ISC_R_SUCCESS) {
1140 			dbi = txn->dbi;
1141 		}
1142 		if (dbi != NULL) {
1143 			state->log(ISC_LOG_DEBUG(1),
1144 				   "%s: lookup in live transaction %p, DBI %p",
1145 				   modname, txn, dbi);
1146 		}
1147 	}
1148 
1149 	if (strcmp(name, "@") == 0) {
1150 		real_name = (char *)malloc(strlen(zone) + 1);
1151 		if (real_name == NULL) {
1152 			return (ISC_R_NOMEMORY);
1153 		}
1154 		strcpy(real_name, zone);
1155 	} else {
1156 		real_name = (char *)malloc(strlen(name) + 1);
1157 		if (real_name == NULL) {
1158 			return (ISC_R_NOMEMORY);
1159 		}
1160 		strcpy(real_name, name);
1161 	}
1162 
1163 	if (strcmp(real_name, zone) == 0) {
1164 		/*
1165 		 * Get the Zones table data for use in the SOA:
1166 		 * zone admin serial refresh retry expire min
1167 		 */
1168 		query = build_query(state, dbi, Q_GETSOA, zone);
1169 		if (query == NULL) {
1170 			free(real_name);
1171 			return (ISC_R_NOMEMORY);
1172 		}
1173 
1174 		res = db_query(state, dbi, query);
1175 		free(query);
1176 
1177 		if (res == NULL) {
1178 			free(real_name);
1179 			return (ISC_R_NOTFOUND);
1180 		}
1181 
1182 		while ((row = mysql_fetch_row(res)) != NULL) {
1183 			char host[1024], admin[1024], data[4096];
1184 			int ttl;
1185 
1186 			sscanf(row[7], "%d", &ttl);
1187 			fqhn(row[0], zone, host);
1188 			fqhn(row[1], zone, admin);
1189 
1190 			/* zone admin serial refresh retry expire min */
1191 			snprintf(data, sizeof(data), "%s%s %s%s %s %s %s %s %s",
1192 				 host, dot(host), admin, dot(admin), row[2],
1193 				 row[3], row[4], row[5], row[6]);
1194 
1195 			result = state->putrr(lookup, "soa", ttl, data);
1196 			if (result != ISC_R_SUCCESS) {
1197 				free(real_name);
1198 				mysql_free_result(res);
1199 				return (result);
1200 			}
1201 		}
1202 
1203 		mysql_free_result(res);
1204 
1205 		/*
1206 		 *  Now we'll get the rest of the apex data
1207 		 */
1208 		query = build_query(state, dbi, Q_GETAPEX, zone, real_name);
1209 	} else {
1210 		query = build_query(state, dbi, Q_GETNODE, zone, real_name);
1211 	}
1212 
1213 	res = db_query(state, dbi, query);
1214 	free(query);
1215 
1216 	if (res == NULL) {
1217 		free(real_name);
1218 		return (ISC_R_NOTFOUND);
1219 	}
1220 
1221 	while ((row = mysql_fetch_row(res)) != NULL) {
1222 		int ttl;
1223 		sscanf(row[2], "%d", &ttl);
1224 		result = state->putrr(lookup, row[0], ttl, row[1]);
1225 		if (result != ISC_R_SUCCESS) {
1226 			free(real_name);
1227 			mysql_free_result(res);
1228 			return (result);
1229 		}
1230 
1231 		found = true;
1232 	}
1233 
1234 	if (state->debug && state->log != NULL) {
1235 		state->log(ISC_LOG_INFO, "%s: dlz_lookup %s/%s/%s - (%d rows)",
1236 			   modname, name, real_name, zone, mysql_num_rows(res));
1237 	}
1238 
1239 	mysql_free_result(res);
1240 	free(real_name);
1241 
1242 	if (!found) {
1243 		return (ISC_R_NOTFOUND);
1244 	}
1245 
1246 	return (ISC_R_SUCCESS);
1247 }
1248 
1249 /*
1250  * See if a zone transfer is allowed
1251  */
1252 isc_result_t
dlz_allowzonexfr(void * dbdata,const char * name,const char * client)1253 dlz_allowzonexfr(void *dbdata, const char *name, const char *client) {
1254 	mysql_data_t *state = (mysql_data_t *)dbdata;
1255 
1256 	if (state->debug && state->log != NULL) {
1257 		state->log(ISC_LOG_INFO, "dlz_allowzonexfr: %s %s", name,
1258 			   client);
1259 	}
1260 
1261 	/* Just say yes for all our zones */
1262 	return (dlz_findzonedb(dbdata, name, NULL, NULL));
1263 }
1264 
1265 /*
1266  * Perform a zone transfer
1267  */
1268 isc_result_t
dlz_allnodes(const char * zone,void * dbdata,dns_sdlzallnodes_t * allnodes)1269 dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) {
1270 	isc_result_t result = ISC_R_SUCCESS;
1271 	mysql_data_t *state = (mysql_data_t *)dbdata;
1272 	MYSQL_RES *res;
1273 	MYSQL_ROW row;
1274 	char *query;
1275 
1276 	UNUSED(zone);
1277 
1278 	if (state->debug && (state->log != NULL)) {
1279 		state->log(ISC_LOG_INFO, "dlz_allnodes: %s", zone);
1280 	}
1281 
1282 	if (state->putnamedrr == NULL) {
1283 		return (ISC_R_NOTIMPLEMENTED);
1284 	}
1285 
1286 	/*
1287 	 * Get all the ZoneData for this zone
1288 	 */
1289 	query = build_query(state, NULL, Q_GETALL, zone);
1290 	if (query == NULL) {
1291 		return (ISC_R_NOMEMORY);
1292 	}
1293 
1294 	res = db_query(state, NULL, query);
1295 	free(query);
1296 	if (res == NULL) {
1297 		return (ISC_R_NOTFOUND);
1298 	}
1299 
1300 	while ((row = mysql_fetch_row(res)) != NULL) {
1301 		char hostname[1024];
1302 		int ttl;
1303 
1304 		sscanf(row[3], "%d", &ttl);
1305 		fqhn(row[0], zone, hostname);
1306 		result = state->putnamedrr(allnodes, hostname, row[1], ttl,
1307 					   row[2]);
1308 		if (result != ISC_R_SUCCESS) {
1309 			break;
1310 		}
1311 	}
1312 
1313 	mysql_free_result(res);
1314 	return (result);
1315 }
1316 
1317 /*
1318  * Start a transaction
1319  */
1320 isc_result_t
dlz_newversion(const char * zone,void * dbdata,void ** versionp)1321 dlz_newversion(const char *zone, void *dbdata, void **versionp) {
1322 	isc_result_t result = ISC_R_FAILURE;
1323 	mysql_data_t *state = (mysql_data_t *)dbdata;
1324 	MYSQL_RES *res;
1325 	MYSQL_ROW row;
1326 	char *query;
1327 	char zone_id[16];
1328 	mysql_transaction_t *txn = NULL, *newtx = NULL;
1329 
1330 	/*
1331 	 * Check Zone is writable
1332 	 */
1333 	query = build_query(state, NULL, Q_WRITEABLE, zone);
1334 	if (query == NULL) {
1335 		return (ISC_R_NOMEMORY);
1336 	}
1337 
1338 	res = db_query(state, NULL, query);
1339 	free(query);
1340 	if (res == NULL) {
1341 		return (ISC_R_FAILURE);
1342 	}
1343 
1344 	if ((row = mysql_fetch_row(res)) == NULL) {
1345 		mysql_free_result(res);
1346 		return (ISC_R_FAILURE);
1347 	}
1348 
1349 	strcpy(zone_id, row[0]);
1350 	mysql_free_result(res);
1351 
1352 	/*
1353 	 * See if we already have a transaction for this zone
1354 	 */
1355 	dlz_mutex_lock(&state->tx_mutex);
1356 	for (txn = state->transactions; txn != NULL; txn = txn->next) {
1357 		if (strcmp(txn->zone, zone) == 0) {
1358 			if (state->log != NULL) {
1359 				state->log(ISC_LOG_ERROR,
1360 					   "%s: transaction already "
1361 					   "started for zone %s",
1362 					   modname, zone);
1363 			}
1364 			dlz_mutex_unlock(&state->tx_mutex);
1365 			return (ISC_R_FAILURE);
1366 		}
1367 	}
1368 
1369 	/*
1370 	 * Create new transaction
1371 	 */
1372 	newtx = (mysql_transaction_t *)calloc(1, sizeof(mysql_transaction_t));
1373 	if (newtx == NULL) {
1374 		result = ISC_R_NOMEMORY;
1375 		goto cleanup;
1376 	}
1377 	newtx->zone = strdup(zone);
1378 	if (newtx->zone == NULL) {
1379 		result = ISC_R_NOMEMORY;
1380 		goto cleanup;
1381 	}
1382 	newtx->zone_id = strdup(zone_id);
1383 	if (newtx->zone_id == NULL) {
1384 		result = ISC_R_NOMEMORY;
1385 		goto cleanup;
1386 	}
1387 	newtx->dbi = get_dbi(state);
1388 	newtx->next = NULL;
1389 
1390 	if (newtx->dbi == NULL) {
1391 		result = ISC_R_FAILURE;
1392 		goto cleanup;
1393 	}
1394 
1395 	result = db_execute(state, newtx->dbi, "START TRANSACTION");
1396 	if (result != ISC_R_SUCCESS) {
1397 		dlz_mutex_unlock(&newtx->dbi->mutex);
1398 		goto cleanup;
1399 	}
1400 
1401 	/*
1402 	 * Add this tx to front of list
1403 	 */
1404 	newtx->next = state->transactions;
1405 	state->transactions = newtx;
1406 
1407 	if (state->debug && (state->log != NULL)) {
1408 		state->log(ISC_LOG_INFO, "%s: New tx %x", modname, newtx);
1409 	}
1410 
1411 cleanup:
1412 	dlz_mutex_unlock(&state->tx_mutex);
1413 	if (result == ISC_R_SUCCESS) {
1414 		*versionp = (void *)newtx;
1415 	} else {
1416 		dlz_mutex_unlock(&state->tx_mutex);
1417 		if (newtx != NULL) {
1418 			if (newtx->zone != NULL) {
1419 				free(newtx->zone);
1420 			}
1421 			if (newtx->zone != NULL) {
1422 				free(newtx->zone_id);
1423 			}
1424 			free(newtx);
1425 		}
1426 	}
1427 
1428 	return (result);
1429 }
1430 
1431 /*
1432  * End a transaction
1433  */
1434 void
dlz_closeversion(const char * zone,bool commit,void * dbdata,void ** versionp)1435 dlz_closeversion(const char *zone, bool commit, void *dbdata, void **versionp) {
1436 	isc_result_t result;
1437 	mysql_data_t *state = (mysql_data_t *)dbdata;
1438 	mysql_transaction_t *txn = (mysql_transaction_t *)*versionp;
1439 	mysql_transaction_t *txp;
1440 	char *query;
1441 	MYSQL_RES *res;
1442 	MYSQL_ROW row;
1443 
1444 	/*
1445 	 * Find the transaction
1446 	 */
1447 	dlz_mutex_lock(&state->tx_mutex);
1448 	if (state->transactions == txn) {
1449 		/* Tx is first in list; remove it. */
1450 		state->transactions = txn->next;
1451 	} else {
1452 		txp = state->transactions;
1453 		while (txp != NULL) {
1454 			if (txp->next != NULL) {
1455 				if (txp->next == txn) {
1456 					txp->next = txn->next;
1457 					break;
1458 				}
1459 			}
1460 			if (txp == txn) {
1461 				txp = txn->next;
1462 				break;
1463 			}
1464 			txp = txp->next;
1465 		}
1466 	}
1467 
1468 	/*
1469 	 * Tidy up
1470 	 */
1471 	dlz_mutex_unlock(&state->tx_mutex);
1472 	*versionp = NULL;
1473 
1474 	if (commit) {
1475 		int oldsn = 0, newsn = 0;
1476 
1477 		/*
1478 		 * Find out the serial number of the zone out with the
1479 		 * transaction so we can see if it has incremented or not
1480 		 */
1481 		query = build_query(state, txn->dbi, Q_GETSERIAL, zone);
1482 		if (query == NULL && state->log != NULL) {
1483 			state->log(ISC_LOG_ERROR,
1484 				   "%s: unable to commit transaction %x "
1485 				   "on zone %s: no memory",
1486 				   modname, txn, zone);
1487 			return;
1488 		}
1489 
1490 		res = db_query(state, txn->dbi, query);
1491 		if (res != NULL) {
1492 			while ((row = mysql_fetch_row(res)) != NULL) {
1493 				sscanf(row[0], "%d", &oldsn);
1494 			}
1495 			mysql_free_result(res);
1496 		}
1497 
1498 		/*
1499 		 * Commit the transaction to the database
1500 		 */
1501 		result = db_execute(state, txn->dbi, "COMMIT");
1502 		if (result != ISC_R_SUCCESS && state->log != NULL) {
1503 			state->log(ISC_LOG_INFO,
1504 				   "%s: (%x) commit transaction on zone %s",
1505 				   modname, txn, zone);
1506 			return;
1507 		}
1508 
1509 		if (state->debug && state->log != NULL) {
1510 			state->log(ISC_LOG_INFO,
1511 				   "%s: (%x) committing transaction "
1512 				   "on zone %s",
1513 				   modname, txn, zone);
1514 		}
1515 
1516 		/*
1517 		 * Now get the serial number again
1518 		 */
1519 		query = build_query(state, txn->dbi, Q_GETSERIAL, zone);
1520 		res = db_query(state, txn->dbi, query);
1521 		free(query);
1522 
1523 		if (res != NULL) {
1524 			while ((row = mysql_fetch_row(res)) != NULL) {
1525 				sscanf(row[0], "%d", &newsn);
1526 			}
1527 			mysql_free_result(res);
1528 		}
1529 
1530 		/*
1531 		 * Look to see if serial numbers have changed
1532 		 */
1533 		if (newsn > oldsn) {
1534 			notify(state, zone, newsn);
1535 		}
1536 	} else {
1537 		result = db_execute(state, txn->dbi, "ROLLBACK");
1538 		if (state->debug && (state->log != NULL)) {
1539 			state->log(ISC_LOG_INFO,
1540 				   "%s: (%x) roll back transaction on zone %s",
1541 				   modname, txn, zone);
1542 		}
1543 	}
1544 
1545 	/*
1546 	 * Unlock the mutex for this txn
1547 	 */
1548 	dlz_mutex_unlock(&txn->dbi->mutex);
1549 
1550 	/*
1551 	 * Free up other structures
1552 	 */
1553 	free(txn->zone);
1554 	free(txn->zone_id);
1555 	free(txn);
1556 }
1557 
1558 /*
1559  * Configure a writeable zone
1560  */
1561 #if DLZ_DLOPEN_VERSION < 3
1562 isc_result_t
dlz_configure(dns_view_t * view,void * dbdata)1563 dlz_configure(dns_view_t *view, void *dbdata)
1564 #else  /* DLZ_DLOPEN_VERSION >= 3 */
1565 isc_result_t
1566 dlz_configure(dns_view_t *view, dns_dlzdb_t *dlzdb, void *dbdata)
1567 #endif /* DLZ_DLOPEN_VERSION */
1568 {
1569 	mysql_data_t *state = (mysql_data_t *)dbdata;
1570 	isc_result_t result;
1571 	MYSQL_RES *res;
1572 	MYSQL_ROW row;
1573 	int count;
1574 
1575 	/*
1576 	 * Seed PRNG (used by Notify code)
1577 	 */
1578 	srand(getpid());
1579 
1580 	if (state->debug && state->log != NULL) {
1581 		state->log(ISC_LOG_INFO, "%s: dlz_confgure", modname);
1582 	}
1583 
1584 	if (state->writeable_zone == NULL) {
1585 		if (state->log != NULL) {
1586 			state->log(ISC_LOG_ERROR,
1587 				   "%s: no writeable_zone method available",
1588 				   modname);
1589 		}
1590 		return (ISC_R_FAILURE);
1591 	}
1592 
1593 	/*
1594 	 * Get a list of Zones (ignore writeable column at this point)
1595 	 */
1596 	res = db_query(state, NULL, Q_GETZONES);
1597 	if (res == NULL) {
1598 		return (ISC_R_FAILURE);
1599 	}
1600 
1601 	count = 0;
1602 	while ((row = mysql_fetch_row(res)) != NULL) {
1603 		int sn;
1604 		sscanf(row[1], "%d", &sn);
1605 		notify(state, row[0], sn);
1606 		result = state->writeable_zone(view,
1607 #if DLZ_DLOPEN_VERSION >= 3
1608 					       dlzdb,
1609 #endif /* if DLZ_DLOPEN_VERSION >= 3 */
1610 					       row[0]);
1611 		if (result != ISC_R_SUCCESS) {
1612 			if (state->log != NULL) {
1613 				state->log(ISC_LOG_ERROR,
1614 					   "%s: failed to configure zone %s",
1615 					   modname, row[0]);
1616 			}
1617 			mysql_free_result(res);
1618 			return (result);
1619 		}
1620 		count++;
1621 	}
1622 	mysql_free_result(res);
1623 
1624 	if (state->debug && state->log != NULL) {
1625 		state->log(ISC_LOG_INFO, "%s: configured %d zones", modname,
1626 			   count);
1627 	}
1628 	return (ISC_R_SUCCESS);
1629 }
1630 
1631 /*
1632  * Authorize a zone update
1633  */
1634 bool
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)1635 dlz_ssumatch(const char *signer, const char *name, const char *tcpaddr,
1636 	     const char *type, const char *key, uint32_t keydatalen,
1637 	     unsigned char *keydata, void *dbdata) {
1638 	mysql_data_t *state = (mysql_data_t *)dbdata;
1639 
1640 	UNUSED(tcpaddr);
1641 	UNUSED(type);
1642 	UNUSED(keydatalen);
1643 	UNUSED(keydata);
1644 	UNUSED(key);
1645 
1646 	if (state->debug && state->log != NULL) {
1647 		state->log(ISC_LOG_INFO, "%s: allowing update of %s by key %s",
1648 			   modname, name, signer);
1649 	}
1650 	return (true);
1651 }
1652 
1653 isc_result_t
dlz_addrdataset(const char * name,const char * rdatastr,void * dbdata,void * version)1654 dlz_addrdataset(const char *name, const char *rdatastr, void *dbdata,
1655 		void *version) {
1656 	mysql_data_t *state = (mysql_data_t *)dbdata;
1657 	mysql_transaction_t *txn = (mysql_transaction_t *)version;
1658 	char *new_name, *query;
1659 	mysql_record_t *record;
1660 	isc_result_t result;
1661 
1662 	if (txn == NULL) {
1663 		return (ISC_R_FAILURE);
1664 	}
1665 
1666 	new_name = relname(name, txn->zone);
1667 	if (new_name == NULL) {
1668 		return (ISC_R_NOMEMORY);
1669 	}
1670 
1671 	if (state->debug && (state->log != NULL)) {
1672 		state->log(ISC_LOG_INFO, "%s: add (%x) %s (as %s) %s", modname,
1673 			   version, name, new_name, rdatastr);
1674 	}
1675 
1676 	record = makerecord(state, new_name, rdatastr);
1677 	free(new_name);
1678 	if (record == NULL) {
1679 		return (ISC_R_FAILURE);
1680 	}
1681 
1682 	/* Write out data to database */
1683 	if (strcasecmp(record->type, "SOA") != 0) {
1684 		query = build_query(state, txn->dbi, I_DATA, txn->zone_id,
1685 				    record->name, record->type, record->data,
1686 				    record->ttl);
1687 		if (query == NULL) {
1688 			result = ISC_R_FAILURE;
1689 			goto cleanup;
1690 		}
1691 		result = db_execute(state, txn->dbi, query);
1692 		free(query);
1693 	} else {
1694 		/*
1695 		 * This is an SOA record, so we update: it must exist,
1696 		 * or we wouldn't have gotten this far.
1697 		 * SOA: zone admin serial refresh retry expire min
1698 		 */
1699 		char sn[32];
1700 		sscanf(record->data, "%*s %*s %31s %*s %*s %*s %*s", sn);
1701 		query = build_query(state, txn->dbi, U_SERIAL, sn,
1702 				    txn->zone_id);
1703 		if (query == NULL) {
1704 			result = ISC_R_FAILURE;
1705 			goto cleanup;
1706 		}
1707 		result = db_execute(state, txn->dbi, query);
1708 		free(query);
1709 	}
1710 
1711 cleanup:
1712 	free(record);
1713 	return (result);
1714 }
1715 
1716 isc_result_t
dlz_subrdataset(const char * name,const char * rdatastr,void * dbdata,void * version)1717 dlz_subrdataset(const char *name, const char *rdatastr, void *dbdata,
1718 		void *version) {
1719 	mysql_data_t *state = (mysql_data_t *)dbdata;
1720 	mysql_transaction_t *txn = (mysql_transaction_t *)version;
1721 	char *new_name, *query;
1722 	mysql_record_t *record;
1723 	isc_result_t result;
1724 
1725 	if (txn == NULL) {
1726 		return (ISC_R_FAILURE);
1727 	}
1728 
1729 	new_name = relname(name, txn->zone);
1730 	if (new_name == NULL) {
1731 		return (ISC_R_NOMEMORY);
1732 	}
1733 
1734 	if (state->debug && (state->log != NULL)) {
1735 		state->log(ISC_LOG_INFO, "%s: sub (%x) %s %s", modname, version,
1736 			   name, rdatastr);
1737 	}
1738 
1739 	record = makerecord(state, new_name, rdatastr);
1740 	free(new_name);
1741 	if (record == NULL) {
1742 		return (ISC_R_FAILURE);
1743 	}
1744 	/*
1745 	 * If 'type' isn't 'SOA', delete the records
1746 	 */
1747 	if (strcasecmp(record->type, "SOA") == 0) {
1748 		result = ISC_R_SUCCESS;
1749 	} else {
1750 		query = build_query(state, txn->dbi, D_RECORD, txn->zone_id,
1751 				    record->name, record->type, record->data,
1752 				    record->ttl);
1753 		if (query == NULL) {
1754 			result = ISC_R_NOMEMORY;
1755 			goto cleanup;
1756 		}
1757 
1758 		result = db_execute(state, txn->dbi, query);
1759 		free(query);
1760 	}
1761 
1762 cleanup:
1763 	free(record);
1764 	return (result);
1765 }
1766 
1767 isc_result_t
dlz_delrdataset(const char * name,const char * type,void * dbdata,void * version)1768 dlz_delrdataset(const char *name, const char *type, void *dbdata,
1769 		void *version) {
1770 	mysql_data_t *state = (mysql_data_t *)dbdata;
1771 	mysql_transaction_t *txn = (mysql_transaction_t *)version;
1772 	char *new_name, *query;
1773 	isc_result_t result;
1774 
1775 	if (txn == NULL) {
1776 		return (ISC_R_FAILURE);
1777 	}
1778 
1779 	new_name = relname(name, txn->zone);
1780 	if (new_name == NULL) {
1781 		return (ISC_R_NOMEMORY);
1782 	}
1783 
1784 	if (state->debug && (state->log != NULL)) {
1785 		state->log(ISC_LOG_INFO, "%s: del (%x) %s %s", modname, version,
1786 			   name, type);
1787 	}
1788 
1789 	query = build_query(state, txn->dbi, D_RRSET, txn->zone_id, new_name,
1790 			    type);
1791 	if (query == NULL) {
1792 		result = ISC_R_NOMEMORY;
1793 		goto cleanup;
1794 	}
1795 
1796 	result = db_execute(state, txn->dbi, query);
1797 	free(query);
1798 
1799 cleanup:
1800 	free(new_name);
1801 	return (result);
1802 }
1803