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