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