]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
[master] better DDNS in DLZ; mysqldyn
authorEvan Hunt <each@isc.org>
Fri, 25 Apr 2014 20:06:30 +0000 (13:06 -0700)
committerEvan Hunt <each@isc.org>
Fri, 25 Apr 2014 20:06:30 +0000 (13:06 -0700)
3821. [contrib] Added a new "mysqldyn" DLZ module with dynamic
update and transaction support. Thanks to Marty
Lee for the contribution. [RT #35656]

3820. [func] The DLZ API doesn't pass the database version to
the lookup() function; this can cause DLZ modules
that allow dynamic updates to mishandle prerequisite
checks. This has been corrected by adding a
'dbversion' field to the dns_clientinfo_t
structure. [RT #35656]

20 files changed:
CHANGES
bin/named/query.c
bin/named/update.c
bin/tests/system/dlzexternal/driver.c
bin/tests/system/dlzexternal/tests.sh
contrib/dlz/example/README
contrib/dlz/example/dlz_example.c
contrib/dlz/example/named.conf
contrib/dlz/modules/include/dlz_minimal.h
contrib/dlz/modules/include/dlz_pthread.h
contrib/dlz/modules/mysqldyn/Makefile [new file with mode: 0644]
contrib/dlz/modules/mysqldyn/README [new file with mode: 0644]
contrib/dlz/modules/mysqldyn/dlz_mysqldyn_mod.c [new file with mode: 0644]
contrib/dlz/modules/mysqldyn/testing/README [new file with mode: 0644]
contrib/dlz/modules/mysqldyn/testing/dlz.data [new file with mode: 0644]
contrib/dlz/modules/mysqldyn/testing/dlz.schema [new file with mode: 0644]
contrib/dlz/modules/mysqldyn/testing/named.conf [new file with mode: 0644]
lib/dns/clientinfo.c
lib/dns/include/dns/clientinfo.h
lib/dns/sdlz.c

diff --git a/CHANGES b/CHANGES
index 1b77f6e0cb6f225541192b08090aeb40cc18af79..156630eb3300af49e46db66d715ad00be7af3a3d 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,14 @@
+3821.  [contrib]       Added a new "mysqldyn" DLZ module with dynamic
+                       update and transaction support. Thanks to Marty
+                       Lee for the contribution. [RT #35656]
+
+3820.  [func]          The DLZ API doesn't pass the database version to
+                       the lookup() function; this can cause DLZ modules
+                       that allow dynamic updates to mishandle prerequisite
+                       checks. This has been corrected by adding a
+                       'dbversion' field to the dns_clientinfo_t
+                       structure. [RT #35656]
+
 3819.  [bug]           NSEC3 hashes need to be able to be entered and
                        displayed without padding.  This is not a issue for
                        currently defined algorithms but may be for future
index 2fc7d334ecc6e83e9b63d96ecc8b0a1000cbed9b..b90955ac3d32336407410230400fbb37273d6c54 100644 (file)
@@ -1118,7 +1118,7 @@ query_getdb(ns_client_t *client, dns_name_t *name, dns_rdatatype_t qtype,
                dns_clientinfo_t ci;
 
                dns_clientinfomethods_init(&cm, ns_client_sourceip);
-               dns_clientinfo_init(&ci, client);
+               dns_clientinfo_init(&ci, client, NULL);
 
                tresult = dns_view_searchdlz(client->view, name,
                                             zonelabels, &cm, &ci, &tdbp);
@@ -1254,7 +1254,7 @@ query_addadditional(void *arg, dns_name_t *name, dns_rdatatype_t qtype) {
        zone = NULL;
 
        dns_clientinfomethods_init(&cm, ns_client_sourceip);
-       dns_clientinfo_init(&ci, client);
+       dns_clientinfo_init(&ci, client, NULL);
 
        /*
         * We treat type A additional section processing as if it
@@ -1733,7 +1733,7 @@ query_addadditional2(void *arg, dns_name_t *name, dns_rdatatype_t qtype) {
        additionaltype = dns_rdatasetadditional_fromauth;
        dns_name_init(&cfname, NULL);
        dns_clientinfomethods_init(&cm, ns_client_sourceip);
-       dns_clientinfo_init(&ci, client);
+       dns_clientinfo_init(&ci, client, NULL);
 
        CTRACE("query_addadditional2");
 
@@ -2606,7 +2606,7 @@ query_addsoa(ns_client_t *client, dns_db_t *db, dns_dbversion_t *version,
        node = NULL;
 
        dns_clientinfomethods_init(&cm, ns_client_sourceip);
-       dns_clientinfo_init(&ci, client);
+       dns_clientinfo_init(&ci, client, NULL);
 
        /*
         * Don't add the SOA record for test which set "-T nosoa".
@@ -2731,7 +2731,7 @@ query_addns(ns_client_t *client, dns_db_t *db, dns_dbversion_t *version) {
        dns_fixedname_init(&foundname);
        fname = dns_fixedname_name(&foundname);
        dns_clientinfomethods_init(&cm, ns_client_sourceip);
-       dns_clientinfo_init(&ci, client);
+       dns_clientinfo_init(&ci, client, NULL);
 
        /*
         * Get resources and make 'name' be the database origin.
@@ -2899,7 +2899,7 @@ mark_secure(ns_client_t *client, dns_db_t *db, dns_name_t *name,
        rdataset->trust = dns_trust_secure;
        sigrdataset->trust = dns_trust_secure;
        dns_clientinfomethods_init(&cm, ns_client_sourceip);
-       dns_clientinfo_init(&ci, client);
+       dns_clientinfo_init(&ci, client, NULL);
 
        /*
         * Save the updated secure state.  Ignore failures.
@@ -2936,7 +2936,7 @@ get_key(ns_client_t *client, dns_db_t *db, dns_rdata_rrsig_t *rrsig,
        dns_clientinfo_t ci;
 
        dns_clientinfomethods_init(&cm, ns_client_sourceip);
-       dns_clientinfo_init(&ci, client);
+       dns_clientinfo_init(&ci, client, NULL);
 
        if (!dns_rdataset_isassociated(keyrdataset)) {
                result = dns_db_findnodeext(db, &rrsig->signer, ISC_FALSE,
@@ -3084,7 +3084,7 @@ query_addbestns(ns_client_t *client) {
        use_zone = ISC_FALSE;
 
        dns_clientinfomethods_init(&cm, ns_client_sourceip);
-       dns_clientinfo_init(&ci, client);
+       dns_clientinfo_init(&ci, client, NULL);
 
        /*
         * Find the right database.
@@ -3410,7 +3410,7 @@ query_addwildcardproof(ns_client_t *client, dns_db_t *db,
        node = NULL;
 
        dns_clientinfomethods_init(&cm, ns_client_sourceip);
-       dns_clientinfo_init(&ci, client);
+       dns_clientinfo_init(&ci, client, NULL);
 
        /*
         * Get the NOQNAME proof then if !ispositive
@@ -4141,8 +4141,9 @@ rpz_get_zbits(ns_client_t *client,
  */
 static isc_result_t
 rpz_rrset_find(ns_client_t *client, dns_name_t *name, dns_rdatatype_t type,
-              dns_rpz_type_t rpz_type, dns_db_t **dbp, dns_dbversion_t *version,
-              dns_rdataset_t **rdatasetp, isc_boolean_t resuming)
+              dns_rpz_type_t rpz_type, dns_db_t **dbp,
+              dns_dbversion_t *version, dns_rdataset_t **rdatasetp,
+              isc_boolean_t resuming)
 {
        dns_rpz_st_t *st;
        isc_boolean_t is_zone;
@@ -4153,6 +4154,9 @@ rpz_rrset_find(ns_client_t *client, dns_name_t *name, dns_rdatatype_t type,
        dns_clientinfomethods_t cm;
        dns_clientinfo_t ci;
 
+       dns_clientinfomethods_init(&cm, ns_client_sourceip);
+       dns_clientinfo_init(&ci, client, NULL);
+
        st = client->query.rpz_st;
        if ((st->state & DNS_RPZ_RECURSING) != 0) {
                INSIST(st->r.r_type == type);
@@ -4207,7 +4211,7 @@ rpz_rrset_find(ns_client_t *client, dns_name_t *name, dns_rdatatype_t type,
        dns_fixedname_init(&fixed);
        found = dns_fixedname_name(&fixed);
        dns_clientinfomethods_init(&cm, ns_client_sourceip);
-       dns_clientinfo_init(&ci, client);
+       dns_clientinfo_init(&ci, client, NULL);
        result = dns_db_findext(*dbp, name, version, type, DNS_DBFIND_GLUEOK,
                                client->now, &node, found,
                                &cm, &ci, *rdatasetp, NULL);
@@ -4345,6 +4349,9 @@ rpz_find_p(ns_client_t *client, dns_name_t *self_name, dns_rdatatype_t qtype,
 
        REQUIRE(nodep != NULL);
 
+       dns_clientinfomethods_init(&cm, ns_client_sourceip);
+       dns_clientinfo_init(&ci, client, NULL);
+
        /*
         * Try to find either a CNAME or the type of record demanded by the
         * request from the policy zone.
@@ -4359,8 +4366,7 @@ rpz_find_p(ns_client_t *client, dns_name_t *self_name, dns_rdatatype_t qtype,
                return (DNS_R_NXDOMAIN);
        dns_fixedname_init(&foundf);
        found = dns_fixedname_name(&foundf);
-       dns_clientinfomethods_init(&cm, ns_client_sourceip);
-       dns_clientinfo_init(&ci, client);
+
        result = dns_db_findext(*dbp, p_name, *versionp, dns_rdatatype_any, 0,
                                client->now, nodep, found, &cm, &ci,
                                *rdatasetp, NULL);
@@ -5761,7 +5767,7 @@ query_findclosestnsec3(dns_name_t *qname, dns_db_t *db,
        dns_name_clone(qname, &name);
        labels = dns_name_countlabels(&name);
        dns_clientinfomethods_init(&cm, ns_client_sourceip);
-       dns_clientinfo_init(&ci, client);
+       dns_clientinfo_init(&ci, client, NULL);
 
        /*
         * Map unknown algorithm to known value.
@@ -5966,7 +5972,7 @@ redirect(ns_client_t *client, dns_name_t *name, dns_rdataset_t *rdataset,
        dns_rdataset_init(&trdataset);
 
        dns_clientinfomethods_init(&cm, ns_client_sourceip);
-       dns_clientinfo_init(&ci, client);
+       dns_clientinfo_init(&ci, client, NULL);
 
        if (WANTDNSSEC(client) && dns_db_iszone(*dbp) && dns_db_issecure(*dbp))
                return (ISC_FALSE);
@@ -6119,7 +6125,7 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
        is_staticstub_zone = ISC_FALSE;
 
        dns_clientinfomethods_init(&cm, ns_client_sourceip);
-       dns_clientinfo_init(&ci, client);
+       dns_clientinfo_init(&ci, client, NULL);
 
        if (event != NULL) {
                /*
index fa1136de3285024b587d12ed45725d12197c72e4..4ae02649bcede8bccfbae5c13f964f33106ee103 100644 (file)
@@ -541,9 +541,21 @@ foreach_rrset(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
        isc_result_t result;
        dns_dbnode_t *node;
        dns_rdatasetiter_t *iter;
+       dns_clientinfomethods_t cm;
+       dns_clientinfo_t ci;
+       dns_dbversion_t *oldver = NULL;
+
+       dns_clientinfomethods_init(&cm, ns_client_sourceip);
+
+       /*
+        * Only set the clientinfo 'versionp' if the new version is
+        * different from the current version
+        */
+       dns_db_currentversion(db, &oldver);
+       dns_clientinfo_init(&ci, NULL, (ver != oldver) ? ver : NULL);
 
        node = NULL;
-       result = dns_db_findnode(db, name, ISC_FALSE, &node);
+       result = dns_db_findnodeext(db, name, ISC_FALSE, &cm, &ci, &node);
        if (result == ISC_R_NOTFOUND)
                return (ISC_R_SUCCESS);
        if (result != ISC_R_SUCCESS)
@@ -620,6 +632,18 @@ foreach_rr(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
        isc_result_t result;
        dns_dbnode_t *node;
        dns_rdataset_t rdataset;
+       dns_clientinfomethods_t cm;
+       dns_clientinfo_t ci;
+       dns_dbversion_t *oldver = NULL;
+
+       dns_clientinfomethods_init(&cm, ns_client_sourceip);
+
+       /*
+        * Only set the clientinfo 'versionp' if the new version is
+        * different from the current version
+        */
+       dns_db_currentversion(db, &oldver);
+       dns_clientinfo_init(&ci, NULL, (ver != oldver) ? ver : NULL);
 
        if (type == dns_rdatatype_any)
                return (foreach_node_rr(db, ver, name,
@@ -630,7 +654,8 @@ foreach_rr(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
            (type == dns_rdatatype_rrsig && covers == dns_rdatatype_nsec3))
                result = dns_db_findnsec3node(db, name, ISC_FALSE, &node);
        else
-               result = dns_db_findnode(db, name, ISC_FALSE, &node);
+               result = dns_db_findnodeext(db, name, ISC_FALSE,
+                                           &cm, &ci, &node);
        if (result == ISC_R_NOTFOUND)
                return (ISC_R_SUCCESS);
        if (result != ISC_R_SUCCESS)
index fa0021cdaa622edf5f66dfd0024d053d3ff49584..01145b5d8e2de33338d192171f969518ae9aa1f3 100644 (file)
@@ -14,8 +14,6 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* $Id$ */
-
 /*
  * This provides a very simple example of an external loadable DLZ
  * driver, with update support.
@@ -251,7 +249,7 @@ dlz_create(const char *dlzname, unsigned int argc, char *argv[],
        }
        va_end(ap);
 
-       if (argc < 2) {
+       if (argc < 2 || argv[1][0] == '\0') {
                if (state->log != NULL)
                        state->log(ISC_LOG_ERROR,
                                   "dlz_example: please specify a zone name");
@@ -259,11 +257,16 @@ dlz_create(const char *dlzname, unsigned int argc, char *argv[],
                return (ISC_R_FAILURE);
        }
 
-       state->zone_name = strdup(argv[1]);
+       /* Ensure zone name is absolute */
+       state->zone_name = malloc(strlen(argv[1]) + 2);
        if (state->zone_name == NULL) {
                free(state);
                return (ISC_R_NOMEMORY);
        }
+       if (argv[1][strlen(argv[1]) - 1] == '.')
+               strcpy(state->zone_name, argv[1]);
+       else
+               sprintf(state->zone_name, "%s.", argv[1]);
 
        if (strcmp(state->zone_name, ".") == 0)
                extra = ".root";
@@ -313,7 +316,6 @@ dlz_destroy(void *dbdata) {
        free(state);
 }
 
-
 /*
  * See if we handle a given zone
  */
@@ -325,6 +327,7 @@ dlz_findzonedb(void *dbdata, const char *name,
        struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
        isc_sockaddr_t *src;
        char addrbuf[100];
+       char absolute[1024];
 
        strcpy(addrbuf, "unknown");
        if (methods != NULL &&
@@ -335,11 +338,11 @@ dlz_findzonedb(void *dbdata, const char *name,
                methods->sourceip(clientinfo, &src);
                fmt_address(src, addrbuf, sizeof(addrbuf));
        }
-       fprintf(stderr, "findzonedb: connection from: %s\n", addrbuf);
 
        state->log(ISC_LOG_INFO,
                   "dlz_example: dlz_findzonedb called with name '%s' "
-                  "in zone DB '%s'", name, state->zone_name);
+                  "in zone DB '%s' from %s",
+                  name, state->zone_name, addrbuf);
 
        /*
         * Returning ISC_R_NOTFOUND will cause the query logic to
@@ -374,6 +377,10 @@ dlz_findzonedb(void *dbdata, const char *name,
        if (strcasecmp(state->zone_name, name) == 0)
                return (ISC_R_SUCCESS);
 
+       snprintf(absolute, sizeof(absolute), "%s.", name);
+       if (strcasecmp(state->zone_name, absolute) == 0)
+               return (ISC_R_SUCCESS);
+
        return (ISC_R_NOTFOUND);
 }
 
@@ -394,6 +401,7 @@ dlz_lookup(const char *zone, const char *name, void *dbdata,
        isc_result_t result;
        struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
        isc_boolean_t found = ISC_FALSE;
+       void *dbversion = NULL;
        isc_sockaddr_t *src;
        char full_name[256];
        char buf[512];
@@ -410,6 +418,30 @@ dlz_lookup(const char *zone, const char *name, void *dbdata,
        } else
                snprintf(full_name, 255, "%s.%s", name, state->zone_name);
 
+       /*
+        * If we need to know the database version (as set in
+        * the 'newversion' dlz function) we can pick it up from the
+        * clientinfo.
+        *
+        * This allows a lookup to query the correct version of the DNS
+        * data, if the DLZ can differentiate between versions.
+        *
+        * For example, if a new database transaction is created by
+        * 'newversion', the lookup should query within the same
+        * transaction scope if it can.
+        *
+        * If the DLZ only operates on 'live' data, then version
+        * wouldn't necessarily be needed.
+        */
+       if (clientinfo != NULL &&
+           clientinfo->version >= DNS_CLIENTINFO_VERSION) {
+               dbversion = clientinfo->dbversion;
+               if (dbversion != NULL && *(isc_boolean_t *)dbversion)
+                       state->log(ISC_LOG_INFO,
+                                  "dlz_example: lookup against live "
+                                  "transaction\n");
+       }
+
        if (strcmp(name, "source-addr") == 0) {
                strcpy(buf, "unknown");
                if (methods != NULL &&
@@ -422,7 +454,8 @@ dlz_lookup(const char *zone, const char *name, void *dbdata,
                        fmt_address(src, buf, sizeof(buf));
                }
 
-               fprintf(stderr, "lookup: connection from: %s\n", buf);
+               state->log(ISC_LOG_INFO,
+                          "dlz_example: lookup connection from %s\n", buf);
 
                found = ISC_TRUE;
                result = state->putrr(lookup, "TXT", 0, buf);
@@ -642,6 +675,7 @@ modrdataset(struct dlz_example_data *state, const char *name,
            const char *rdatastr, struct record *list)
 {
        char *full_name, *dclass, *type, *data, *ttlstr, *buf;
+       char absolute[1024];
        isc_result_t result;
 #if defined(WIN32) || defined(_REENTRANT)
        char *saveptr = NULL;
@@ -679,6 +713,11 @@ modrdataset(struct dlz_example_data *state, const char *name,
        if (data == NULL)
                goto error;
 
+       if (name[strlen(name) - 1] != '.') {
+               snprintf(absolute, sizeof(absolute), "%s.", name);
+               name = absolute;
+       }
+
        result = add_name(state, list, name, type,
                          strtoul(ttlstr, NULL, 10), data);
        free(buf);
@@ -722,7 +761,6 @@ dlz_subrdataset(const char *name, const char *rdatastr,
        return (modrdataset(state, name, rdatastr, &state->deletes[0]));
 }
 
-
 isc_result_t
 dlz_delrdataset(const char *name, const char *type,
                void *dbdata, void *version)
index 9242baf35fcb294d62344501f2d76c51709d359a..5a8a385124e5b17b3822421462d5be712e5d7890 100644 (file)
@@ -67,6 +67,20 @@ status=`expr $status + $ret`
 test_update deny.example.nil. TXT "86400 TXT helloworld" "helloworld" should_fail && ret=1
 status=`expr $status + $ret`
 
+echo "I:testing prerequisites are checked correctly"
+ret=0
+cat > ns1/update.txt << EOF
+server 10.53.0.1 5300
+prereq nxdomain testdc3.example.nil
+update add testdc3.example.nil 86500 in a 10.53.0.12
+send
+EOF
+$NSUPDATE -k ns1/ddns.key ns1/update.txt > /dev/null 2>&1 && ret=1
+out=`$DIG $DIGOPTS +short a testdc3.example.nil`
+[ "$out" = "10.53.0.12" ] && ret=1
+[ "$ret" -eq 0 ] || echo "I:failed"
+status=`expr $status + $ret`
+
 echo "I:testing passing client info into DLZ driver"
 ret=0
 out=`$DIG $DIGOPTS +short -t txt -q source-addr.example.nil | grep -v '^;'`
index 3642de8ce33db63bd530838447793c73738f323b..b9eebf140722d169db81c129aaa7373874da2b7c 100644 (file)
@@ -87,6 +87,27 @@ responses.  (Normally, this feature would be used to alter responses in
 some other fashion, e.g., by providing different address records for
 a particular name depending on the network from which the query arrived.)
 
+DYNAMIC UPDATES AND TRANSACTIONS:
+
+If a DLZ module wants to implement dynamic DNS updates (DDNS), the
+normal calling sequence is
+ - dlz_newversion (start a 'transaction')
+ - dlz_addrdataset (add records)
+ - dlz_subrdataset (remove records)
+ - dlz_closeversion (end a 'transaction')
+
+However, BIND may also query the database during the transaction
+(e.g., to check prerequisites), and your DLZ might need to know whether
+the lookup is against the pre-existing data, or the new data.
+dlz_lookup() doesn't give you access to the 'versionp' pointer
+directly, so it must be passed via 'clientinfo' structure if
+it is needed.
+
+The dlz_example.c code has sample code to show how to get the 'versionp'
+pointer from within dlz_lookup(). If it's set to NULL, we query
+the standard database; if non-NULL, we query against the in-flight
+data within the appropriate uncommitted transaction.
+
 IMPLEMENTATION NOTES:
 
 The minimal set of type definitions, prototypes, and macros needed
index 706db9cf9fd7327e679e387456842e65b325704f..5e522e136e9749649c474d2cdbdd723ba7612d8b 100644 (file)
@@ -14,8 +14,6 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* $Id$ */
-
 /*
  * This provides a very simple example of an external loadable DLZ
  * driver, with update support.
@@ -27,7 +25,7 @@
 #include <stdarg.h>
 #include <stdint.h>
 
-#include "../modules/dlz_minimal.h"
+#include "../modules/include/dlz_minimal.h"
 
 #ifdef WIN32
 #define STRTOK_R(a, b, c)      strtok_s(a, b, c)
@@ -239,7 +237,7 @@ dlz_create(const char *dlzname, unsigned int argc, char *argv[],
        }
        va_end(ap);
 
-       if (argc < 2) {
+       if (argc < 2 || argv[1][0] == '\0') {
                if (state->log != NULL)
                        state->log(ISC_LOG_ERROR,
                                   "dlz_example: please specify a zone name");
@@ -247,11 +245,16 @@ dlz_create(const char *dlzname, unsigned int argc, char *argv[],
                return (ISC_R_FAILURE);
        }
 
-       state->zone_name = strdup(argv[1]);
+       /* Ensure zone name is absolute */
+       state->zone_name = malloc(strlen(argv[1]) + 2);
        if (state->zone_name == NULL) {
                free(state);
                return (ISC_R_NOMEMORY);
        }
+       if (argv[1][strlen(argv[1]) - 1] == '.')
+               strcpy(state->zone_name, argv[1]);
+       else
+               sprintf(state->zone_name, "%s.", argv[1]);
 
        if (strcmp(state->zone_name, ".") == 0)
                extra = ".root";
@@ -301,7 +304,6 @@ dlz_destroy(void *dbdata) {
        free(state);
 }
 
-
 /*
  * See if we handle a given zone
  */
@@ -313,6 +315,7 @@ dlz_findzonedb(void *dbdata, const char *name,
        struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
        isc_sockaddr_t *src;
        char addrbuf[100];
+       char absolute[1024];
 
        strcpy(addrbuf, "unknown");
        if (methods != NULL &&
@@ -324,7 +327,7 @@ dlz_findzonedb(void *dbdata, const char *name,
                fmt_address(src, addrbuf, sizeof(addrbuf));
        }
        state->log(ISC_LOG_INFO,
-                  "dlz_example: findzonedb connection from: %s\n", addrbuf);
+                  "dlz_example: findzonedb connection from: %s", addrbuf);
 
        state->log(ISC_LOG_INFO,
                   "dlz_example: dlz_findzonedb called with name '%s' "
@@ -351,6 +354,10 @@ dlz_findzonedb(void *dbdata, const char *name,
        if (strcasecmp(state->zone_name, name) == 0)
                return (ISC_R_SUCCESS);
 
+       snprintf(absolute, sizeof(absolute), "%s.", name);
+       if (strcasecmp(state->zone_name, absolute) == 0)
+               return (ISC_R_SUCCESS);
+
        return (ISC_R_NOTFOUND);
 }
 
@@ -372,6 +379,7 @@ dlz_lookup(const char *zone, const char *name, void *dbdata,
        isc_result_t result;
        struct dlz_example_data *state = (struct dlz_example_data *)dbdata;
        isc_boolean_t found = ISC_FALSE;
+       void *dbversion = NULL;
        isc_sockaddr_t *src;
        char full_name[256];
        char buf[512];
@@ -388,6 +396,30 @@ dlz_lookup(const char *zone, const char *name, void *dbdata,
        } else
                snprintf(full_name, 255, "%s.%s", name, state->zone_name);
 
+       /*
+        * If we need to know the database version (as set in
+        * the 'newversion' dlz function) we can pick it up from the
+        * clientinfo.
+        *
+        * This allows a lookup to query the correct version of the DNS
+        * data, if the DLZ can differentiate between versions.
+        *
+        * For example, if a new database transaction is created by
+        * 'newversion', the lookup should query within the same
+        * transaction scope if it can.
+        *
+        * If the DLZ only operates on 'live' data, then version
+        * wouldn't necessarily be needed.
+        */
+       if (clientinfo != NULL &&
+           clientinfo->version >= DNS_CLIENTINFO_VERSION) {
+               dbversion = clientinfo->dbversion;
+               if (dbversion != NULL && *(isc_boolean_t *)dbversion)
+                       state->log(ISC_LOG_INFO,
+                                  "dlz_example: lookup against live "
+                                  "transaction\n");
+       }
+
        if (strcmp(name, "source-addr") == 0) {
                strcpy(buf, "unknown");
                if (methods != NULL &&
@@ -401,7 +433,7 @@ dlz_lookup(const char *zone, const char *name, void *dbdata,
                }
 
                state->log(ISC_LOG_INFO,
-                          "dlz_example: lookup connection from: %s\n", buf);
+                          "dlz_example: lookup connection from: %s", buf);
 
                found = ISC_TRUE;
                result = state->putrr(lookup, "TXT", 0, buf);
@@ -619,6 +651,7 @@ modrdataset(struct dlz_example_data *state, const char *name,
            const char *rdatastr, struct record *list)
 {
        char *full_name, *dclass, *type, *data, *ttlstr, *buf;
+       char absolute[1024];
        isc_result_t result;
 #if defined(WIN32) || defined(_REENTRANT)
        char *saveptr = NULL;
@@ -656,6 +689,11 @@ modrdataset(struct dlz_example_data *state, const char *name,
        if (data == NULL)
                goto error;
 
+       if (name[strlen(name) - 1] != '.') {
+               snprintf(absolute, sizeof(absolute), "%s.", name);
+               name = absolute;
+       }
+
        result = add_name(state, list, name, type,
                          strtoul(ttlstr, NULL, 10), data);
        free(buf);
@@ -699,7 +737,6 @@ dlz_subrdataset(const char *name, const char *rdatastr,
        return (modrdataset(state, name, rdatastr, &state->deletes[0]));
 }
 
-
 isc_result_t
 dlz_delrdataset(const char *name, const char *type,
                void *dbdata, void *version)
index 5c9e1962280e478883fcf3d6cf0855d4b956d506..bc68beebc6a2fe6eaf47168b3554a2ac0fd6bb72 100644 (file)
@@ -14,8 +14,6 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* $Id: named.conf,v 1.2 2011/10/20 22:01:48 each Exp $ */
-
 /*
  * This is a sample named.conf file that uses the DLZ module defined in
  * dlz_example.c.  It sets up a zone 'example.nil' which can accept DDNS
@@ -45,6 +43,21 @@ options {
        recursion no;
 };
 
+/*
+ * To test dynamic updates, create a DDNS key:
+ *
+ *   ddns-confgen -q -z example.nil > ddns.key
+ *
+ * Then uncomment the following line:
+ *
+ *   include "ddns.key";
+ *
+ * Use "nsupdate -k ddns.key" when sending updates. (NOTE: This driver does
+ * not check the key that's used: as long as the update is signed by a key
+ * known to named, the update will be accepted.  Only updates to names
+ * that begin with "deny." are rejected.)
+ */
+
 dlz "example" {
        database "dlopen ./dlz_example.so example.nil";
 };
index 6019a1236fc9b09dac2670ad64df736a2b6f7ec6..e3d61832a591872a35bf77913b7d69be70889a5f 100644 (file)
@@ -80,6 +80,12 @@ typedef uint32_t dns_ttl_t;
 
 /* other useful definitions */
 #define UNUSED(x) (void)(x)
+#define DE_CONST(konst, var) \
+       do { \
+               union { const void *k; void *v; } _u; \
+               _u.k = konst; \
+               var = _u.v; \
+       } while (0)
 
 /* opaque structures */
 typedef void *dns_sdlzlookup_t;
@@ -105,22 +111,26 @@ typedef struct isc_sockaddr {
        void *                          link;
 } isc_sockaddr_t;
 
-#define DNS_CLIENTINFO_VERSION 1
+#define DNS_CLIENTINFO_VERSION 2
 typedef struct dns_clientinfo {
        uint16_t version;
        void *data;
+       void *dbversion;
 } dns_clientinfo_t;
 
 typedef isc_result_t (*dns_clientinfo_sourceip_t)(dns_clientinfo_t *client,
                                                  isc_sockaddr_t **addrp);
 
-#define DNS_CLIENTINFOMETHODS_VERSION 1
-#define DNS_CLIENTINFOMETHODS_AGE 0
+typedef isc_result_t (*dns_clientinfo_version_t)(dns_clientinfo_t *client,
+                                                 void **addrp);
 
+#define DNS_CLIENTINFOMETHODS_VERSION 2
+#define DNS_CLIENTINFOMETHODS_AGE 1
 typedef struct dns_clientinfomethods {
        uint16_t version;
        uint16_t age;
        dns_clientinfo_sourceip_t sourceip;
+       dns_clientinfo_version_t dbversion;
 } dns_clientinfomethods_t;
 #endif /* DLZ_DLOPEN_VERSION > 1 */
 
index 993e9fc4cc078f6c1d3e8c087230c0bfef592320..6aa5e26c23a835576a471ff791bbc77736ddaa19 100644 (file)
 #define dlz_mutex_t pthread_mutex_t
 #define dlz_mutex_init pthread_mutex_init
 #define dlz_mutex_destroy pthread_mutex_destroy
+#define dlz_mutex_lock pthread_mutex_lock
 #define dlz_mutex_trylock pthread_mutex_trylock
 #define dlz_mutex_unlock pthread_mutex_unlock
 #else /* !PTHREADS */
 #define dlz_mutex_t void
 #define dlz_mutex_init(a, b) (0)
 #define dlz_mutex_destroy(a) (0)
+#define dlz_mutex_lock(a) (0)
 #define dlz_mutex_trylock(a) (0)
 #define dlz_mutex_unlock(a) (0)
 #endif
diff --git a/contrib/dlz/modules/mysqldyn/Makefile b/contrib/dlz/modules/mysqldyn/Makefile
new file mode 100644 (file)
index 0000000..5fb48d6
--- /dev/null
@@ -0,0 +1,21 @@
+prefix = /usr
+libdir = $(prefix)/lib/bind9
+
+CFLAGS=-fPIC -g -I../include
+MYSQL_LIBS=-lmysqlclient
+
+all: dlz_mysqldyn_mod.so
+
+dlz_dbi.o: ../common/dlz_dbi.c
+       $(CC) $(CFLAGS) -c ../common/dlz_dbi.c
+
+dlz_mysqldyn_mod.so: dlz_mysqldyn_mod.c dlz_dbi.o
+       $(CC) $(CFLAGS) -shared -o dlz_mysqldyn_mod.so \
+               dlz_mysqldyn_mod.c dlz_dbi.o $(MYSQL_LIBS)
+
+clean:
+       rm -f dlz_mysqldyn_mod.so *.o
+
+install: dlz_mysqldyn_mod.so
+       mkdir -p $(DESTDIR)$(libdir)
+       install dlz_mysqldyn_mod.so $(DESTDIR)$(libdir)
diff --git a/contrib/dlz/modules/mysqldyn/README b/contrib/dlz/modules/mysqldyn/README
new file mode 100644 (file)
index 0000000..468e37c
--- /dev/null
@@ -0,0 +1,60 @@
+BIND 9 DLZ MySQL module with support for dynamic DNS (DDNS)
+
+Adapted from code contributed by Marty Lee, Maui Systems Ltd.
+
+This is a dynamically loadable zone (DLZ) plugin that uses a fixed-
+schema MySQL database for back-end storage. It allows zone data
+to be updated via dynamic DNS updates, and sends DNS NOTIFY packets
+to other name servers when appropriate.
+
+The database for this module uses the following schema:
+
+    CREATE TABLE `Zones` (
+        `id` int(11) NOT NULL AUTO_INCREMENT,
+        `domain` varchar(128) NOT NULL DEFAULT '',
+        `host` varchar(128) NOT NULL DEFAULT '',
+        `admin` varchar(128) NOT NULL DEFAULT '',
+        `serial` int(11) NOT NULL DEFAULT '1',
+        `expire` int(11) NOT NULL DEFAULT '86400',
+        `refresh` int(11) NOT NULL DEFAULT '86400',
+        `retry` int(11) NOT NULL DEFAULT '86400',
+        `minimum` int(11) NOT NULL DEFAULT '86400',
+        `ttl` int(11) NOT NULL DEFAULT '86400',
+        `writeable` tinyint(1) NOT NULL DEFAULT '0',
+        PRIMARY KEY (`id`),
+        KEY `domain_idx` (`domain`)
+    );
+
+    CREATE TABLE `ZoneData` (
+        `id` int(11) NOT NULL AUTO_INCREMENT,
+        `zone_id` int(11) NOT NULL,
+        `name` varchar(128) NOT NULL DEFAULT '',
+        `type` varchar(16) NOT NULL DEFAULT '',
+        `ttl` int(11) NOT NULL DEFAULT '86400',
+        `data` varchar(128) NOT NULL DEFAULT '',
+        PRIMARY KEY (`id`),
+        KEY `zone_idx` (`zone_id`),
+        KEY `name_idx` (`zone_id`, `name`),
+        KEY `type_idx` (`type`)
+    );
+
+'Zones' contains information about specific zones:
+ - domain: the zone name
+ - admin: the zone administrator
+ - serial, expire, reresh, retry, minimum: values in the SOA record
+ - ttl: default zone TTL
+ - writeable: set to true if the zone can be updated via DDNS
+
+'ZoneData' contains the individual records within the zone:
+ - zone_id: the 'id' from the corresponding record in Zones 
+ - name: domain name, relative to the zone apex. (Data at the zone
+   apex itself may use a blank name or "@".)
+ - type: the RR type, expressed as text
+ - ttl: the record's TTL
+ - data: the records rdata, expressed as text.
+
+To configure this module in named.conf:
+
+dlz "mysqldlz" {
+    database "dlopen <path to>/dlz_mysqldyn_mod.so <dbname> [dbhost [dbuser [dbpass]]]";
+};
diff --git a/contrib/dlz/modules/mysqldyn/dlz_mysqldyn_mod.c b/contrib/dlz/modules/mysqldyn/dlz_mysqldyn_mod.c
new file mode 100644 (file)
index 0000000..c0ee9f3
--- /dev/null
@@ -0,0 +1,1723 @@
+/*
+ * Copyright (C) 2014 Maui Systems Ltd, Scotland, contact@maui-systems.co.uk.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND MAUI SYSTEMS LTD DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL MAUI SYSTEMS LTD  BE
+ * LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR
+ * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+ * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+/*
+ * Copyright (C) 2011,2014  Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * BIND 9 DLZ MySQL module with support for dynamic DNS (DDNS)
+ *
+ * Adapted from code contributed by Marty Lee, Maui Systems Ltd.
+ *
+ * See README for database schema and usage details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <netdb.h>
+#include <ifaddrs.h>
+
+#include <mysql/mysql.h>
+#include <mysql/errmsg.h>
+
+#include <dlz_minimal.h>
+#include <dlz_list.h>
+#include <dlz_pthread.h>
+
+/*
+ * The SQL queries that will be used for lookups and updates are defined
+ * here.  They will be processed into queries by the build_query()
+ * function.
+ *
+ * NOTE: Despite appearances, these do NOT use printf-style formatting.
+ * "%s", with no modifiers, is the only supported directive.
+ */
+
+/*
+ * Get the NS RRset for a zone
+ * Arguments: zone-name
+ */
+#define Q_GETNS \
+       "SELECT d.data FROM ZoneData d, Zones z " \
+       "WHERE UPPER(d.type) = 'NS' AND LOWER(z.domain) = LOWER('%s') " \
+       "AND z.id = d.zone_id"
+
+/*
+ * Get a list of zones (ignoring writable or not)
+ * Arguments: (none)
+ */
+#define Q_GETZONES "SELECT LOWER(domain), serial FROM Zones"
+
+/*
+ * Find a specific zone
+ * Arguments: zone-name
+ */
+#define Q_FINDZONE \
+       "SELECT id FROM Zones WHERE LOWER(domain) = LOWER('%s')"
+
+/*
+ * Get SOA data from zone apex
+ * Arguments: zone-name
+ */
+#define Q_GETSOA \
+       "SELECT host, admin, serial, refresh, retry, expire, minimum, ttl " \
+       "FROM Zones WHERE LOWER(domain) = LOWER('%s')"
+
+/*
+ * Get other data from zone apex
+ * Arguments: zone-name, zone-name (repeated)
+ */
+#define Q_GETAPEX \
+       "SELECT d.type, d.data, d.ttl FROM ZoneData d, Zones z " \
+       "WHERE LOWER(z.domain) = LOWER('%s') AND z.id = d.zone_id " \
+       "AND LOWER(d.name) IN (LOWER('%s'), '', '@') "\
+       "ORDER BY UPPER(d.type) ASC"
+
+/*
+ * Get data from non-apex nodes
+ * Arguments: zone-name, node-name (relative to zone name)
+ */
+#define Q_GETNODE \
+       "SELECT d.type, d.data, d.ttl FROM ZoneData d, Zones z " \
+       "WHERE LOWER(z.domain) = LOWER('%s') AND z.id = d.zone_id " \
+       "AND LOWER(d.name) = LOWER('%s') " \
+       "ORDER BY UPPER(d.type) ASC"
+
+/*
+ * Get all data from a zone, for AXFR
+ * Arguments: zone-name
+ */
+#define Q_GETALL \
+       "SELECT d.name, d.type, d.data, d.ttl FROM ZoneData d, Zones z " \
+       "WHERE LOWER(z.domain) = LOWER('%s') AND z.id = d.zone_id"
+
+/*
+ * Get SOA serial number for a zone.
+ * Arguments: zone-name
+ */
+#define Q_GETSERIAL \
+       "SELECT serial FROM Zones WHERE domain = '%s'"
+
+/*
+ * Determine whether a zone is writeable, and if so, retrieve zone_id
+ * Arguments: zone-name
+ */
+#define Q_WRITEABLE \
+       "SELECT id FROM Zones WHERE " \
+       "LOWER(domain) = LOWER('%s') AND writeable = 1"
+
+/*
+ * Insert data into zone (other than SOA)
+ * Arguments: zone-id (from Q_WRITEABLE), node-name (relative to zone-name),
+ *           rrtype, rdata text, TTL (in text format)
+ */
+#define I_DATA \
+       "INSERT INTO ZoneData (zone_id, name, type, data, ttl) " \
+       "VALUES (%s, LOWER('%s'), UPPER('%s'), '%s', %s)"
+
+/*
+ * Update SOA serial number for a zone
+ * Arguments: new serial number (in text format), zone-id (from Q_WRITEABLE)
+ */
+#define U_SERIAL \
+       "UPDATE Zones SET serial = %s WHERE id = %s"
+
+/*
+ * Delete a specific record (non-SOA) from a zone
+ *
+ * Arguments: node-name (relative to zone-name), zone-id (from Q_WRITEABLE),
+ *           rrtype, rdata text, TTL (in text format).
+ */
+#define D_RECORD \
+       "DELETE FROM ZoneData WHERE zone_id = %s AND " \
+       "LOWER(name) = LOWER('%s') AND UPPER(type) = UPPER('%s') AND " \
+       "data = '%s' AND ttl = %s"
+
+/*
+ * Delete an entire rrset from a zone
+ * Arguments: node-name (relative to zone-name), zone-id (from Q_WRITEABLE),
+ *           rrtype.
+ */
+#define D_RRSET \
+       "DELETE FROM ZoneData WHERE zone_id = %s AND " \
+       "LOWER(name) = LOWER('%s') AND UPPER(type) = UPPER('%s')"
+
+#ifdef WIN32
+#define STRTOK_R(a, b, c)       strtok_s(a, b, c)
+#elif defined(_REENTRANT)
+#define STRTOK_R(a, b, c)       strtok_r(a, b, c)
+#else
+#define STRTOK_R(a, b, c)       strtok(a, b)
+#endif
+
+/*
+ * Number of concurrent database connections we support
+ * - equivalent to maxmium number of concurrent transactions
+ *   that can be 'in-flight' + 1
+ */
+#define MAX_DBI 16
+
+typedef struct mysql_record {
+       char zone[255];
+       char name[100];
+       char type[10];
+       char data[200];
+       char ttl[10];
+} mysql_record_t;
+
+typedef struct mysql_instance {
+       int id;
+       MYSQL *sock;
+       int connected;
+       dlz_mutex_t mutex;
+} mysql_instance_t;
+
+typedef struct mysql_transaction mysql_transaction_t;
+struct mysql_transaction {
+       char *zone;
+       char *zone_id;
+       mysql_instance_t *dbi;
+       mysql_transaction_t *next;
+};
+
+typedef struct mysql_data {
+       int debug;
+
+       /*
+        * Database connection details
+        */
+       char *db_name;
+       char *db_host;
+       char *db_user;
+       char *db_pass;
+
+       /*
+        * Database structures
+        */
+       mysql_instance_t db[MAX_DBI];
+
+       /*
+        * Transactions
+        */
+       mysql_transaction_t *transactions;
+
+       /*
+        * Mutex for transactions
+        */
+       dlz_mutex_t tx_mutex;
+
+       /* Helper functions from the dlz_dlopen driver */
+       log_t *log;
+       dns_sdlz_putrr_t *putrr;
+       dns_sdlz_putnamedrr_t *putnamedrr;
+       dns_dlz_writeablezone_t *writeable_zone;
+} mysql_data_t;
+
+typedef struct mysql_arg mysql_arg_t;
+typedef DLZ_LIST(mysql_arg_t) mysql_arglist_t;
+struct mysql_arg {
+       char *arg;
+       DLZ_LINK(mysql_arg_t) link;
+};
+
+static const char *modname = "dlz_mysqldyn";
+
+/*
+ * Local functions
+ */
+static isc_boolean_t
+db_connect(mysql_data_t *state, mysql_instance_t *dbi) {
+       MYSQL *conn;
+       /*
+        * Make sure this thread has been through 'init'
+        */
+       mysql_thread_init();
+
+       if (dbi->connected)
+               return (ISC_TRUE);
+
+       if (state->log != NULL)
+               state->log(ISC_LOG_INFO, "%s: init connection %d ",
+                          modname, dbi->id);
+
+       conn = mysql_real_connect(dbi->sock, state->db_host,
+                                 state->db_user, state->db_pass,
+                                 state->db_name, 0, NULL,
+                                 CLIENT_REMEMBER_OPTIONS);
+       if (conn == NULL) {
+               if (state->log != NULL)
+                       state->log(ISC_LOG_ERROR,
+                                  "%s: database connection failed: %s",
+                                  modname, mysql_error(dbi->sock));
+
+               dlz_mutex_unlock(&dbi->mutex);
+               return (ISC_FALSE);
+       }
+
+       dbi->connected = 1;
+       return (ISC_TRUE);
+}
+
+static mysql_instance_t *
+get_dbi(mysql_data_t *state) {
+       int i;
+
+       /*
+        * Find an available dbi
+        */
+       for (i = 0; i < MAX_DBI; i++) {
+               if (dlz_mutex_trylock(&state->db[i].mutex) == 0)
+                       break;
+       }
+
+       if (i == MAX_DBI) {
+               if (state->debug && state->log != NULL)
+                       state->log(ISC_LOG_ERROR,
+                                  "%s: No available connections", modname);
+               return (NULL);
+       }
+       return (&state->db[i]);
+}
+
+/*
+ * Allocate memory and store an escaped, sanitized version
+ * of string 'original'
+ */
+static char *
+sanitize(mysql_instance_t *dbi, const char *original) {
+       char *s;
+
+       if (original == NULL)
+               return (NULL);
+
+       s = (char *) malloc((strlen(original) * 2) + 1);
+       if (s != NULL) {
+               memset(s, 0, (strlen(original) * 2) + 1);
+
+               mysql_real_escape_string(dbi->sock, s, original,
+                                        strlen(original));
+       }
+
+       return (s);
+}
+
+/*
+ * Append the string pointed to by 's' to the argument list 'arglist',
+ * and add the string length to the running total pointed to by 'len'.
+ */
+static isc_result_t
+additem(mysql_data_t *state, mysql_arglist_t *arglist, char **s, size_t *len) {
+       mysql_arg_t *item;
+
+       item = malloc(sizeof(*item));
+       if (item == NULL)
+               return (ISC_R_NOMEMORY);
+
+       DLZ_LINK_INIT(item, link);
+       DE_CONST(*s, item->arg);
+       *len += strlen(*s);
+       DLZ_LIST_APPEND(*arglist, item, link);
+       *s = NULL;
+
+       return (ISC_R_SUCCESS);
+}
+
+/*
+ * Construct a query string using a variable number of arguments, and
+ * save it into newly allocated memory.
+ *
+ * NOTE: 'command' resembles a printf-style format string, but ONLY
+ * supports the "%s" directive with no modifiers of any kind.
+ *
+ * If 'dbi' is NULL, we attempt to get a temporary database connection;
+ * otherwise we use the existing one.
+ */
+static char *
+build_query(mysql_data_t *state, mysql_instance_t *dbi,
+           const char *command, ...)
+{
+       isc_result_t result;
+       isc_boolean_t localdbi = ISC_FALSE;
+       mysql_arglist_t arglist;
+       mysql_arg_t *item;
+       char *p, *q, *tmp, *querystr;
+       char *query = NULL;
+       size_t len = 0;
+       const char *arg;
+       va_list ap1;
+
+       /* Get a DB instance if needed */
+       if (dbi == NULL) {
+               dbi = get_dbi(state);
+               if (dbi == NULL)
+                       return (NULL);
+               localdbi = ISC_TRUE;
+       }
+
+       /* Make sure this instance is connected */
+       if (!db_connect(state, dbi))
+               goto fail;
+
+       va_start(ap1, command);
+       DLZ_LIST_INIT(arglist);
+       q = querystr = strdup(command);
+       if (querystr == NULL)
+               goto fail;
+
+       for (;;) {
+               mysql_arg_t *item;
+
+               if (*q == '\0')
+                       break;
+
+               p = strstr(q, "%s");
+               if (p != NULL) {
+                       *p = '\0';
+                       tmp = strdup(q);
+                       if (tmp == NULL)
+                               goto fail;
+
+                       result = additem(state, &arglist, &tmp, &len);
+                       if (result != ISC_R_SUCCESS)
+                               goto fail;
+
+                       tmp = sanitize(dbi, va_arg(ap1, const char *));
+                       if (tmp == NULL)
+                               goto fail;
+
+                       result = additem(state, &arglist, &tmp, &len);
+                       if (result != ISC_R_SUCCESS)
+                               goto fail;
+
+                       q = p + 2;
+               } else {
+                       tmp = strdup(q);
+                       if (tmp == NULL)
+                               goto fail;
+
+                       result = additem(state, &arglist, &tmp, &len);
+                       if (result != ISC_R_SUCCESS)
+                               goto fail;
+
+                       break;
+               }
+       }
+
+       if (len == 0)
+               goto fail;
+
+       query = malloc(len + 1);
+       if (query == NULL)
+               goto fail;
+
+       *query = '\0';
+       for (item = DLZ_LIST_HEAD(arglist);
+            item != NULL;
+            item = DLZ_LIST_NEXT(item, link))
+               if (item->arg != NULL)
+                       strcat(query, item->arg);
+
+ fail:
+       va_end(ap1);
+
+       for (item = DLZ_LIST_HEAD(arglist);
+            item != NULL;
+            item = DLZ_LIST_NEXT(item, link))
+       {
+               if (item->arg != NULL)
+                       free(item->arg);
+               free(item);
+       }
+
+       if (tmp != NULL)
+               free(tmp);
+       if (querystr != NULL)
+               free (querystr);
+
+       if (dbi != NULL && localdbi)
+               dlz_mutex_unlock(&dbi->mutex);
+
+       return (query);
+}
+
+/* Does this name end in a dot? */
+static isc_boolean_t
+isrelative(const char *s) {
+       if (s == NULL || s[strlen(s) - 1] == '.')
+               return (ISC_FALSE);
+       return (ISC_TRUE);
+}
+
+/* Return a dot if 's' doesn't already end with one */
+static inline const char *
+dot(const char *s) {
+       return (isrelative(s) ? "." : "");
+}
+
+/*
+ * Generate a full hostname from a (presumably relative) name 'name'
+ * and a zone name 'zone'; store the result in 'dest' (which must have
+ * enough space).
+ */
+static void
+fqhn(const char *name, const char *zone, char *dest) {
+       if (dest == NULL)
+               return;
+
+       if (strlen(name) == 0 || strcmp(name, "@") == 0)
+               sprintf(dest, "%s%s", zone, dot(zone));
+       else {
+               if (isrelative(name))
+                       sprintf(dest, "%s.%s%s", name, zone, dot(zone));
+               else
+                       strcpy(dest, name);
+       }
+}
+
+/*
+ * Names are stored in relative form in ZoneData; this function
+ * removes labels matching 'zone' from the end of 'name'.
+ */
+static char *
+relname(const char *name, const char *zone) {
+       size_t nlen, zlen;
+       const char *p;
+       char *new;
+
+       new = (char *) malloc(strlen(name) + 1);
+       if (new == NULL)
+               return (NULL);
+
+       nlen = strlen(name);
+       zlen = strlen(zone);
+
+       if (nlen < zlen) {
+               strcpy(new, name);
+               return (new);
+       } else if (nlen == zlen || strcasecmp(name, zone) == 0) {
+               strcpy(new, "@");
+               return (new);
+       }
+
+       p = name + nlen - zlen;
+       if (strcasecmp(p, zone) != 0 &&
+           (zone[zlen - 1] != '.' ||
+            strncasecmp(p, zone, zlen - 1) != 0))
+       {
+               strcpy(new, name);
+               return (new);
+       }
+
+       strncpy(new, name, nlen - zlen);
+       new[nlen - zlen - 1] = '\0';
+       return (new);
+}
+
+static isc_result_t
+validate_txn(mysql_data_t *state, mysql_transaction_t *txn) {
+       isc_result_t result = ISC_R_FAILURE;
+       mysql_transaction_t *txp;
+
+       dlz_mutex_lock(&state->tx_mutex);
+       for (txp = state->transactions; txp != NULL; txp = txp->next) {
+               if (txn == txp) {
+                       result = ISC_R_SUCCESS;
+                       break;
+               }
+       }
+       dlz_mutex_unlock(&state->tx_mutex);
+
+       if (result != ISC_R_SUCCESS && state->log != NULL)
+               state->log(ISC_LOG_ERROR, "%s: invalid txn %x", modname, txn);
+
+       return (result);
+}
+
+static isc_result_t
+db_execute(mysql_data_t *state, mysql_instance_t *dbi, char *query) {
+       int ret;
+
+       /* Make sure this instance is connected.  */
+       if (!db_connect(state, dbi))
+               return (ISC_R_FAILURE);
+
+       ret = mysql_real_query(dbi->sock, query, strlen(query));
+       if (ret != 0) {
+               if (state->debug && state->log != NULL)
+                       state->log(ISC_LOG_ERROR,
+                                  "%s: query '%s' failed: %s",
+                                  modname, query, mysql_error(dbi->sock));
+               return (ISC_R_FAILURE);
+       }
+
+       if (state->debug && state->log != NULL)
+               state->log(ISC_LOG_INFO, "%s: execute(%d) %s",
+                          modname, dbi->id, query);
+
+       return (ISC_R_SUCCESS);
+}
+
+static MYSQL_RES *
+db_query(mysql_data_t *state, mysql_instance_t *dbi, char *query) {
+       isc_result_t result;
+       isc_boolean_t localdbi = ISC_FALSE;
+       MYSQL_RES *res = NULL;
+
+       if (query == NULL)
+               return (NULL);
+
+       /* Get a DB instance if needed */
+       if (dbi == NULL) {
+               dbi = get_dbi(state);
+               if (dbi == NULL)
+                       return (NULL);
+               localdbi = ISC_TRUE;
+       }
+
+       /* Make sure this instance is connected */
+       if (!db_connect(state, dbi))
+               goto fail;
+
+       result = db_execute(state, dbi, query);
+       if (result != ISC_R_SUCCESS)
+               goto fail;
+
+       res = mysql_store_result(dbi->sock);
+       if (res == NULL) {
+               if (state->log != NULL)
+                       state->log(ISC_LOG_ERROR,
+                                  "%s: unable to store result: %s",
+                                  modname, mysql_error(dbi->sock));
+               goto fail;
+       }
+
+       if (state->debug && state->log != NULL)
+               state->log(ISC_LOG_INFO,
+                          "%s: query(%d) returned %d rows",
+                          modname, dbi->id, mysql_num_rows(res));
+
+ fail:
+       if (dbi != NULL && localdbi)
+               dlz_mutex_unlock(&dbi->mutex);
+       return (res);
+}
+
+/*
+ * Generate a DNS NOTIFY packet:
+ * 12 bytes header
+ * Question (1)
+ *     strlen(zone) +2
+ *  2 bytes qtype
+ *  2 bytes qclass
+ *
+ * -> 18 bytes + strlen(zone)
+ *
+ * N.B. Need to be mindful of byte ordering; using htons to map 16bit
+ * values to the 'on the wire' packet values.
+ */
+static unsigned char *
+make_notify(const char *zone, int *packetlen) {
+       int i, j;
+       unsigned char *packet = (unsigned char *) malloc(strlen(zone) + 18);
+
+       if (packet == NULL)
+               return (NULL);
+
+       *packetlen = strlen(zone) + 18;
+       memset(packet, 0, *packetlen);
+
+       /* Random query ID */
+       i = rand();
+       packet[0] = htons(i) & 0xff;
+       packet[1] = htons(i) >> 8;
+
+       /* Flags (OpCode '4' in bits 14-11), Auth Answer set in bit 10 */
+       i = 0x2400;
+       packet[2] = htons(i) & 0xff;
+       packet[3] = htons(i) >> 8;
+
+       /* QD Count */
+       i = 0x1;
+       packet[4] = htons(i) & 0xff;
+       packet[5] = htons(i) >> 8;
+
+       /* Question */
+       packet[12] = '.';
+       memcpy(&packet[13], zone, strlen(zone));
+       packet[13 + strlen(zone)] = 0;
+
+       /* Make the question into labels */
+       j = 12;
+       while (packet[j]) {
+               for (i = j + 1; packet[i] != '\0' && packet[i] != '.'; i++);
+               packet[j] = i - j - 1;
+               j = i;
+       }
+
+       /* Question type */
+       i = 6;
+       packet[j + 1] = htons(i) & 0xff;
+       packet[j + 2] = htons(i) >> 8;
+
+       /* Queston class */
+       i = 1;
+       packet[j + 3] = htons(i) & 0xff;
+       packet[j + 4] = htons(i) >> 8;
+
+       return (packet);
+}
+
+static void
+send_notify(struct sockaddr_in *addr, const unsigned char *p, const int plen) {
+       int s;
+
+       addr->sin_family = AF_INET;
+       addr->sin_port = htons(53);
+
+       if ((s = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
+               return;
+
+       sendto(s, p, plen, 0, (struct sockaddr *)addr, sizeof(*addr));
+       close(s);
+       return;
+}
+
+/*
+ * Generate and send a DNS NOTIFY packet
+ */
+static void
+notify(mysql_data_t *state, const char *zone, int sn) {
+       MYSQL_RES *res;
+       MYSQL_ROW row;
+       char *query;
+       unsigned char *packet;
+       int packetlen;
+       struct ifaddrs *ifap, *ifa;
+       char zaddr[INET_ADDRSTRLEN];
+       void *addrp = NULL;
+
+       /* Get the name servers from the NS rrset */
+       query = build_query(state, NULL, Q_GETNS, zone);
+       res = db_query(state, NULL, query);
+       free (query);
+       if (res == NULL)
+               return;
+
+       /* Create a DNS NOTIFY packet */
+       packet = make_notify(zone, &packetlen);
+       if (packet == NULL) {
+               mysql_free_result(res);
+               return;
+       }
+
+       /* Get a list of our own addresses */
+       if (getifaddrs(&ifap) < 0)
+               ifap = NULL;
+
+       /* Tell each nameserver of the update */
+       while ((row = mysql_fetch_row(res)) != NULL) {
+               isc_boolean_t local = ISC_FALSE;
+               struct hostent *h;
+               struct sockaddr_in addr, *sin;
+               int rs;
+
+               /*
+                * Put nameserver rdata through gethostbyname as it
+                * might be an IP address or a hostname. (XXX: switch
+                * this to inet_pton/getaddrinfo.)
+                */
+               h = gethostbyname(row[0]);
+               if (h == NULL)
+                       continue;
+
+               memcpy(&addr.sin_addr, h->h_addr, h->h_length);
+               addrp = &addr.sin_addr;
+
+               /* Get the address for the nameserver into a string */
+               inet_ntop(AF_INET, addrp, zaddr, INET_ADDRSTRLEN);
+               for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
+                       char ifaddr[INET_ADDRSTRLEN];
+
+                       if (ifa->ifa_addr->sa_family != AF_INET)
+                               continue;
+
+                       /* Get local address into a string */
+                       sin = (struct sockaddr_in *) ifa->ifa_addr;
+                       addrp = &sin->sin_addr;
+                       inet_ntop(AF_INET, addrp, ifaddr, INET_ADDRSTRLEN);
+
+                       /* See if nameserver address matches this one */
+                       if (strcmp(ifaddr, zaddr) == 0)
+                               local = ISC_TRUE;
+               }
+
+               if (!local) {
+                       if (state->log != NULL)
+                               state->log(ISC_LOG_INFO,
+                                          "%s: notify %s zone %s serial %d",
+                                          modname, row[0], zone, sn);
+                       send_notify(&addr, packet, packetlen);
+               }
+       }
+
+       mysql_free_result(res);
+       free(packet);
+       if (ifap != NULL)
+               freeifaddrs(ifap);
+}
+
+/*
+ * Constructs a mysql_record_t structure from 'rdatastr', to be
+ * used in the dlz_{add,sub,del}rdataset functions below.
+ */
+static mysql_record_t *
+makerecord(mysql_data_t *state, const char *name, const char *rdatastr) {
+       mysql_record_t *new_record;
+       char *real_name, *dclass, *type, *data, *ttlstr, *buf;
+       char *saveptr = NULL;
+       dns_ttl_t ttlvalue;
+
+       new_record = (mysql_record_t *)
+               malloc(sizeof(mysql_record_t));
+
+       if (new_record == NULL) {
+               if (state->log != NULL)
+                       state->log(ISC_LOG_ERROR,
+                                  "%s: makerecord - unable to malloc",
+                                  modname);
+               return (NULL);
+       }
+
+       buf = strdup(rdatastr);
+       if (buf == NULL) {
+               if (state->log != NULL)
+                       state->log(ISC_LOG_ERROR,
+                                  "%s: makerecord - unable to malloc",
+                                  modname);
+               free(new_record);
+               return (NULL);
+       }
+
+       /*
+        * The format is:
+        * FULLNAME\tTTL\tDCLASS\tTYPE\tDATA
+        *
+        * The DATA field is space separated, and is in the data format
+        * for the type used by dig
+        */
+       real_name = STRTOK_R(buf, "\t", &saveptr);
+       if (real_name == NULL)
+               goto error;
+
+       ttlstr = STRTOK_R(NULL, "\t", &saveptr);
+       if (ttlstr == NULL || sscanf(ttlstr, "%d", &ttlvalue) != 1)
+               goto error;
+
+       dclass = STRTOK_R(NULL, "\t", &saveptr);
+       if (dclass == NULL)
+               goto error;
+
+       type = STRTOK_R(NULL, "\t", &saveptr);
+       if (type == NULL)
+               goto error;
+
+       data = STRTOK_R(NULL, "\t", &saveptr);
+       if (data == NULL)
+               goto error;
+
+       strcpy(new_record->name, name);
+       strcpy(new_record->type, type);
+       strcpy(new_record->data, data);
+       sprintf(new_record->ttl, "%d", ttlvalue);
+
+       free(buf);
+       return (new_record);
+
+ error:
+       free(buf);
+       free(new_record);
+       return (NULL);
+}
+
+/*
+ * Remember a helper function from the bind9 dlz_dlopen driver
+ */
+static void
+b9_add_helper(mysql_data_t *state, const char *helper_name, void *ptr) {
+       if (strcmp(helper_name, "log") == 0)
+               state->log = (log_t *)ptr;
+       if (strcmp(helper_name, "putrr") == 0)
+               state->putrr = (dns_sdlz_putrr_t *)ptr;
+       if (strcmp(helper_name, "putnamedrr") == 0)
+               state->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr;
+       if (strcmp(helper_name, "writeable_zone") == 0)
+               state->writeable_zone = (dns_dlz_writeablezone_t *)ptr;
+}
+
+/*
+ * DLZ API functions
+ */
+
+/*
+ * Return the version of the API
+ */
+int
+dlz_version(unsigned int *flags) {
+       UNUSED(flags);
+       *flags |= DNS_SDLZFLAG_THREADSAFE;
+       return (DLZ_DLOPEN_VERSION);
+}
+
+/*
+ * Called to initialize the driver
+ */
+isc_result_t
+dlz_create(const char *dlzname, unsigned int argc, char *argv[],
+          void **dbdata, ...)
+{
+       mysql_data_t *state;
+       const char *helper_name;
+       va_list ap;
+       char soa_data[200];
+       const char *extra;
+       isc_result_t result;
+       int n;
+
+       UNUSED(dlzname);
+
+       state = calloc(1, sizeof(mysql_data_t));
+       dlz_mutex_init(&state->tx_mutex, NULL);
+       state->transactions=NULL;
+
+       if (state == NULL)
+               return (ISC_R_NOMEMORY);
+
+       /* Fill in the helper functions */
+       va_start(ap, dbdata);
+       while ((helper_name = va_arg(ap, const char *)) != NULL)
+               b9_add_helper(state, helper_name, va_arg(ap, void *));
+       va_end(ap);
+
+       if (state->log != NULL)
+               state->log(ISC_LOG_INFO, "loading %s module", modname);
+
+       if ((argc < 2) || (argc > 6)) {
+               if (state->log != NULL)
+                       state->log(ISC_LOG_ERROR,
+                                  "%s: missing args <dbname> "
+                                  "[<dbhost> [<user> <pass>]]", modname);
+               dlz_destroy(state);
+               return (ISC_R_FAILURE);
+       }
+
+       state->db_name = strdup(argv[1]);
+       if (argc > 2) {
+               state->db_host = strdup(argv[2]);
+               if (argc > 4) {
+                       state->db_user = strdup(argv[3]);
+                       state->db_pass = strdup(argv[4]);
+               } else {
+                       state->db_user = strdup("bind");
+                       state->db_pass = strdup("");
+               }
+       } else {
+               state->db_host = strdup("localhost");
+               state->db_user = strdup("bind");
+               state->db_pass = strdup("");
+       }
+
+       if (state->log != NULL)
+               state->log(ISC_LOG_INFO,
+                          "%s: DB=%s, Host=%s, User=%s",
+                          modname, state->db_name,
+                          state->db_host, state->db_user);
+
+       /*
+        * Assign the 'state' to dbdata so we get it in our callbacks
+        */
+
+       *dbdata = state;
+
+       dlz_mutex_lock(&state->tx_mutex);
+
+       /*
+        * Populate DB instances
+        */
+       if (mysql_thread_safe()) {
+               for (n = 0; n < MAX_DBI; n++) {
+                       my_bool opt = 1;
+                       dlz_mutex_init(&state->db[n].mutex, NULL);
+                       dlz_mutex_lock(&state->db[n].mutex);
+                       state->db[n].id = n;
+                       state->db[n].connected = 0;
+                       state->db[n].sock = mysql_init(NULL);
+                       mysql_options(state->db[n].sock,
+                                     MYSQL_READ_DEFAULT_GROUP,
+                                     modname);
+                       mysql_options(state->db[n].sock,
+                                     MYSQL_OPT_RECONNECT, &opt);
+                       dlz_mutex_unlock(&state->db[n].mutex);
+               }
+
+               dlz_mutex_unlock(&state->tx_mutex);
+               return (ISC_R_SUCCESS);
+       }
+
+ failure:
+       free(state->db_name);
+       free(state->db_host);
+       free(state->db_user);
+       free(state->db_pass);
+       dlz_mutex_destroy(&state->tx_mutex);
+       free(state);
+       return (result);
+}
+
+/*
+ * Shut down the backend
+ */
+void
+dlz_destroy(void *dbdata) {
+       mysql_data_t *state = (mysql_data_t *)dbdata;
+       int i;
+
+       if (state->debug && state->log != NULL)
+               state->log(ISC_LOG_INFO, "%s: shutting down", modname);
+
+       for (i = 0; i < MAX_DBI; i++) {
+               if (state->db[i].sock) {
+                       mysql_close(state->db[i].sock);
+                       state->db[i].sock=NULL;
+               }
+       }
+       free(state->db_name);
+       free(state->db_host);
+       free(state->db_user);
+       free(state->db_pass);
+       dlz_mutex_destroy(&state->tx_mutex);
+       free(state);
+}
+
+/*
+ * See if we handle a given zone
+ */
+isc_result_t
+dlz_findzonedb(void *dbdata, const char *name,
+              dns_clientinfomethods_t *methods,
+              dns_clientinfo_t *clientinfo)
+{
+       isc_result_t result = ISC_R_SUCCESS;
+       mysql_data_t *state = (mysql_data_t *)dbdata;
+       MYSQL_RES *res;
+       char *query;
+       int rs;
+
+       /* Query the Zones table to see if this zone is present */
+       query = build_query(state, NULL, Q_FINDZONE, name);
+
+       if (query == NULL)
+               return (ISC_R_NOMEMORY);
+
+       res = db_query(state, NULL, query);
+       if (res == NULL)
+               return (ISC_R_FAILURE);
+
+       if (mysql_num_rows(res) == 0)
+               result = ISC_R_NOTFOUND;
+
+       mysql_free_result(res);
+       return (result);
+}
+
+/*
+ * Perform a database lookup
+ */
+isc_result_t
+dlz_lookup(const char *zone, const char *name, void *dbdata,
+          dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods,
+          dns_clientinfo_t *clientinfo)
+{
+       isc_result_t result;
+       mysql_data_t *state = (mysql_data_t *)dbdata;
+       isc_boolean_t found = ISC_FALSE;
+       char *real_name;
+       MYSQL_RES *res;
+       MYSQL_ROW row;
+       char *query;
+       mysql_transaction_t *txn = NULL;
+       mysql_instance_t *dbi = NULL;
+       int rs;
+
+       if (state->putrr == NULL) {
+               if (state->log != NULL)
+                       state->log(ISC_LOG_ERROR,
+                                  "%s: dlz_lookup - no putrr", modname);
+               return (ISC_R_NOTIMPLEMENTED);
+       }
+
+       /* Are we okay to try to find the txn version?  */
+       if (clientinfo != NULL &&
+           clientinfo->version >= DNS_CLIENTINFO_VERSION) {
+               txn = (mysql_transaction_t *) clientinfo->dbversion;
+               if (txn != NULL && validate_txn(state, txn) == ISC_R_SUCCESS)
+                       dbi = txn->dbi;
+               if (dbi != NULL) {
+                       state->log(ISC_LOG_DEBUG(1),
+                                  "%s: lookup in live transaction %p, DBI %p",
+                                  modname, txn, dbi);
+               }
+       }
+
+       if (strcmp(name, "@") == 0) {
+               real_name = (char *) malloc(strlen(zone) + 1);
+               if (real_name == NULL)
+                       return (ISC_R_NOMEMORY);
+               strcpy(real_name, zone);
+       } else {
+               real_name = (char *) malloc(strlen(name) + 1);
+               if (real_name == NULL)
+                       return (ISC_R_NOMEMORY);
+               strcpy(real_name, name);
+       }
+
+       if (strcmp(real_name, zone) == 0) {
+               /*
+                * Get the Zones table data for use in the SOA:
+                * zone admin serial refresh retry expire min
+                */
+               query = build_query(state, dbi, Q_GETSOA, zone);
+               if (query == NULL) {
+                       free(real_name);
+                       return (ISC_R_NOMEMORY);
+               }
+
+               res = db_query(state, dbi, query);
+               free (query);
+
+               if (res == NULL) {
+                       free(real_name);
+                       return (ISC_R_NOTFOUND);
+               }
+
+               while ((row = mysql_fetch_row(res)) != NULL) {
+                       char host[1024], admin[1024], data[1024];
+                       int ttl;
+
+                       sscanf(row[7], "%d", &ttl);
+                       fqhn(row[0], zone, host);
+                       fqhn(row[1], zone, admin);
+
+                       /* zone admin serial refresh retry expire min */
+                       sprintf(data, "%s%s %s%s %s %s %s %s %s",
+                               host, dot(host), admin, dot(admin),
+                               row[2], row[3], row[4], row[5], row[6]);
+
+                       result = state->putrr(lookup, "soa", ttl, data);
+                       if (result != ISC_R_SUCCESS) {
+                               free(real_name);
+                               mysql_free_result(res);
+                               return (result);
+                       }
+               }
+
+               mysql_free_result(res);
+
+               /*
+                *  Now we'll get the rest of the apex data
+                */
+               query = build_query(state, dbi, Q_GETAPEX, zone, real_name);
+       } else
+               query = build_query(state, dbi, Q_GETNODE, zone, real_name);
+
+       res = db_query(state, dbi, query);
+       free(query);
+
+       if (res == NULL) {
+               free(real_name);
+               return (ISC_R_NOTFOUND);
+       }
+
+       while ((row = mysql_fetch_row(res)) != NULL) {
+               int ttl;
+               sscanf(row[2], "%d", &ttl);
+               result = state->putrr(lookup, row[0], ttl, row[1]);
+               if (result != ISC_R_SUCCESS) {
+                       free(real_name);
+                       mysql_free_result(res);
+                       return (result);
+               }
+
+               found = ISC_TRUE;
+       }
+
+       if (state->debug && state->log != NULL)
+               state->log(ISC_LOG_INFO,
+                          "%s: dlz_lookup %s/%s/%s - (%d rows)",
+                          modname, name, real_name, zone,
+                          mysql_num_rows(res));
+
+       mysql_free_result(res);
+       free(real_name);
+
+       if (!found)
+               return (ISC_R_NOTFOUND);
+
+       return (ISC_R_SUCCESS);
+}
+
+/*
+ * See if a zone transfer is allowed
+ */
+isc_result_t
+dlz_allowzonexfr(void *dbdata, const char *name, const char *client) {
+       mysql_data_t *state = (mysql_data_t *)dbdata;
+
+       if (state->debug && state->log != NULL)
+               state->log(ISC_LOG_INFO,
+                          "dlz_allowzonexfr: %s %s", name, client);
+
+       /* Just say yes for all our zones */
+       return (dlz_findzonedb(dbdata, name, NULL, NULL));
+}
+
+/*
+ * Perform a zone transfer
+ */
+isc_result_t
+dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) {
+       isc_result_t result = ISC_R_SUCCESS;
+       mysql_data_t *state = (mysql_data_t *)dbdata;
+       int i;
+       MYSQL_RES *res;
+       MYSQL_ROW row;
+       char *query;
+
+       UNUSED(zone);
+
+       if (state->debug && (state->log != NULL))
+               state->log(ISC_LOG_INFO, "dlz_allnodes: %s", zone);
+
+       if (state->putnamedrr == NULL)
+               return (ISC_R_NOTIMPLEMENTED);
+
+       /*
+        * Get all the ZoneData for this zone
+        */
+       query = build_query(state, NULL, Q_GETALL, zone);
+       if (query == NULL)
+               return (ISC_R_NOMEMORY);
+
+       res = db_query(state, NULL, query);
+       free(query);
+       if (res == NULL)
+               return (ISC_R_NOTFOUND);
+
+       while ((row = mysql_fetch_row(res)) != NULL) {
+               char hostname[1024];
+               int ttl;
+
+               sscanf(row[3], "%d", &ttl);
+               fqhn(row[0], zone, hostname);
+               result = state->putnamedrr(allnodes, hostname,
+                                          row[1], ttl, row[2]);
+               if (result != ISC_R_SUCCESS)
+                       break;
+       }
+
+       mysql_free_result(res);
+       return (result);
+}
+
+/*
+ * Start a transaction
+ */
+isc_result_t
+dlz_newversion(const char *zone, void *dbdata, void **versionp) {
+       isc_result_t result = ISC_R_FAILURE;
+       mysql_data_t *state = (mysql_data_t *) dbdata;
+       MYSQL_RES *res;
+       MYSQL_ROW row;
+       char *query;
+       char zone_id[16];
+       mysql_transaction_t *txn = NULL, *newtx = NULL;
+
+       /*
+        * Check Zone is writable
+        */
+       query = build_query(state, NULL, Q_WRITEABLE, zone);
+       if (query == NULL)
+               return (ISC_R_NOMEMORY);
+
+       res = db_query(state, NULL, query);
+       free(query);
+       if (res == NULL)
+               return (ISC_R_FAILURE);
+
+       if ((row = mysql_fetch_row(res)) == NULL) {
+               mysql_free_result(res);
+               return (ISC_R_FAILURE);
+       }
+
+       strcpy(zone_id, row[0]);
+       mysql_free_result(res);
+
+       /*
+        * See if we already have a transaction for this zone
+        */
+       dlz_mutex_lock(&state->tx_mutex);
+       for (txn = state->transactions; txn != NULL; txn = txn->next) {
+               if (strcmp(txn->zone, zone) == 0) {
+                       if (state->log != NULL)
+                               state->log(ISC_LOG_ERROR,
+                                  "%s: transaction already "
+                                  "started for zone %s", modname, zone);
+                       dlz_mutex_unlock(&state->tx_mutex);
+                       return (ISC_R_FAILURE);
+               }
+       }
+
+       /*
+        * Create new transaction
+        */
+       newtx = (mysql_transaction_t *)
+               malloc(sizeof(mysql_transaction_t));
+       newtx->zone = strdup(zone);
+       newtx->zone_id = strdup(zone_id);
+       newtx->dbi = get_dbi(state);
+       newtx->next = NULL;
+
+       if (newtx->dbi == NULL) {
+               result = ISC_R_FAILURE;
+               goto cleanup;
+       }
+
+       result = db_execute(state, newtx->dbi, "START TRANSACTION");
+       if (result != ISC_R_SUCCESS) {
+               dlz_mutex_unlock(&newtx->dbi->mutex);
+               goto cleanup;
+       }
+
+       /*
+        * Add this tx to front of list
+        */
+       newtx->next = state->transactions;
+       state->transactions = newtx;
+
+       if (state->debug && (state->log != NULL))
+               state->log(ISC_LOG_INFO, "%s: New tx %x", modname, newtx);
+
+ cleanup:
+       dlz_mutex_unlock(&state->tx_mutex);
+       if (result == ISC_R_SUCCESS) {
+               *versionp = (void *) newtx;
+       } else {
+               dlz_mutex_unlock(&state->tx_mutex);
+               free(newtx->zone);
+               free(newtx->zone_id);
+               free(newtx);
+       }
+
+       return (result);
+}
+
+/*
+ * End a transaction
+ */
+void
+dlz_closeversion(const char *zone, isc_boolean_t commit,
+                void *dbdata, void **versionp)
+{
+       isc_result_t result;
+       mysql_data_t *state = (mysql_data_t *)dbdata;
+       mysql_transaction_t *txn = (mysql_transaction_t *)*versionp;
+       mysql_transaction_t *txp;
+       char *query;
+       MYSQL_RES *res;
+       MYSQL_ROW row;
+
+       /*
+        * Find the transaction
+        */
+       dlz_mutex_lock(&state->tx_mutex);
+       if (state->transactions == txn) {
+               /* Tx is first in list; remove it. */
+               state->transactions = txn->next;
+       } else {
+               txp = state->transactions;
+               while (txp != NULL) {
+                       if (txp->next != NULL) {
+                               if (txp->next == txn) {
+                                       txp->next = txn->next;
+                                       break;
+                               }
+                       }
+                       if (txp == txn) {
+                               txp = txn->next;
+                               break;
+                       }
+                       txp = txp->next;
+               }
+       }
+
+       /*
+        * Tidy up
+        */
+       dlz_mutex_unlock(&state->tx_mutex);
+       *versionp = NULL;
+
+       if (commit) {
+               int oldsn = 0, newsn = 0;
+
+               /*
+                * Find out the serial number of the zone out with the
+                * transaction so we can see if it has incremented or not
+                */
+               query = build_query(state, txn->dbi, Q_GETSERIAL, zone);
+               if (query == NULL && state->log != NULL) {
+                       state->log(ISC_LOG_ERROR,
+                                  "%s: unable to commit transaction %x "
+                                  "on zone %s: no memory",
+                                  modname, txn, zone);
+                       return;
+               }
+
+               res = db_query(state, txn->dbi, query);
+               if (res != NULL) {
+                       while ((row = mysql_fetch_row(res)) != NULL)
+                               sscanf(row[0], "%d", &oldsn);
+                       mysql_free_result(res);
+               }
+
+               /*
+                * Commit the transaction to the database
+                */
+               result = db_execute(state, txn->dbi, "COMMIT");
+               if (result != ISC_R_SUCCESS && state->log != NULL) {
+                       state->log(ISC_LOG_INFO,
+                                  "%s: (%x) commit transaction on zone %s",
+                                  modname, txn, zone);
+                       return;
+               }
+
+               if (state->debug && state->log != NULL)
+                       state->log(ISC_LOG_INFO,
+                                  "%s: (%x) committing transaction "
+                                  "on zone %s",
+                                  modname, txn, zone);
+
+               /*
+                * Now get the serial number again
+                */
+               query = build_query(state, txn->dbi, Q_GETSERIAL, zone);
+               res = db_query(state, txn->dbi, query);
+               free(query);
+
+               if (res != NULL) {
+                       while ((row = mysql_fetch_row(res)) != NULL)
+                               sscanf(row[0], "%d", &newsn);
+                       mysql_free_result(res);
+               }
+
+               /*
+                * Look to see if serial numbers have changed
+                */
+               if (newsn > oldsn)
+                       notify(state, zone, newsn);
+       } else {
+               result = db_execute(state, txn->dbi, "ROLLBACK");
+               if (state->debug && (state->log != NULL))
+                       state->log(ISC_LOG_INFO,
+                                  "%s: (%x) roll back transaction on zone %s",
+                                  modname, txn, zone);
+       }
+
+ cleanup:
+       /*
+        * Unlock the mutex for this txn
+        */
+       dlz_mutex_unlock(&txn->dbi->mutex);
+
+       /*
+        * Free up other structures
+        */
+       free(txn->zone);
+       free(txn->zone_id);
+       free(txn);
+}
+
+/*
+ * Configure a writeable zone
+ */
+#if DLZ_DLOPEN_VERSION < 3
+isc_result_t
+dlz_configure(dns_view_t *view, void *dbdata)
+#else /* DLZ_DLOPEN_VERSION >= 3 */
+isc_result_t
+dlz_configure(dns_view_t *view, dns_dlzdb_t *dlzdb, void *dbdata)
+#endif /* DLZ_DLOPEN_VERSION */
+{
+       mysql_data_t *state = (mysql_data_t *)dbdata;
+       isc_result_t result;
+       MYSQL_RES *res;
+       MYSQL_ROW row;
+       char *query;
+       int count;
+
+       /*
+        * Seed PRNG (used by Notify code)
+        */
+       srand(getpid());
+
+       if (state->debug && state->log != NULL)
+               state->log(ISC_LOG_INFO, "%s: dlz_confgure", modname);
+
+       if (state->writeable_zone == NULL) {
+               if (state->log != NULL)
+                       state->log(ISC_LOG_ERROR,
+                                  "%s: no writeable_zone method available",
+                                  modname);
+               return (ISC_R_FAILURE);
+       }
+
+       /*
+        * Get a list of Zones (ignore writeable column at this point)
+        */
+       res = db_query(state, NULL, Q_GETZONES);
+       if (res == NULL)
+               return (ISC_R_FAILURE);
+
+       count = 0;
+       while ((row = mysql_fetch_row(res)) != NULL) {
+               int sn;
+               sscanf(row[1], "%d", &sn);
+               notify(state, row[0], sn);
+               result = state->writeable_zone(view,
+#if DLZ_DLOPEN_VERSION >= 3
+                                              dlzdb,
+#endif
+                                              row[0]);
+               if (result != ISC_R_SUCCESS) {
+                       if (state->log != NULL)
+                               state->log(ISC_LOG_ERROR,
+                                          "%s: failed to configure zone %s",
+                                          modname, row[0]);
+                       mysql_free_result(res);
+                       return (result);
+               }
+               count++;
+       }
+       mysql_free_result(res);
+
+       if (state->debug && state->log != NULL)
+               state->log(ISC_LOG_INFO,
+                          "%s: configured %d zones", modname, count);
+       return (ISC_R_SUCCESS);
+}
+
+/*
+ * Authorize a zone update
+ */
+isc_boolean_t
+dlz_ssumatch(const char *signer, const char *name, const char *tcpaddr,
+            const char *type, const char *key, uint32_t keydatalen,
+            unsigned char *keydata, void *dbdata)
+{
+       mysql_data_t *state = (mysql_data_t *)dbdata;
+
+       UNUSED(tcpaddr);
+       UNUSED(type);
+       UNUSED(keydatalen);
+       UNUSED(keydata);
+       UNUSED(key);
+
+       if (state->debug && state->log != NULL)
+               state->log(ISC_LOG_INFO,
+                          "%s: allowing update of %s by key %s",
+                          modname, name, signer);
+       return (ISC_TRUE);
+}
+
+isc_result_t
+dlz_addrdataset(const char *name, const char *rdatastr,
+               void *dbdata, void *version)
+{
+       mysql_data_t *state = (mysql_data_t *)dbdata;
+       mysql_transaction_t *txn = (mysql_transaction_t *)version;
+       char *new_name, *query;
+       mysql_record_t *record;
+       isc_result_t result;
+       size_t len;
+
+       if (txn == NULL)
+               return (ISC_R_FAILURE);
+
+       new_name = relname(name, txn->zone);
+       if (new_name == NULL)
+               return (ISC_R_NOMEMORY);
+
+       if (state->debug && (state->log != NULL))
+               state->log(ISC_LOG_INFO, "%s: add (%x) %s (as %s) %s",
+                          modname, version, name, new_name, rdatastr);
+
+       record = makerecord(state, new_name, rdatastr);
+       free(new_name);
+       if (record == NULL)
+               return (ISC_R_FAILURE);
+
+       /* Write out data to database */
+       if (strcasecmp(record->type, "SOA") != 0) {
+               query = build_query(state, txn->dbi, I_DATA,
+                                   txn->zone_id, record->name,
+                                   record->type, record->data,
+                                   record->ttl);
+               if (query == NULL) {
+                       result = ISC_R_FAILURE;
+                       goto cleanup;
+               }
+               result = db_execute(state, txn->dbi, query);
+               free(query);
+       } else {
+               /*
+                * This is an SOA record, so we update: it must exist,
+                * or we wouldn't have gotten this far.
+                * SOA: zone admin serial refresh retry expire min
+                */
+               char sn[32];
+               sscanf(record->data, "%*s %*s %s %*s %*s %*s %*s", sn);
+               query = build_query(state, txn->dbi, U_SERIAL, sn,
+                                   txn->zone_id);
+               if (query == NULL) {
+                       result = ISC_R_FAILURE;
+                       goto cleanup;
+               }
+               result = db_execute(state, txn->dbi, query);
+               free(query);
+       }
+
+ cleanup:
+       free(record);
+       return (result);
+}
+
+isc_result_t
+dlz_subrdataset(const char *name, const char *rdatastr,
+               void *dbdata, void *version)
+{
+       mysql_data_t *state = (mysql_data_t *)dbdata;
+       mysql_transaction_t *txn = (mysql_transaction_t *)version;
+       char *new_name, *query;
+       mysql_record_t *record;
+       isc_result_t result;
+       size_t len;
+
+       if (txn == NULL)
+               return (ISC_R_FAILURE);
+
+       new_name = relname(name, txn->zone);
+       if (new_name == NULL)
+               return (ISC_R_NOMEMORY);
+
+       if (state->debug && (state->log != NULL))
+               state->log(ISC_LOG_INFO, "%s: sub (%x) %s %s",
+                          modname, version, name, rdatastr);
+
+       record = makerecord(state, new_name, rdatastr);
+       free(new_name);
+       if (record == NULL)
+               return (ISC_R_FAILURE);
+       /*
+        * If 'type' isn't 'SOA', delete the records
+        */
+       if (strcasecmp(record->type, "SOA") == 0)
+               result = ISC_R_SUCCESS;
+       else {
+               query = build_query(state, txn->dbi, D_RECORD,
+                                   txn->zone_id, record->name,
+                                   record->type, record->data,
+                                   record->ttl);
+               if (query == NULL) {
+                       result = ISC_R_NOMEMORY;
+                       goto cleanup;
+               }
+
+               result = db_execute(state, txn->dbi, query);
+               free(query);
+       }
+
+ cleanup:
+       free(record);
+       return (result);
+}
+
+isc_result_t
+dlz_delrdataset(const char *name, const char *type,
+               void *dbdata, void *version)
+{
+       mysql_data_t *state = (mysql_data_t *)dbdata;
+       mysql_transaction_t *txn = (mysql_transaction_t *)version;
+       char *new_name, *query;
+       isc_result_t result;
+       size_t len;
+
+       if (txn == NULL)
+               return (ISC_R_FAILURE);
+
+       new_name = relname(name, txn->zone);
+       if (new_name == NULL)
+               return (ISC_R_NOMEMORY);
+
+       if (state->debug && (state->log != NULL))
+               state->log(ISC_LOG_INFO, "%s: del (%x) %s %s",
+                          modname, version, name, type);
+
+       query = build_query(state, txn->dbi, D_RRSET,
+                           txn->zone_id, new_name, type);
+       if (query == NULL) {
+               result = ISC_R_NOMEMORY;
+               goto cleanup;
+       }
+
+       result = db_execute(state, txn->dbi, query);
+       free(query);
+
+ cleanup:
+       free(new_name);
+       return (result);
+}
diff --git a/contrib/dlz/modules/mysqldyn/testing/README b/contrib/dlz/modules/mysqldyn/testing/README
new file mode 100644 (file)
index 0000000..862ec6f
--- /dev/null
@@ -0,0 +1,11 @@
+These files were used for testing on Ubuntu Linux using MySQL
+
+To set up a test server:
+- Install MySQL: sudo apt-get install mysql-server
+- Run "mysql --user=USER --password=PASSWORD < dlz.schema" to set up database
+- Run "mysql --user=USER --password=PASSWORD < dlz.data" to populate it
+- Update named.conf with correct USER and PASSWORD
+- Generate a TSIG key: "ddns-confgen -qz example.com"
+
+To query the database, use "dig -p 5300 @localhost"
+To send dynamic updates, use "nsupdate -p 5300 -k ddns.key"
diff --git a/contrib/dlz/modules/mysqldyn/testing/dlz.data b/contrib/dlz/modules/mysqldyn/testing/dlz.data
new file mode 100644 (file)
index 0000000..068ad7a
--- /dev/null
@@ -0,0 +1,18 @@
+use BindDB;
+insert into `Zones`
+    ( `id`, `domain`, `host`, `admin`, `serial`, `expire`,
+    `refresh`, `retry`, `minimum`, `ttl`, `writeable`) VALUES
+    (1, 'example.com', '@', 'info', 2014040100, 10800,
+    7200, 604800, 86400, 86400, 1);
+
+insert into `ZoneData`
+    (`id`, `zone_id`, `name`, `type`, `data`) VALUES
+    ('', 1, '@', 'NS', 'ns1.example.com.'),
+    ('', 1, '@', 'NS', 'ns2.example.com.'),
+    ('', 1, '@', 'MX', '10 mail.example.com.'),
+    ('', 1, '@', 'A', '192.168.0.2'),
+    ('', 1, '@', 'TXT', '"v=spf1 ip:192.168.0.3 ~all"'),
+    ('', 1, 'www', 'CNAME', 'example.com.'),
+    ('', 1, 'mail', 'A', '192.168.0.3'),
+    ('', 1, 'ns1', 'A', '192.168.1.111'),
+    ('', 1, 'ns2', 'A', '192.168.1.222');
diff --git a/contrib/dlz/modules/mysqldyn/testing/dlz.schema b/contrib/dlz/modules/mysqldyn/testing/dlz.schema
new file mode 100644 (file)
index 0000000..a28f912
--- /dev/null
@@ -0,0 +1,31 @@
+CREATE DATABASE `BindDB` DEFAULT CHARACTER SET latin1;
+USE `BindDB`;
+
+CREATE TABLE `ZoneData` (
+    `id` int(11) NOT NULL AUTO_INCREMENT,
+    `zone_id` int(11) NOT NULL,
+    `name` varchar(128) NOT NULL DEFAULT '',
+    `type` varchar(16) NOT NULL DEFAULT '',
+    `data` varchar(128) NOT NULL DEFAULT '',
+    `ttl` int(11) NOT NULL DEFAULT '86400',
+    PRIMARY KEY (`id`),
+    KEY `zone_idx` (`zone_id`),
+    KEY `name_idx` (`zone_id`, `name`),
+    KEY `type_idx` (`type`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+CREATE TABLE `Zones` (
+    `id` int(11) NOT NULL AUTO_INCREMENT,
+    `domain` varchar(128) NOT NULL DEFAULT '',
+    `host` varchar(128) NOT NULL DEFAULT '',
+    `admin` varchar(128) NOT NULL DEFAULT '',
+    `serial` int(11) NOT NULL DEFAULT '1',
+    `expire` int(11) NOT NULL DEFAULT '86400',
+    `refresh` int(11) NOT NULL DEFAULT '86400',
+    `retry` int(11) NOT NULL DEFAULT '86400',
+    `minimum` int(11) NOT NULL DEFAULT '86400',
+    `ttl` int(11) NOT NULL DEFAULT '86400',
+    `writeable` tinyint(1) NOT NULL DEFAULT '0',
+    PRIMARY KEY (`id`),
+    KEY `domain_idx` (`domain`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
diff --git a/contrib/dlz/modules/mysqldyn/testing/named.conf b/contrib/dlz/modules/mysqldyn/testing/named.conf
new file mode 100644 (file)
index 0000000..3b3a0dc
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+controls { };
+
+options {
+       directory ".";
+       port 5300;
+       pid-file "named.pid";
+       session-keyfile "session.key";
+       listen-on { any; };
+       listen-on-v6 { none; };
+       recursion no;
+};
+
+include "ddns.key";
+
+key rndc_key {
+       secret "1234abcd8765";
+       algorithm hmac-md5;
+};
+
+controls {
+       inet 127.0.0.1 port 9953 allow { any; } keys { rndc_key; };
+};
+
+dlz "test" {
+       database "dlopen ../dlz_mysqldyn_mod.so BindDB localhost root password";
+};
index fd5a5e2816429e9043e80fa77d223552829c5fac..a5a9583c4675337c2d4ae0438d50605c96388567 100644 (file)
@@ -14,8 +14,6 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* $Id: clientinfo.c,v 1.3 2011/10/11 00:25:12 marka Exp $ */
-
 /*! \file */
 
 #include "config.h"
@@ -32,7 +30,8 @@ dns_clientinfomethods_init(dns_clientinfomethods_t *methods,
 }
 
 void
-dns_clientinfo_init(dns_clientinfo_t *ci, void *data) {
+dns_clientinfo_init(dns_clientinfo_t *ci, void *data, void *versionp) {
        ci->version = DNS_CLIENTINFO_VERSION;
        ci->data = data;
+       ci->dbversion = versionp;
 }
index 4f2b89cda43e4c4724a186e780ab3f3afe10399e..aa2d0ad68f608bbdd38ce42485fc3d2f2f3adb73 100644 (file)
@@ -52,10 +52,11 @@ ISC_LANG_BEGINDECLS
  ***** Types
  *****/
 
-#define DNS_CLIENTINFO_VERSION 1
+#define DNS_CLIENTINFO_VERSION 2
 typedef struct dns_clientinfo {
        isc_uint16_t version;
        void *data;
+       void *dbversion;
 } dns_clientinfo_t;
 
 typedef isc_result_t (*dns_clientinfo_sourceip_t)(dns_clientinfo_t *client,
@@ -78,7 +79,7 @@ dns_clientinfomethods_init(dns_clientinfomethods_t *methods,
                           dns_clientinfo_sourceip_t sourceip);
 
 void
-dns_clientinfo_init(dns_clientinfo_t *ci, void *data);
+dns_clientinfo_init(dns_clientinfo_t *ci, void *data, void *versionp);
 
 ISC_LANG_ENDDECLS
 
index dcf39f7a0eb7628de8635c67e975c796e29668dd..5d6d5342a52630b50b6e5b453cd00c8d5f199e2d 100644 (file)
@@ -426,8 +426,7 @@ newversion(dns_db_t *db, dns_dbversion_t **versionp) {
 }
 
 static void
-attachversion(dns_db_t *db, dns_dbversion_t *source,
-             dns_dbversion_t **targetp)
+attachversion(dns_db_t *db, dns_dbversion_t *source, dns_dbversion_t **targetp)
 {
        dns_sdlz_db_t *sdlz = (dns_sdlz_db_t *)db;
 
@@ -854,7 +853,9 @@ findext(dns_db_t *db, dns_name_t *name, dns_dbversion_t *version,
 
        REQUIRE(VALID_SDLZDB(sdlz));
        REQUIRE(nodep == NULL || *nodep == NULL);
-       REQUIRE(version == NULL || version == (void*)&sdlz->dummy_version);
+       REQUIRE(version == NULL ||
+               version == (void*)&sdlz->dummy_version ||
+               version == sdlz->future_version);
 
        UNUSED(options);
        UNUSED(sdlz);