]> granicus.if.org Git - pdns/commitdiff
cherry pick squash of e09ff8b07703...ae03af280d89 from master - various remotebackend...
authorPeter van Dijk <peter.van.dijk@netherlabs.nl>
Fri, 7 Jun 2013 11:53:42 +0000 (13:53 +0200)
committerPeter van Dijk <peter.van.dijk@netherlabs.nl>
Fri, 7 Jun 2013 11:53:42 +0000 (13:53 +0200)
23 files changed:
configure.ac
modules/remotebackend/.gitignore [new file with mode: 0644]
modules/remotebackend/Makefile.am
modules/remotebackend/httpconnector.cc
modules/remotebackend/regression-tests/backend.rb
modules/remotebackend/regression-tests/dnsbackend.rb
modules/remotebackend/regression-tests/http-backend.rb
modules/remotebackend/regression-tests/pipe-backend.rb
modules/remotebackend/remotebackend.cc
modules/remotebackend/remotebackend.hh
modules/remotebackend/test-remotebackend-http.cc [new file with mode: 0644]
modules/remotebackend/test-remotebackend-json.cc [new file with mode: 0644]
modules/remotebackend/test-remotebackend-keys.hh [new file with mode: 0644]
modules/remotebackend/test-remotebackend-pipe.cc [new file with mode: 0644]
modules/remotebackend/test-remotebackend-post.cc [new file with mode: 0644]
modules/remotebackend/test-remotebackend.cc [new file with mode: 0644]
modules/remotebackend/testrunner.sh [new file with mode: 0644]
modules/remotebackend/unittest.rb [new file with mode: 0644]
modules/remotebackend/unittest_http.rb [new file with mode: 0644]
modules/remotebackend/unittest_json.rb [new file with mode: 0644]
modules/remotebackend/unittest_pipe.rb [new file with mode: 0644]
modules/remotebackend/unittest_post.rb [new file with mode: 0644]
pdns/docs/pdns.xml

index 04e0d70d98ec86925b4a9683e5d75257804eb45a..d8c37ccf15099ff534ef412c88e5aec713657007 100644 (file)
@@ -255,10 +255,12 @@ AM_CONDITIONAL(REMOTEBACKEND_HTTP,test x"$enable_remotebackend_http" = "xyes")
 if test "x$enable_remotebackend_http" = "xyes"
 then
         PKG_CHECK_MODULES(LIBCURL, libcurl, HAVE_LIBCURL=yes, AC_MSG_ERROR([Could not find libcurl]))
+        REMOTEBACKEND_HTTP=yes
         AC_SUBST(LIBCURL_LIBS)
         AC_SUBST(LIBCURL_CFLAGS)
         AC_DEFINE(HAVE_LIBCURL,1,[If we have libcurl])
        AC_DEFINE(REMOTEBACKEND_HTTP,1,[If we want HTTP connector])
+        AC_SUBST(REMOTEBACKEND_HTTP)
 fi
 
 AC_MSG_CHECKING(whether we should build static binaries)
diff --git a/modules/remotebackend/.gitignore b/modules/remotebackend/.gitignore
new file mode 100644 (file)
index 0000000..c14f877
--- /dev/null
@@ -0,0 +1,5 @@
+remotebackend-access.log
+test_remotebackend_http
+test_remotebackend_pipe
+test_remotebackend_json
+test_remotebackend_post
index a47f8bbae6ff8b846e24813c1d633ba84eace8f4..c4bf57930f7548dbc6238195f1534f65a694800f 100644 (file)
@@ -5,9 +5,55 @@ AM_CPPFLAGS=@THREADFLAGS@ $(BOOST_CPPFLAGS) $(LIBCURL_CFLAGS) -I../../pdns/ext/r
 #endif
 
 EXTRA_DIST=OBJECTFILES OBJECTLIBS
-lib_LTLIBRARIES = libremotebackend.la
+EXTRA_PROGRAMS=test_remotebackend_pipe test_remotebackend_http test_remotebackend_post test_remotebackend_json
+EXTRA_LTLIBRARIES=libtestremotebackend.la
+
+lib_LTLIBRARIES = libremotebackend.la 
 
 libremotebackend_la_SOURCES=remotebackend.hh remotebackend.cc unixconnector.cc httpconnector.cc pipeconnector.cc 
 
 libremotebackend_la_LDFLAGS=-module -avoid-version
 libremotebackend_la_LIBS=$(LIBCURL_LIBS)
+
+TESTS_ENVIRONMENT = env BOOST_TEST_LOG_LEVEL=message REMOTEBACKEND_HTTP=$(REMOTEBACKEND_HTTP) ./testrunner.sh 
+TESTS=test_remotebackend_pipe test_remotebackend_http test_remotebackend_post test_remotebackend_json
+
+BUILT_SOURCES=../../pdns/dnslabeltext.cc
+
+../../pdns/dnslabeltext.cc: ../../pdns/dnslabeltext.rl
+       make -C ../../pdns dnslabeltext.cc
+
+libtestremotebackend_la_SOURCES=../../pdns/dnsbackend.hh ../../pdns/dnsbackend.cc ../../pdns/ueberbackend.hh ../../pdns/ueberbackend.cc \
+        ../../pdns/nameserver.cc ../../pdns/misc.cc ../../pdns/arguments.hh \
+        ../../pdns/unix_utility.cc ../../pdns/logger.cc ../../pdns/statbag.cc ../../pdns/arguments.hh ../../pdns/arguments.cc ../../pdns/qtype.cc ../../pdns/dnspacket.cc \
+        ../../pdns/dnswriter.cc ../../pdns/base64.cc ../../pdns/base32.cc ../../pdns/dnsrecords.cc ../../pdns/dnslabeltext.cc ../../pdns/dnsparser.cc \
+        ../../pdns/rcpgenerator.cc ../../pdns/ednssubnet.cc ../../pdns/nsecrecords.cc ../../pdns/sillyrecords.cc ../../pdns/dnssecinfra.cc \
+        ../../pdns/aes/dns_random.cc ../../pdns/packetcache.hh ../../pdns/packetcache.cc \
+        ../../pdns/aes/aescpp.h ../../pdns/dns.hh ../../pdns/dns.cc ../../pdns/json.hh ../../pdns/json.cc \
+        ../../pdns/aes/aescrypt.c ../../pdns/aes/aes.h ../../pdns/aes/aeskey.c ../../pdns/aes/aes_modes.c ../../pdns/aes/aesopt.h \
+        ../../pdns/aes/aestab.c ../../pdns/aes/aestab.h ../../pdns/aes/brg_endian.h ../../pdns/aes/brg_types.h ../pipebackend/coprocess.cc \
+        remotebackend.hh remotebackend.cc unixconnector.cc httpconnector.cc pipeconnector.cc
+
+libtestremotebackend_la_CFLAGS=$(BOOST_CPPFLAGS) @THREADFLAGS@ $(LIBCURL_CFLAGS) -g -O0 -I../../pdns
+libtestremotebackend_la_CXXFLAGS=$(BOOST_CPPFLAGS) @THREADFLAGS@ $(LIBCURL_CFLAGS) -g -O0 -I../../pdns
+
+test_remotebackend_pipe_SOURCES=test-remotebackend.cc test-remotebackend-pipe.cc 
+test_remotebackend_http_SOURCES=test-remotebackend.cc test-remotebackend-http.cc ../../config.h
+test_remotebackend_post_SOURCES=test-remotebackend.cc test-remotebackend-post.cc ../../config.h
+test_remotebackend_json_SOURCES=test-remotebackend.cc test-remotebackend-json.cc ../../config.h
+
+test_remotebackend_pipe_CFLAGS=$(BOOST_CPPFLAGS) @THREADFLAGS@ $(LIBCURL_CFLAGS) -g -O0 -I../../pdns
+test_remotebackend_pipe_CXXFLAGS=$(BOOST_CPPFLAGS) @THREADFLAGS@ $(LIBCURL_CFLAGS) -g -O0 -I../../pdns
+test_remotebackend_pipe_LDADD=libtestremotebackend.la @DYNLINKFLAGS@ @THREADFLAGS@ $(BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS) ../../pdns/ext/polarssl-1.1.2/library/libpolarssl.a $(BOOST_UNIT_TEST_FRAMEWORK_LIBS) $(BOOST_SERIALIZATION_LIBS) $(BOOST_PROGRAM_OPTIONS_LIBS) @LIBDL@ $(LIBCURL_LIBS)
+
+test_remotebackend_http_CFLAGS=$(BOOST_CPPFLAGS) @THREADFLAGS@ $(LIBCURL_CFLAGS) -g -O0 -I../../pdns
+test_remotebackend_http_CXXFLAGS=$(BOOST_CPPFLAGS) @THREADFLAGS@ $(LIBCURL_CFLAGS) -g -O0 -I../../pdns
+test_remotebackend_http_LDADD=libtestremotebackend.la @DYNLINKFLAGS@ @THREADFLAGS@ $(BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS) ../../pdns/ext/polarssl-1.1.2/library/libpolarssl.a $(BOOST_UNIT_TEST_FRAMEWORK_LIBS) $(BOOST_SERIALIZATION_LIBS) $(BOOST_PROGRAM_OPTIONS_LIBS) @LIBDL@ $(LIBCURL_LIBS)
+
+test_remotebackend_post_CFLAGS=$(BOOST_CPPFLAGS) @THREADFLAGS@ $(LIBCURL_CFLAGS) -g -O0 -I../../pdns
+test_remotebackend_post_CXXFLAGS=$(BOOST_CPPFLAGS) @THREADFLAGS@ $(LIBCURL_CFLAGS) -g -O0 -I../../pdns
+test_remotebackend_post_LDADD=libtestremotebackend.la @DYNLINKFLAGS@ @THREADFLAGS@ $(BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS) ../../pdns/ext/polarssl-1.1.2/library/libpolarssl.a $(BOOST_UNIT_TEST_FRAMEWORK_LIBS) $(BOOST_SERIALIZATION_LIBS) $(BOOST_PROGRAM_OPTIONS_LIBS) @LIBDL@ $(LIBCURL_LIBS)
+
+test_remotebackend_json_CFLAGS=$(BOOST_CPPFLAGS) @THREADFLAGS@ $(LIBCURL_CFLAGS) -g -O0 -I../../pdns
+test_remotebackend_json_CXXFLAGS=$(BOOST_CPPFLAGS) @THREADFLAGS@ $(LIBCURL_CFLAGS) -g -O0 -I../../pdns
+test_remotebackend_json_LDADD=libtestremotebackend.la @DYNLINKFLAGS@ @THREADFLAGS@ $(BOOST_UNIT_TEST_FRAMEWORK_LDFLAGS) ../../pdns/ext/polarssl-1.1.2/library/libpolarssl.a $(BOOST_UNIT_TEST_FRAMEWORK_LIBS) $(BOOST_SERIALIZATION_LIBS) $(BOOST_PROGRAM_OPTIONS_LIBS) @LIBDL@ $(LIBCURL_LIBS)
index af49cd4c67baf42c8a30131f591c2eb4d39ec644..fe6a4cda47c3c1acd68ec097a62f8c203bfde634 100644 (file)
@@ -4,6 +4,8 @@
 #include <fcntl.h>
 #include <boost/foreach.hpp>
 #include <sstream>
+#include "rapidjson/stringbuffer.h"
+#include "rapidjson/writer.h"
 
 #ifdef REMOTEBACKEND_HTTP
 #include <curl/curl.h>
@@ -22,9 +24,26 @@ HTTPConnector::HTTPConnector(std::map<std::string,std::string> options) {
       this->d_url_suffix = "";
     }
     this->timeout = 2;
+    this->d_post = false;
+    this->d_post_json = false;
+
     if (options.find("timeout") != options.end()) { 
       this->timeout = boost::lexical_cast<int>(options.find("timeout")->second)/1000;
     }
+    if (options.find("post") != options.end()) {
+      std::string val = options.find("post")->second;
+      if (val == "yes" || val == "true" || val == "on" || val == "1") {
+        this->d_post = true;
+      }
+    }
+    if (options.find("post_json") != options.end()) {
+      std::string val = options.find("post_json")->second;
+      if (val == "yes" || val == "true" || val == "on" || val == "1") {
+        this->d_post_json = true;
+      }
+    }
+    if (options.find("capath") != options.end()) this->d_capath = options.find("capath")->second;
+    if (options.find("cafile") != options.end()) this->d_cafile = options.find("cafile")->second;
 }
 
 HTTPConnector::~HTTPConnector() {
@@ -40,12 +59,15 @@ size_t httpconnector_write_data(void *buffer, size_t size, size_t nmemb, void *u
 }
 
 // converts json value into string
-void HTTPConnector::json2string(const rapidjson::Value &input, std::string &output) {
+bool HTTPConnector::json2string(const rapidjson::Value &input, std::string &output) {
    if (input.IsString()) output = input.GetString();
    else if (input.IsNull()) output = "";
+   else if (input.IsUint64()) output = lexical_cast<std::string>(input.GetUint64());
+   else if (input.IsInt64()) output = lexical_cast<std::string>(input.GetInt64());
    else if (input.IsUint()) output = lexical_cast<std::string>(input.GetUint());
    else if (input.IsInt()) output = lexical_cast<std::string>(input.GetInt());
-   else output = "inconvertible value";
+   else return false;
+   return true;
 }
 
 void HTTPConnector::addUrlComponent(const rapidjson::Value &parameters, const char *element, std::stringstream& ss) {
@@ -59,9 +81,36 @@ void HTTPConnector::addUrlComponent(const rapidjson::Value &parameters, const ch
     }
 }
 
+template <class T> std::string buildMemberListArgs(std::string prefix, const T* value, CURL* curlContext) {
+    std::stringstream stream;
+
+    for (rapidjson::Value::ConstMemberIterator itr = value->MemberBegin(); itr != value->MemberEnd(); itr++) {
+        stream << prefix << "[" << itr->name.GetString() << "]=";
 
-// builds our request
-void HTTPConnector::requestbuilder(const std::string &method, const rapidjson::Value &parameters, struct curl_slist **slist)
+        if (itr->value.IsUint64()) {
+            stream << itr->value.GetUint64();
+        } else if (itr->value.IsInt64()) {
+            stream << itr->value.GetInt64();
+        } else if (itr->value.IsUint()) {
+            stream << itr->value.GetUint();
+        } else if (itr->value.IsInt()) {
+            stream << itr->value.GetInt();
+        } else if (itr->value.IsBool()) {
+            stream << (itr->value.GetBool() ? 1 : 0);
+        } else if (itr->value.IsString()) {
+            char *tmpstr = curl_easy_escape(curlContext, itr->value.GetString(), 0);
+            stream << tmpstr;
+            curl_free(tmpstr);
+        }
+
+        stream << "&";
+    }
+
+    return stream.str();
+}
+
+// builds our request (near-restful)
+void HTTPConnector::restful_requestbuilder(const std::string &method, const rapidjson::Value &parameters, struct curl_slist **slist)
 {
     std::stringstream ss;
     std::string sparam;
@@ -77,16 +126,13 @@ void HTTPConnector::requestbuilder(const std::string &method, const rapidjson::V
     // id must be first due to the fact that the qname/name can be empty
 
     addUrlComponent(parameters, "id", ss);
+    addUrlComponent(parameters, "domain_id", ss);
     addUrlComponent(parameters, "zonename", ss);
     addUrlComponent(parameters, "qname", ss);
     addUrlComponent(parameters, "name", ss);
     addUrlComponent(parameters, "kind", ss);
     addUrlComponent(parameters, "qtype", ss);
 
-    // finally add suffix
-    ss << d_url_suffix;
-    curl_easy_setopt(d_c, CURLOPT_URL, ss.str().c_str());
-    
     (*slist) = NULL;
     // set the correct type of request based on method
     if (method == "activateDomainKey" || method == "deactivateDomainKey") { 
@@ -106,8 +152,86 @@ void HTTPConnector::requestbuilder(const std::string &method, const rapidjson::V
         curl_easy_setopt(d_c, CURLOPT_COPYPOSTFIELDS, postfields);
         curl_free(tmpstr);
         delete postfields;
+    } else if (method == "superMasterBackend") {
+        std::stringstream ss2;
+        addUrlComponent(parameters, "ip", ss);
+        addUrlComponent(parameters, "domain", ss);
+        // then we need to serialize rrset payload into POST
+        size_t index = 0;
+        for(rapidjson::Value::ConstValueIterator itr = parameters["nsset"].Begin(); itr != parameters["nsset"].End(); itr++) {
+            index++;
+            ss2 << buildMemberListArgs("nsset[" + boost::lexical_cast<std::string>(index) + "]", itr, d_c);
+        }
+        // then give it to curl
+        std::string out = ss2.str();
+        curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, out.size());
+        curl_easy_setopt(d_c, CURLOPT_COPYPOSTFIELDS, out.c_str());
+    } else if (method == "createSlaveDomain") {
+        addUrlComponent(parameters, "ip", ss);
+        addUrlComponent(parameters, "domain", ss);
+        if (parameters.HasMember("account")) {
+           std::string out = parameters["account"].GetString();
+           curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, out.size());
+           curl_easy_setopt(d_c, CURLOPT_COPYPOSTFIELDS, out.c_str()); 
+        } else {
+           curl_easy_setopt(d_c, CURLOPT_POST, 1);
+           curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, 0); 
+        }
+    } else if (method == "replaceRRSet") {
+        std::stringstream ss2;
+        size_t index = 0;
+        for(rapidjson::Value::ConstValueIterator itr = parameters["rrset"].Begin(); itr != parameters["rrset"].End(); itr++) {
+            index++;
+            ss2 << buildMemberListArgs("rrset[" + boost::lexical_cast<std::string>(index) + "]", itr, d_c);
+        }
+        // then give it to curl
+        std::string out = ss2.str();
+        curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, out.size());
+        curl_easy_setopt(d_c, CURLOPT_COPYPOSTFIELDS, out.c_str());
+    } else if (method == "feedRecord") {
+        std::string out = buildMemberListArgs("rr", &parameters["rr"], d_c);
+        addUrlComponent(parameters, "trxid", ss);
+        curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, out.size());
+        curl_easy_setopt(d_c, CURLOPT_COPYPOSTFIELDS, out.c_str());
+    } else if (method == "feedEnts") {
+        std::stringstream ss2;
+        addUrlComponent(parameters, "trxid", ss);
+        for(rapidjson::Value::ConstValueIterator itr = parameters["nonterm"].Begin(); itr != parameters["nonterm"].End(); itr++) {
+          tmpstr = curl_easy_escape(d_c, itr->GetString(), 0);
+          ss2 << "nonterm[]=" << tmpstr << "&";
+          curl_free(tmpstr);
+        }
+        std::string out = ss2.str();
+        curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, out.size());
+        curl_easy_setopt(d_c, CURLOPT_COPYPOSTFIELDS, out.c_str());
+    } else if (method == "feedEnts3") {
+        std::stringstream ss2;
+        addUrlComponent(parameters, "domain", ss);
+        addUrlComponent(parameters, "trxid", ss);
+        ss2 << "times=" << parameters["times"].GetInt() << "&salt=" << parameters["salt"].GetString() << "&narrow=" << (parameters["narrow"].GetBool() ? 1 : 0) << "&";
+        for(rapidjson::Value::ConstValueIterator itr = parameters["nonterm"].Begin(); itr != parameters["nonterm"].End(); itr++) {
+          tmpstr = curl_easy_escape(d_c, itr->GetString(), 0);
+          ss2 << "nonterm[]=" << tmpstr << "&";
+          curl_free(tmpstr);
+        }
+        std::string out = ss2.str();
+        curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, out.size());
+        curl_easy_setopt(d_c, CURLOPT_COPYPOSTFIELDS, out.c_str());
+    } else if (method == "startTransaction") {
+        addUrlComponent(parameters, "domain", ss);
+        addUrlComponent(parameters, "trxid", ss);
+        curl_easy_setopt(d_c, CURLOPT_POST, 1);
+        curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, 0);
+    } else if (method == "commitTransaction" || method == "abortTransaction") {
+        addUrlComponent(parameters, "trxid", ss);
+        curl_easy_setopt(d_c, CURLOPT_POST, 1);
+        curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, 0);
+    } else if (method == "calculateSOASerial") {
+        addUrlComponent(parameters, "domain", ss);
+        std::string out = buildMemberListArgs("sd", &parameters["sd"], d_c);
+        curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, out.size());
+        curl_easy_setopt(d_c, CURLOPT_COPYPOSTFIELDS, out.c_str());
     } else if (method == "setDomainMetadata") {
-        int n=0;
         // copy all metadata values into post
         std::stringstream ss2;
         const rapidjson::Value& param = parameters["value"];
@@ -115,7 +239,7 @@ void HTTPConnector::requestbuilder(const std::string &method, const rapidjson::V
         // this one has values too
         if (param.IsArray()) {
            for(rapidjson::Value::ConstValueIterator i = param.Begin(); i != param.End(); i++) {
-              ss2 << "value" << (++n) << "=" << i->GetString() << "&";
+              ss2 << "value[]=" << i->GetString() << "&";
            }
         }
         sparam = ss2.str();
@@ -124,6 +248,12 @@ void HTTPConnector::requestbuilder(const std::string &method, const rapidjson::V
     } else if (method == "removeDomainKey") {
         // this one is delete
         curl_easy_setopt(d_c, CURLOPT_CUSTOMREQUEST, "DELETE");
+    } else if (method == "setNotified") {
+        tmpstr = (char*)malloc(128);
+        snprintf(tmpstr, 128, "serial=%u", parameters["serial"].GetInt());
+        curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, strlen(tmpstr));
+        curl_easy_setopt(d_c, CURLOPT_COPYPOSTFIELDS, tmpstr);
+        free(tmpstr);
     } else {
         // perform normal get
         curl_easy_setopt(d_c, CURLOPT_HTTPGET, 1);
@@ -138,15 +268,49 @@ void HTTPConnector::requestbuilder(const std::string &method, const rapidjson::V
           !strncmp(member,"name",4) || !strncmp(member,"kind",4) ||
           !strncmp(member,"qtype",5) || !strncmp(member,"id",2) ||
           !strncmp(member,"key",3)) continue;
-      json2string(parameters[member], sparam);
-      snprintf(header, sizeof header, "X-RemoteBackend-%s: %s", iter->name.GetString(), sparam.c_str());
-      (*slist) = curl_slist_append((*slist), header);
+      if (json2string(parameters[member], sparam)) {
+         snprintf(header, sizeof header, "X-RemoteBackend-%s: %s", iter->name.GetString(), sparam.c_str());
+         (*slist) = curl_slist_append((*slist), header);
+      }
     };
 
+    // finally add suffix and store url
+    ss << d_url_suffix;
+    curl_easy_setopt(d_c, CURLOPT_URL, ss.str().c_str());
+
     // store headers into request
     curl_easy_setopt(d_c, CURLOPT_HTTPHEADER, *slist); 
 }
 
+void HTTPConnector::post_requestbuilder(const rapidjson::Document &input, struct curl_slist **slist) {
+    if (this->d_post_json) {
+        // simple case, POST JSON into url. nothing fancy. 
+        std::string out = makeStringFromDocument(input);
+        (*slist) = curl_slist_append((*slist), "Content-Type: text/javascript; charset=utf-8");
+        curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, out.size());
+        curl_easy_setopt(d_c, CURLOPT_COPYPOSTFIELDS, out.c_str());
+        curl_easy_setopt(d_c, CURLOPT_URL, d_url.c_str());
+        curl_easy_setopt(d_c, CURLOPT_HTTPHEADER, *slist);
+    } else {
+        std::stringstream url,content;
+        char *tmpstr;
+        // call url/method.suffix
+        rapidjson::StringBuffer output;
+        rapidjson::Writer<rapidjson::StringBuffer> w(output);
+        input["parameters"].Accept(w);
+        url << d_url << "/" << input["method"].GetString() << d_url_suffix;
+        // then build content
+        tmpstr = curl_easy_escape(d_c, output.GetString(), 0);
+        content << "parameters=" << tmpstr;
+        // convert into parameters=urlencoded
+        curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, content.str().size());
+        curl_easy_setopt(d_c, CURLOPT_COPYPOSTFIELDS, content.str().c_str());
+        free(tmpstr);
+        curl_easy_setopt(d_c, CURLOPT_URL, d_url.c_str());
+        curl_easy_setopt(d_c, CURLOPT_URL, url.str().c_str());
+    }
+}
+
 int HTTPConnector::send_message(const rapidjson::Document &input) {
     int rv;
     long rcode;
@@ -160,11 +324,26 @@ int HTTPConnector::send_message(const rapidjson::Document &input) {
     d_data = "";
     curl_easy_setopt(d_c, CURLOPT_NOSIGNAL, 1);
     curl_easy_setopt(d_c, CURLOPT_TIMEOUT, this->timeout);
+    // turn off peer verification or set verification roots
+    if (d_capath.empty()) {
+      if (d_cafile.empty()) {
+         curl_easy_setopt(d_c, CURLOPT_SSL_VERIFYPEER, 0);
+      } else {
+         curl_easy_setopt(d_c, CURLOPT_CAINFO, d_cafile.c_str());
+      }
+    } else {
+       curl_easy_setopt(d_c, CURLOPT_CAPATH, d_capath.c_str());
+    }
 
     slist = NULL;
 
-    // build request
-    requestbuilder(input["method"].GetString(), input["parameters"], &slist);
+    // build request based on mode
+    
+    if (d_post) 
+      post_requestbuilder(input, &slist);
+    else
+      restful_requestbuilder(input["method"].GetString(), input["parameters"], &slist);
 
     // setup write function helper
     curl_easy_setopt(d_c, CURLOPT_WRITEFUNCTION, &(httpconnector_write_data));
index 444e402182ed07a56410a3a837bf5047a215fce2..8f859418094fb44ad43cfa0dd3860058ccd89adf 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/ruby
+#!/usr/bin/ruby1.9.1
 
 require 'json'
 require 'sqlite3'
index dbf9e95835459006c1de3ab1c88b1720a091891e..aaac5a5b7f98859b07f0846c69a0f9f94803eb15 100644 (file)
@@ -8,6 +8,28 @@ class DNSBackendHandler < WEBrick::HTTPServlet::AbstractServlet
      @f = File.open("/tmp/tmp.txt","a")
    end
 
+   def parse_arrays(params)
+     newparams = {}
+     params.each do |key,val|
+         if key=~/^(.*)\[(.*)\]\[(.*)\]/
+             newparams[$1] = {} unless newparams.has_key? $1
+             newparams[$1][$2] = {} unless newparams[$1].has_key? $2
+             newparams[$1][$2][$3] = val
+             params.delete key
+         elsif key=~/^(.*)\[(.*)\]/
+           if $2 == ""
+             newparams[$1] = [] unless newparams.has_key? $1
+             newparams[$1] << val
+           else
+             newparams[$1] = {} unless newparams.has_key? $1
+             newparams[$1][$2] = val
+           end
+           params.delete key
+         end
+     end
+     params.merge newparams
+   end
+
    def parse_url(url)
      url = url.split('/')
      method = url.shift.downcase
@@ -75,13 +97,8 @@ class DNSBackendHandler < WEBrick::HTTPServlet::AbstractServlet
         }
      end
 
-     if method == "do_setdomainmetadata"
-        args["value"] = []
-        args.each do |k,a|
-            args["value"] << a if k[/^value/]
-        end
-     end
-
+     args = parse_arrays args
      @f.puts method
      @f.puts args
 
index 6bf6be373b1ea035a76f39fa4dcd330e789636ae..8be678a877df4e3a1ab05a214e9cb57276d7c699 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/ruby
+#!/usr/bin/ruby1.9.1
 require "rubygems"
 #require "bundler/setup"
 require "webrick"
index 271c782128c3bc79822a9523dcc30314cbb5bfe2..27f1213d370f0d5e7e9c856a75df7bac5914ce8c 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/ruby
+#!/usr/bin/ruby1.9.1
 
 require 'json'
 require '../modules/remotebackend/regression-tests/backend'
index 0e9d132dce80dcf85d2a2a06019d5c8b06ea32be..e81a6f3c8553f34f5fb6f775adae95f102e739d8 100644 (file)
@@ -43,12 +43,16 @@ bool Connector::recv(rapidjson::Document &value) {
     return false;
 }
 
+/** 
+ * Standard ctor and dtor
+ */
 RemoteBackend::RemoteBackend(const std::string &suffix)
 {
       setArgPrefix("remote"+suffix);
       build(getArg("connection-string"));
       this->d_dnssec = mustDo("dnssec");
       this->d_index = -1;
+      this->d_trxid = 0;
 }
 
 RemoteBackend::~RemoteBackend() {
@@ -327,7 +331,10 @@ bool RemoteBackend::getDomainKeys(const std::string& name, unsigned int kind, st
       DNSBackend::KeyData key;
       key.id = (*iter)["id"].GetUint();
       key.flags = (*iter)["flags"].GetUint();
-      key.active = (*iter)["active"].GetBool();
+      if ((*iter)["active"].IsBool())
+         key.active = (*iter)["active"].GetBool();
+      else 
+         key.active = ((*iter)["active"].GetInt() != 0 ? true : false ); // case where it's returned as non-boolean
       key.content = (*iter)["content"].GetString();
       keys.push_back(key);
    }
@@ -342,7 +349,7 @@ bool RemoteBackend::removeDomainKey(const string& name, unsigned int id) {
    if (d_dnssec == false) return false;
 
    query.SetObject();
-   JSON_ADD_MEMBER(query, "method", "getDomainKeys", query.GetAllocator());
+   JSON_ADD_MEMBER(query, "method", "removeDomainKey", query.GetAllocator());
    parameters.SetObject();
    JSON_ADD_MEMBER(parameters, "name", name.c_str(), query.GetAllocator());
    JSON_ADD_MEMBER(parameters, "id", id, query.GetAllocator());
@@ -351,7 +358,7 @@ bool RemoteBackend::removeDomainKey(const string& name, unsigned int id) {
    if (connector->send(query) == false || connector->recv(answer) == false)
      return false;
 
-   return answer["result"].GetBool();
+   return true;
 }
 
 int RemoteBackend::addDomainKey(const string& name, const KeyData& key) {
@@ -394,7 +401,7 @@ bool RemoteBackend::activateDomainKey(const string& name, unsigned int id) {
    if (connector->send(query) == false || connector->recv(answer) == false)
      return false;
 
-   return answer["result"].GetBool();
+   return true;
 }
 
 bool RemoteBackend::deactivateDomainKey(const string& name, unsigned int id) {
@@ -414,7 +421,7 @@ bool RemoteBackend::deactivateDomainKey(const string& name, unsigned int id) {
    if (connector->send(query) == false || connector->recv(answer) == false)
      return false;
 
-   return answer["result"].GetBool();
+   return true;
 }
 
 bool RemoteBackend::doesDNSSEC() {
@@ -499,13 +506,272 @@ void RemoteBackend::setNotified(uint32_t id, uint32_t serial) {
    JSON_ADD_MEMBER(query, "method", "setNotified", query.GetAllocator());
    parameters.SetObject();
    JSON_ADD_MEMBER(parameters, "id", id, query.GetAllocator());
-   JSON_ADD_MEMBER(parameters, "serial", id, query.GetAllocator());
-
+   JSON_ADD_MEMBER(parameters, "serial", serial, query.GetAllocator());
+   query.AddMember("parameters", parameters, query.GetAllocator());
    if (connector->send(query) == false || connector->recv(answer) == false) {
       L<<Logger::Error<<kBackendId<<"Failed to execute RPC for RemoteBackend::setNotified("<<id<<","<<serial<<")"<<endl;
    }
 }
 
+bool RemoteBackend::superMasterBackend(const string &ip, const string &domain, const vector<DNSResourceRecord>&nsset, string *account, DNSBackend **ddb) 
+{
+   rapidjson::Document query,answer;
+   rapidjson::Value parameters;
+   rapidjson::Value rrset;
+
+   query.SetObject();
+   JSON_ADD_MEMBER(query, "method", "superMasterBackend", query.GetAllocator());
+   parameters.SetObject();
+   JSON_ADD_MEMBER(parameters, "ip", ip.c_str(), query.GetAllocator());
+   JSON_ADD_MEMBER(parameters, "domain", domain.c_str(), query.GetAllocator());
+   rrset.SetArray();
+   rrset.Reserve(nsset.size(), query.GetAllocator());
+   for(rapidjson::SizeType i = 0; i < nsset.size(); i++) {
+      rapidjson::Value rr;
+      rr.SetObject();
+      JSON_ADD_MEMBER(rr, "qtype", nsset[i].qtype.getName().c_str(), query.GetAllocator());
+      JSON_ADD_MEMBER(rr, "qname", nsset[i].qname.c_str(), query.GetAllocator());
+      JSON_ADD_MEMBER(rr, "qclass", QClass::IN, query.GetAllocator());
+      JSON_ADD_MEMBER(rr, "content", nsset[i].content.c_str(), query.GetAllocator());
+      JSON_ADD_MEMBER(rr, "ttl", nsset[i].ttl, query.GetAllocator());
+      JSON_ADD_MEMBER(rr, "priority", nsset[i].priority, query.GetAllocator());
+      JSON_ADD_MEMBER(rr, "auth", nsset[i].auth, query.GetAllocator());
+      rrset.PushBack(rr, query.GetAllocator());
+   }
+   parameters.AddMember("nsset", rrset, query.GetAllocator());
+   query.AddMember("parameters", parameters, query.GetAllocator());
+
+   *ddb = 0;
+
+   if (connector->send(query) == false || connector->recv(answer) == false)
+     return false;
+
+   // we are the backend
+   *ddb = this;
+   
+   // we allow simple true as well...
+   if (answer["result"].IsObject() && answer["result"].HasMember("account")) 
+     *account = answer["result"]["account"].GetString();
+
+   return true;
+}
+
+bool RemoteBackend::createSlaveDomain(const string &ip, const string &domain, const string &account) {
+   rapidjson::Document query,answer;
+   rapidjson::Value parameters;
+   query.SetObject();
+   JSON_ADD_MEMBER(query, "method", "createSlaveDomain", query.GetAllocator());
+   parameters.SetObject();
+   JSON_ADD_MEMBER(parameters, "ip", ip.c_str(), query.GetAllocator());
+   JSON_ADD_MEMBER(parameters, "domain", domain.c_str(), query.GetAllocator());
+   JSON_ADD_MEMBER(parameters, "account", account.c_str(), query.GetAllocator());
+   query.AddMember("parameters", parameters, query.GetAllocator());
+
+   if (connector->send(query) == false || connector->recv(answer) == false)
+     return false;
+   return true;
+}
+
+bool RemoteBackend::replaceRRSet(uint32_t domain_id, const string& qname, const QType& qtype, const vector<DNSResourceRecord>& rrset) {
+   rapidjson::Document query,answer;
+   rapidjson::Value parameters;
+   rapidjson::Value rj_rrset;
+   query.SetObject();
+   JSON_ADD_MEMBER(query, "method", "replaceRRSet", query.GetAllocator());
+   parameters.SetObject();
+   JSON_ADD_MEMBER(parameters, "domain_id", domain_id, query.GetAllocator());
+   JSON_ADD_MEMBER(parameters, "qname", qname.c_str(), query.GetAllocator());
+   JSON_ADD_MEMBER(parameters, "qtype", qtype.getName().c_str(), query.GetAllocator());
+   JSON_ADD_MEMBER(parameters, "trxid", d_trxid, query.GetAllocator());
+
+   rj_rrset.SetArray();
+   rj_rrset.Reserve(rrset.size(), query.GetAllocator());
+
+   for(rapidjson::SizeType i = 0; i < rrset.size(); i++) {
+      rapidjson::Value rr;
+      rr.SetObject();
+      JSON_ADD_MEMBER(rr, "qtype", rrset[i].qtype.getName().c_str(), query.GetAllocator());
+      JSON_ADD_MEMBER(rr, "qname", rrset[i].qname.c_str(), query.GetAllocator());
+      JSON_ADD_MEMBER(rr, "qclass", QClass::IN, query.GetAllocator());
+      JSON_ADD_MEMBER(rr, "content", rrset[i].content.c_str(), query.GetAllocator());
+      JSON_ADD_MEMBER(rr, "ttl", rrset[i].ttl, query.GetAllocator());
+      JSON_ADD_MEMBER(rr, "priority", rrset[i].priority, query.GetAllocator());
+      JSON_ADD_MEMBER(rr, "auth", rrset[i].auth, query.GetAllocator());
+      rj_rrset.PushBack(rr, query.GetAllocator());
+   }
+   parameters.AddMember("rrset", rj_rrset, query.GetAllocator());
+   query.AddMember("parameters", parameters, query.GetAllocator());
+
+   if (connector->send(query) == false || connector->recv(answer) == false)
+     return false;
+
+   return true;
+}
+
+bool RemoteBackend::feedRecord(const DNSResourceRecord &rr, string *ordername) {
+   rapidjson::Document query,answer;
+   rapidjson::Value parameters,rj_rr;
+   query.SetObject();
+   JSON_ADD_MEMBER(query, "method", "feedRecord", query.GetAllocator());
+   parameters.SetObject();
+   rj_rr.SetObject();
+   JSON_ADD_MEMBER(rj_rr, "qtype", rr.qtype.getName().c_str(), query.GetAllocator());
+   JSON_ADD_MEMBER(rj_rr, "qname", rr.qname.c_str(), query.GetAllocator());
+   JSON_ADD_MEMBER(rj_rr, "qclass", QClass::IN, query.GetAllocator());
+   JSON_ADD_MEMBER(rj_rr, "content", rr.content.c_str(), query.GetAllocator());
+   JSON_ADD_MEMBER(rj_rr, "ttl", rr.ttl, query.GetAllocator());
+   JSON_ADD_MEMBER(rj_rr, "priority", rr.priority, query.GetAllocator());
+   JSON_ADD_MEMBER(rj_rr, "auth", rr.auth, query.GetAllocator());
+   parameters.AddMember("rr", rj_rr, query.GetAllocator());
+
+   JSON_ADD_MEMBER(parameters, "trxid", d_trxid, query.GetAllocator());
+
+   if (ordername) {
+     JSON_ADD_MEMBER(parameters, "ordername", ordername->c_str(), query.GetAllocator());
+   }
+
+   query.AddMember("parameters", parameters, query.GetAllocator());
+
+   if (connector->send(query) == false || connector->recv(answer) == false)
+     return false;
+   return true; // XXX FIXME this API should not return 'true' I think -ahu
+}
+
+bool RemoteBackend::feedEnts(int domain_id, set<string>& nonterm) {
+   rapidjson::Document query,answer;
+   rapidjson::Value parameters;
+   rapidjson::Value nts;
+   query.SetObject();
+   JSON_ADD_MEMBER(query, "method", "feedEnts", query.GetAllocator());
+   parameters.SetObject();
+   JSON_ADD_MEMBER(parameters, "domain_id", domain_id, query.GetAllocator());
+   JSON_ADD_MEMBER(parameters, "trxid", d_trxid, query.GetAllocator());
+   nts.SetArray();
+   BOOST_FOREACH(const string &t, nonterm) {
+      nts.PushBack(t.c_str(), query.GetAllocator());
+   }
+   parameters.AddMember("nonterm", nts, query.GetAllocator());
+   query.AddMember("parameters", parameters, query.GetAllocator());
+
+   if (connector->send(query) == false || connector->recv(answer) == false)
+     return false;
+   return true; 
+}
+
+bool RemoteBackend::feedEnts3(int domain_id, const string &domain, set<string> &nonterm, unsigned int times, const string &salt, bool narrow) {
+   rapidjson::Document query,answer;
+   rapidjson::Value parameters;
+   rapidjson::Value nts;
+   query.SetObject();
+   JSON_ADD_MEMBER(query, "method", "feedEnts3", query.GetAllocator());
+   parameters.SetObject();
+   JSON_ADD_MEMBER(parameters, "domain_id", domain_id, query.GetAllocator());
+   JSON_ADD_MEMBER(parameters, "domain", domain.c_str(), query.GetAllocator());
+   JSON_ADD_MEMBER(parameters, "times", times, query.GetAllocator());
+   JSON_ADD_MEMBER(parameters, "salt", salt.c_str(), query.GetAllocator());
+   JSON_ADD_MEMBER(parameters, "narrow", narrow, query.GetAllocator());
+   JSON_ADD_MEMBER(parameters, "trxid", d_trxid, query.GetAllocator());
+
+   nts.SetArray();
+   BOOST_FOREACH(const string &t, nonterm) {
+      nts.PushBack(t.c_str(), query.GetAllocator());
+   }
+   parameters.AddMember("nonterm", nts, query.GetAllocator());
+   query.AddMember("parameters", parameters, query.GetAllocator());
+
+   if (connector->send(query) == false || connector->recv(answer) == false)
+     return false;
+   return true;
+}
+
+bool RemoteBackend::startTransaction(const string &domain, int domain_id) {
+   rapidjson::Document query,answer;
+   rapidjson::Value parameters;
+   this->d_trxid = time((time_t*)NULL);
+
+   query.SetObject();
+   JSON_ADD_MEMBER(query, "method", "startTransaction", query.GetAllocator());
+   parameters.SetObject();
+   JSON_ADD_MEMBER(parameters, "domain", domain.c_str(), query.GetAllocator());
+   JSON_ADD_MEMBER(parameters, "domain_id", domain_id, query.GetAllocator());
+   JSON_ADD_MEMBER(parameters, "trxid", d_trxid, query.GetAllocator());
+
+   query.AddMember("parameters", parameters, query.GetAllocator());
+
+   if (connector->send(query) == false || connector->recv(answer) == false) {
+     d_trxid = -1;
+     return false;
+   }
+   return true;
+
+}
+bool RemoteBackend::commitTransaction() { 
+   rapidjson::Document query,answer;
+   rapidjson::Value parameters;
+
+   query.SetObject();
+   JSON_ADD_MEMBER(query, "method", "abortTransaction", query.GetAllocator());
+   parameters.SetObject();
+   JSON_ADD_MEMBER(parameters, "trxid", d_trxid, query.GetAllocator());
+   query.AddMember("parameters", parameters, query.GetAllocator());
+
+   d_trxid = -1;
+   if (connector->send(query) == false || connector->recv(answer) == false)
+     return false;
+   return true;
+}
+
+bool RemoteBackend::abortTransaction() { 
+   rapidjson::Document query,answer;
+   rapidjson::Value parameters;
+
+   query.SetObject();
+   JSON_ADD_MEMBER(query, "method", "commitTransaction", query.GetAllocator());
+   parameters.SetObject();
+   JSON_ADD_MEMBER(parameters, "trxid", d_trxid, query.GetAllocator());
+   query.AddMember("parameters", parameters, query.GetAllocator());
+
+   d_trxid = -1;
+   if (connector->send(query) == false || connector->recv(answer) == false)
+     return false;
+   return true;
+}
+
+bool RemoteBackend::calculateSOASerial(const string& domain, const SOAData& sd, time_t& serial) {
+   rapidjson::Document query,answer;
+   rapidjson::Value parameters;
+   rapidjson::Value soadata;
+
+   query.SetObject();
+   JSON_ADD_MEMBER(query, "method", "calculateSOASerial", query.GetAllocator());
+   parameters.SetObject();
+   JSON_ADD_MEMBER(parameters, "domain", domain.c_str(), query.GetAllocator());
+   soadata.SetObject();
+   JSON_ADD_MEMBER(soadata, "qname", sd.qname.c_str(), query.GetAllocator());
+   JSON_ADD_MEMBER(soadata, "nameserver", sd.nameserver.c_str(), query.GetAllocator());
+   JSON_ADD_MEMBER(soadata, "hostmaster", sd.hostmaster.c_str(), query.GetAllocator());
+   JSON_ADD_MEMBER(soadata, "ttl", sd.ttl, query.GetAllocator());
+   JSON_ADD_MEMBER(soadata, "serial", sd.serial, query.GetAllocator());
+   JSON_ADD_MEMBER(soadata, "refresh", sd.refresh, query.GetAllocator());
+   JSON_ADD_MEMBER(soadata, "retry", sd.retry, query.GetAllocator());
+   JSON_ADD_MEMBER(soadata, "expire", sd.expire, query.GetAllocator());
+   JSON_ADD_MEMBER(soadata, "default_ttl", sd.default_ttl, query.GetAllocator());
+   JSON_ADD_MEMBER(soadata, "domain_id", sd.domain_id, query.GetAllocator());
+   JSON_ADD_MEMBER(soadata, "scopeMask", sd.scopeMask, query.GetAllocator());
+   parameters.AddMember("sd", soadata, query.GetAllocator());
+   query.AddMember("parameters", parameters, query.GetAllocator());
+
+   if (connector->send(query) == false || connector->recv(answer) == false)
+     return false;
+
+   if (answer["result"].IsInt64() == false)
+     return false;
+
+   serial = answer["result"].GetInt64();
+   return true;
+}
+
 DNSBackend *RemoteBackend::maker()
 {
    try {
index 7bc02b71e398bad1636aa0027b6a89da4ce70169..c1758d8a4e780af6d569394912d92d1c128a3f76 100644 (file)
@@ -66,8 +66,13 @@ class HTTPConnector: public Connector {
     CURL *d_c;
     std::string d_data;
     int timeout;
-    void json2string(const rapidjson::Value &input, std::string &output);
-    void requestbuilder(const std::string &method, const rapidjson::Value &parameters, struct curl_slist **slist);
+    bool d_post; 
+    bool d_post_json;
+    std::string d_capath;
+    std::string d_cafile;
+    bool json2string(const rapidjson::Value &input, std::string &output);
+    void restful_requestbuilder(const std::string &method, const rapidjson::Value &parameters, struct curl_slist **slist);
+    void post_requestbuilder(const rapidjson::Document &input, struct curl_slist **slist);
     void addUrlComponent(const rapidjson::Value &parameters, const char *element, std::stringstream& ss);
 };
 #endif
@@ -111,6 +116,16 @@ class RemoteBackend : public DNSBackend
   virtual bool getDomainInfo(const string&, DomainInfo&);
   virtual void setNotified(uint32_t id, uint32_t serial);
   virtual bool doesDNSSEC();
+  virtual bool superMasterBackend(const string &ip, const string &domain, const vector<DNSResourceRecord>&nsset, string *account, DNSBackend **ddb);
+  virtual bool createSlaveDomain(const string &ip, const string &domain, const string &account);
+  virtual bool replaceRRSet(uint32_t domain_id, const string& qname, const QType& qt, const vector<DNSResourceRecord>& rrset);
+  virtual bool feedRecord(const DNSResourceRecord &r, string *ordername);
+  virtual bool feedEnts(int domain_id, set<string>& nonterm);
+  virtual bool feedEnts3(int domain_id, const string &domain, set<string> &nonterm, unsigned int times, const string &salt, bool narrow);
+  virtual bool startTransaction(const string &domain, int domain_id);
+  virtual bool commitTransaction();
+  virtual bool abortTransaction();
+  virtual bool calculateSOASerial(const string& domain, const SOAData& sd, time_t& serial);
 
   static DNSBackend *maker();
 
@@ -119,6 +134,7 @@ class RemoteBackend : public DNSBackend
     Connector *connector;
     bool d_dnssec;
     rapidjson::Document *d_result;
-    int d_index; 
+    int d_index;
+    int64_t d_trxid;
 };
 #endif
diff --git a/modules/remotebackend/test-remotebackend-http.cc b/modules/remotebackend/test-remotebackend-http.cc
new file mode 100644 (file)
index 0000000..f91d05a
--- /dev/null
@@ -0,0 +1,73 @@
+#include "pdns/namespaces.hh"
+#include <pdns/dns.hh>
+#include <pdns/dnsbackend.hh>
+#include <pdns/dnspacket.hh>
+#include <pdns/ueberbackend.hh>
+#include <pdns/ahuexception.hh>
+#include <pdns/logger.hh>
+#include <pdns/arguments.hh>
+#include <boost/lexical_cast.hpp>
+#include <rapidjson/rapidjson.h>
+#include <rapidjson/document.h>
+#include "pdns/json.hh"
+#include "pdns/statbag.hh"
+#include "pdns/packetcache.hh"
+
+StatBag S;
+PacketCache PC;
+ArgvMap &arg()
+{
+  static ArgvMap arg;
+  return arg;
+};
+
+class RemoteLoader
+{
+   public:
+      RemoteLoader();
+};
+
+DNSBackend *be;
+
+#ifdef REMOTEBACKEND_HTTP
+
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_MAIN
+#define BOOST_TEST_MODULE unit
+
+#include <boost/test/unit_test.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/foreach.hpp>
+#include <boost/tuple/tuple.hpp>
+
+struct RemotebackendSetup {
+    RemotebackendSetup()  {
+       be = 0; 
+       try {
+               // setup minimum arguments
+               ::arg().set("module-dir")="";
+                new RemoteLoader();
+               BackendMakers().launch("remote");
+                // then get us a instance of it 
+                ::arg().set("remote-connection-string")="http:url=http://localhost:62434/dns";
+                ::arg().set("remote-dnssec")="yes";
+                be = BackendMakers().all()[0];
+       } catch (AhuException &ex) {
+               BOOST_TEST_MESSAGE("Cannot start remotebackend: " << ex.reason );
+       };
+    }
+    ~RemotebackendSetup()  {  }
+};
+
+BOOST_GLOBAL_FIXTURE( RemotebackendSetup );
+
+#else
+
+#include <iostream>
+
+int main(void) {
+  std::cout << "No HTTP support in remotebackend - skipping test" << std::endl;
+  return 0;
+}
+
+#endif
diff --git a/modules/remotebackend/test-remotebackend-json.cc b/modules/remotebackend/test-remotebackend-json.cc
new file mode 100644 (file)
index 0000000..1522d52
--- /dev/null
@@ -0,0 +1,73 @@
+#include "pdns/namespaces.hh"
+#include <pdns/dns.hh>
+#include <pdns/dnsbackend.hh>
+#include <pdns/dnspacket.hh>
+#include <pdns/ueberbackend.hh>
+#include <pdns/ahuexception.hh>
+#include <pdns/logger.hh>
+#include <pdns/arguments.hh>
+#include <boost/lexical_cast.hpp>
+#include <rapidjson/rapidjson.h>
+#include <rapidjson/document.h>
+#include "pdns/json.hh"
+#include "pdns/statbag.hh"
+#include "pdns/packetcache.hh"
+
+StatBag S;
+PacketCache PC;
+ArgvMap &arg()
+{
+  static ArgvMap arg;
+  return arg;
+};
+
+class RemoteLoader
+{
+   public:
+      RemoteLoader();
+};
+
+DNSBackend *be;
+
+#ifdef REMOTEBACKEND_HTTP
+
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_MAIN
+#define BOOST_TEST_MODULE unit
+
+#include <boost/test/unit_test.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/foreach.hpp>
+#include <boost/tuple/tuple.hpp>
+
+struct RemotebackendSetup {
+    RemotebackendSetup()  {
+       be = 0; 
+       try {
+               // setup minimum arguments
+               ::arg().set("module-dir")="";
+                new RemoteLoader();
+               BackendMakers().launch("remote");
+                // then get us a instance of it 
+                ::arg().set("remote-connection-string")="http:url=http://localhost:62434/dns/endpoint.json,post=1,post_json=1";
+                ::arg().set("remote-dnssec")="yes";
+                be = BackendMakers().all()[0];
+       } catch (AhuException &ex) {
+               BOOST_TEST_MESSAGE("Cannot start remotebackend: " << ex.reason );
+       };
+    }
+    ~RemotebackendSetup()  {  }
+};
+
+BOOST_GLOBAL_FIXTURE( RemotebackendSetup );
+
+#else
+
+#include <iostream>
+
+int main(void) {
+  std::cout << "No HTTP support in remotebackend - skipping test" << std::endl;
+  return 0;
+}
+
+#endif
diff --git a/modules/remotebackend/test-remotebackend-keys.hh b/modules/remotebackend/test-remotebackend-keys.hh
new file mode 100644 (file)
index 0000000..2ccc9b4
--- /dev/null
@@ -0,0 +1,3 @@
+DNSBackend::KeyData k1 = {1, 257, true, std::string("Private-key-format: v1.2\nAlgorithm: 5 (RSASHA1)\nModulus: qpe9fxlN4dBT38cLPWtqljZhcJjbqRprj9XsYmf2/uFu4kA5sHYrlQY7H9lpzGJPRfOAfxShBpKs1AVaVInfJQ==\nPublicExponent: AQAB\nPrivateExponent: Ad3YogzXvVDLsWuAfioY571QlolbdTbzVlhLEMLD6dSRx+xcZgw6c27ak2HAH00iSKTvqK3AyeaK8Eqy/oJ5QQ==\nPrime1: wo8LZrdU2y0xLGCeLhwziQDDtTMi18NEIwlx8tUPnhs=\nPrime2: 4HcuFqgo7NOiXFvN+V2PT+QaIt2+oi6D2m8/qtTDS78=\nExponent1: GUdCoPbi9JM7l1t6Ud1iKMPLqchaF5SMTs0UXAuous8=\nExponent2: nzgKqimX9f1corTAEw0pddrwKyEtcu8ZuhzFhZCsAxM=\nCoefficient: YGNxbulf5GTNiIu0oNKmAF0khNtx9layjOPEI0R4/RY=") };
+
+DNSBackend::KeyData k2 = {2, 256, true, std::string("Private-key-format: v1.2\nAlgorithm: 5 (RSASHA1)\nModulus: tY2TAMgL/whZdSbn2aci4wcMqohO24KQAaq5RlTRwQ33M8FYdW5fZ3DMdMsSLQUkjGnKJPKEdN3Qd4Z5b18f+w==\nPublicExponent: AQAB\nPrivateExponent: BB6xibPNPrBV0PUp3CQq0OdFpk9v9EZ2NiBFrA7osG5mGIZICqgOx/zlHiHKmX4OLmL28oU7jPKgogeuONXJQQ==\nPrime1: yjxe/iHQ4IBWpvCmuGqhxApWF+DY9LADIP7bM3Ejf3M=\nPrime2: 5dGWTyYEQRBVK74q1a64iXgaNuYm1pbClvvZ6ccCq1k=\nExponent1: TwM5RebmWeAqerzJFoIqw5IaQugJO8hM4KZR9A4/BTs=\nExponent2: bpV2HSmu3Fvuj7jWxbFoDIXlH0uJnrI2eg4/4hSnvSk=\nCoefficient: e2uDDWN2zXwYa2P6VQBWQ4mR1ZZjFEtO/+YqOJZun1Y=") };
diff --git a/modules/remotebackend/test-remotebackend-pipe.cc b/modules/remotebackend/test-remotebackend-pipe.cc
new file mode 100644 (file)
index 0000000..9a4eb15
--- /dev/null
@@ -0,0 +1,65 @@
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_MAIN
+#define BOOST_TEST_MODULE unit
+
+#include <boost/test/unit_test.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/foreach.hpp>
+#include <boost/tuple/tuple.hpp>
+#include "pdns/namespaces.hh"
+#include <pdns/dns.hh>
+#include <pdns/dnsbackend.hh>
+#include <pdns/dnspacket.hh>
+#include <pdns/ueberbackend.hh>
+#include <pdns/ahuexception.hh>
+#include <pdns/logger.hh>
+#include <pdns/arguments.hh>
+#include "pdns/dnsrecords.hh"
+#include <boost/lexical_cast.hpp>
+#include <rapidjson/rapidjson.h>
+#include <rapidjson/document.h>
+#include "pdns/json.hh"
+#include "pdns/statbag.hh"
+#include "pdns/packetcache.hh"
+
+StatBag S;
+PacketCache PC;
+ArgvMap &arg()
+{
+  static ArgvMap arg;
+  return arg;
+};
+
+class RemoteLoader
+{
+   public:
+      RemoteLoader();
+};
+
+DNSBackend *be;
+
+struct RemotebackendSetup {
+    RemotebackendSetup()  {
+       be = 0; 
+       try {
+               // setup minimum arguments
+               ::arg().set("module-dir")="";
+                new RemoteLoader();
+               BackendMakers().launch("remote");
+                // then get us a instance of it 
+                ::arg().set("remote-connection-string")="pipe:command=unittest_pipe.rb";
+                ::arg().set("remote-dnssec")="yes";
+                be = BackendMakers().all()[0];
+               // load few record types to help out
+               SOARecordContent::report();
+               NSRecordContent::report();
+                ARecordContent::report();
+       } catch (AhuException &ex) {
+               BOOST_TEST_MESSAGE("Cannot start remotebackend: " << ex.reason );
+       };
+    }
+    ~RemotebackendSetup()  {  }
+};
+
+BOOST_GLOBAL_FIXTURE( RemotebackendSetup );
+
diff --git a/modules/remotebackend/test-remotebackend-post.cc b/modules/remotebackend/test-remotebackend-post.cc
new file mode 100644 (file)
index 0000000..754e7a9
--- /dev/null
@@ -0,0 +1,73 @@
+#include "pdns/namespaces.hh"
+#include <pdns/dns.hh>
+#include <pdns/dnsbackend.hh>
+#include <pdns/dnspacket.hh>
+#include <pdns/ueberbackend.hh>
+#include <pdns/ahuexception.hh>
+#include <pdns/logger.hh>
+#include <pdns/arguments.hh>
+#include <boost/lexical_cast.hpp>
+#include <rapidjson/rapidjson.h>
+#include <rapidjson/document.h>
+#include "pdns/json.hh"
+#include "pdns/statbag.hh"
+#include "pdns/packetcache.hh"
+
+StatBag S;
+PacketCache PC;
+ArgvMap &arg()
+{
+  static ArgvMap arg;
+  return arg;
+};
+
+class RemoteLoader
+{
+   public:
+      RemoteLoader();
+};
+
+DNSBackend *be;
+
+#ifdef REMOTEBACKEND_HTTP
+
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_MAIN
+#define BOOST_TEST_MODULE unit
+
+#include <boost/test/unit_test.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/foreach.hpp>
+#include <boost/tuple/tuple.hpp>
+
+struct RemotebackendSetup {
+    RemotebackendSetup()  {
+       be = 0; 
+       try {
+               // setup minimum arguments
+               ::arg().set("module-dir")="";
+                new RemoteLoader();
+               BackendMakers().launch("remote");
+                // then get us a instance of it 
+                ::arg().set("remote-connection-string")="http:url=http://localhost:62434/dns,post=1";
+                ::arg().set("remote-dnssec")="yes";
+                be = BackendMakers().all()[0];
+       } catch (AhuException &ex) {
+               BOOST_TEST_MESSAGE("Cannot start remotebackend: " << ex.reason );
+       };
+    }
+    ~RemotebackendSetup()  {  }
+};
+
+BOOST_GLOBAL_FIXTURE( RemotebackendSetup );
+
+#else
+
+#include <iostream>
+
+int main(void) {
+  std::cout << "No HTTP support in remotebackend - skipping test" << std::endl;
+  return 0;
+}
+
+#endif
diff --git a/modules/remotebackend/test-remotebackend.cc b/modules/remotebackend/test-remotebackend.cc
new file mode 100644 (file)
index 0000000..28427c8
--- /dev/null
@@ -0,0 +1,247 @@
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/foreach.hpp>
+#include <boost/tuple/tuple.hpp>
+#include "pdns/namespaces.hh"
+#include <pdns/dns.hh>
+#include <pdns/dnsbackend.hh>
+#include <pdns/dnspacket.hh>
+#include <pdns/ueberbackend.hh>
+#include <pdns/ahuexception.hh>
+#include <pdns/logger.hh>
+#include <pdns/arguments.hh>
+#include <boost/lexical_cast.hpp>
+#include <rapidjson/rapidjson.h>
+#include <rapidjson/document.h>
+#include "pdns/json.hh"
+#include "pdns/statbag.hh"
+#include "pdns/packetcache.hh"
+
+#include "test-remotebackend-keys.hh"
+
+extern DNSBackend *be;
+
+BOOST_AUTO_TEST_SUITE(test_remotebackend_so)
+
+BOOST_AUTO_TEST_CASE(test_method_lookup) {
+   BOOST_TEST_MESSAGE("Testing lookup method");
+   DNSResourceRecord rr;
+   be->lookup(QType(QType::SOA), "unit.test");
+   // then try to get()
+   BOOST_CHECK(be->get(rr)); // and this should be TRUE.
+   // then we check rr contains what we expect
+   BOOST_CHECK_EQUAL(rr.qname, "unit.test");
+   BOOST_CHECK_MESSAGE(rr.qtype == QType::SOA, "returned qtype was not SOA");
+   BOOST_CHECK_EQUAL(rr.content, "ns.unit.test hostmaster.unit.test 1 2 3 4 5 6");
+   BOOST_CHECK_EQUAL(rr.ttl, 300);
+}
+
+BOOST_AUTO_TEST_CASE(test_method_list) {
+   int record_count = 0;
+   DNSResourceRecord rr;
+
+   BOOST_TEST_MESSAGE("Testing list method");
+   be->list("unit.test", -1);
+   while(be->get(rr)) record_count++;
+
+   BOOST_CHECK_EQUAL(record_count, 5); // number of records our test domain has
+}
+
+BOOST_AUTO_TEST_CASE(test_method_doesDNSSEC) {
+   BOOST_TEST_MESSAGE("Testing doesDNSSEC method");
+   BOOST_CHECK(be->doesDNSSEC()); // should be true
+}
+
+BOOST_AUTO_TEST_CASE(test_method_setDomainMetadata) {
+   std::vector<std::string> meta;
+   meta.push_back("VALUE");
+   BOOST_TEST_MESSAGE("Testing setDomainMetadata method");
+   BOOST_CHECK(be->setDomainMetadata("unit.test","TEST", meta));
+}
+
+BOOST_AUTO_TEST_CASE(test_method_getDomainMetadata) {
+   std::vector<std::string> meta;
+   BOOST_TEST_MESSAGE("Testing getDomainMetadata method");
+   be->getDomainMetadata("unit.test","TEST", meta);
+   BOOST_CHECK_EQUAL(meta.size(), 1);
+   // in case we got more than one value, which would be unexpected
+   // but not fatal
+   if (meta.size() > 0)
+      BOOST_CHECK_EQUAL(meta[0], "VALUE");
+}
+
+BOOST_AUTO_TEST_CASE(test_method_addDomainKey) {
+   BOOST_TEST_MESSAGE("Testing addDomainKey method");
+   BOOST_CHECK_EQUAL(be->addDomainKey("unit.test",k1), 1);
+   BOOST_CHECK_EQUAL(be->addDomainKey("unit.test",k2), 2);    
+}
+
+BOOST_AUTO_TEST_CASE(test_method_getDomainKeys) {
+   std::vector<DNSBackend::KeyData> keys;
+   BOOST_TEST_MESSAGE("Testing getDomainKeys method");
+   // we expect to get two keys
+   be->getDomainKeys("unit.test",0,keys);
+   BOOST_CHECK_EQUAL(keys.size(), 2);
+   // in case we got more than 2 keys, which would be unexpected
+   // but not fatal
+   if (keys.size() > 1) {
+      // check that we have two keys
+      BOOST_FOREACH(DNSBackend::KeyData &kd, keys) {
+        BOOST_CHECK(kd.id > 0);
+        BOOST_CHECK(kd.flags == 256 || kd.flags == 257);
+        BOOST_CHECK(kd.active == true);
+        BOOST_CHECK(kd.content.size() > 500);
+      }
+   }
+}
+
+BOOST_AUTO_TEST_CASE(test_method_deactivateDomainKey) {
+   BOOST_TEST_MESSAGE("Testing deactivateDomainKey method");
+   BOOST_CHECK(be->deactivateDomainKey("unit.test",1));
+}
+
+BOOST_AUTO_TEST_CASE(test_method_activateDomainKey) {
+   BOOST_TEST_MESSAGE("Testing activateDomainKey method");
+   BOOST_CHECK(be->activateDomainKey("unit.test",1));
+}
+
+BOOST_AUTO_TEST_CASE(test_method_removeDomainKey) {
+   BOOST_CHECK(be->removeDomainKey("unit.test",2));
+   BOOST_CHECK(be->removeDomainKey("unit.test",1));
+}
+
+BOOST_AUTO_TEST_CASE(test_method_getBeforeAndAfterNamesAbsolute) {
+   std::string unhashed,before,after;
+   BOOST_TEST_MESSAGE("Testing getBeforeAndAfterNamesAbsolute method");
+   
+   be->getBeforeAndAfterNamesAbsolute(-1, "middle.unit.test", unhashed, before, after);
+   BOOST_CHECK_EQUAL(unhashed, "middle");
+   BOOST_CHECK_EQUAL(before, "begin");
+   BOOST_CHECK_EQUAL(after, "stop");
+}
+
+BOOST_AUTO_TEST_CASE(test_method_getTSIGKey) {
+   std::string algorithm, content;
+   BOOST_TEST_MESSAGE("Testing getTSIGKey method");
+   be->getTSIGKey("unit.test",&algorithm,&content);
+   BOOST_CHECK_EQUAL(algorithm, "NULL");
+   BOOST_CHECK_EQUAL(content, "NULL");
+}
+
+BOOST_AUTO_TEST_CASE(test_method_setNotified) {
+   BOOST_TEST_MESSAGE("Testing setNotified method");
+   be->setNotified(1, 2);
+   BOOST_CHECK(true); // we check this on next step
+}
+
+BOOST_AUTO_TEST_CASE(test_method_getDomainInfo) {
+   DomainInfo di;
+   BOOST_TEST_MESSAGE("Testing getDomainInfo method");
+   be->getDomainInfo("unit.test", di);
+   BOOST_CHECK_EQUAL(di.zone, "unit.test");
+   BOOST_CHECK_EQUAL(di.serial, 2);
+   BOOST_CHECK_EQUAL(di.notified_serial, 2);
+   BOOST_CHECK_EQUAL(di.kind, DomainInfo::Native);
+   BOOST_CHECK_EQUAL(di.backend, be);
+}
+
+BOOST_AUTO_TEST_CASE(test_method_superMasterBackend) {
+   DNSResourceRecord rr;
+   std::vector<DNSResourceRecord> nsset; 
+   DNSBackend *dbd;
+   BOOST_TEST_MESSAGE("Testing superMasterBackend method");
+
+   rr.qname = "example.com";
+   rr.qtype = QType::NS;
+   rr.qclass = QClass::IN;
+   rr.ttl = 300;
+   rr.content = "ns1.example.com";
+   nsset.push_back(rr);
+   rr.qname = "example.com";
+   rr.qtype = QType::NS;
+   rr.qclass = QClass::IN;
+   rr.ttl = 300;
+   rr.content = "ns2.example.com";
+   nsset.push_back(rr);
+
+   BOOST_CHECK(be->superMasterBackend("10.0.0.1", "example.com", nsset, NULL, &dbd));
+
+   // let's see what we got
+   BOOST_CHECK_EQUAL(dbd, be);
+}
+
+BOOST_AUTO_TEST_CASE(test_method_createSlaveDomain) {
+   BOOST_TEST_MESSAGE("Testing createSlaveDomain method");
+   BOOST_CHECK(be->createSlaveDomain("10.0.0.1", "pirate.unit.test", ""));
+}
+
+BOOST_AUTO_TEST_CASE(test_method_feedRecord) {
+   DNSResourceRecord rr;
+   BOOST_TEST_MESSAGE("Testing feedRecord method");
+   be->startTransaction("example.com",2);
+   rr.qname = "example.com";
+   rr.qtype = QType::SOA;
+   rr.qclass = QClass::IN;
+   rr.ttl = 300;
+   rr.content = "ns1.example.com hostmaster.example.com 2013013441 7200 3600 1209600 300";
+   BOOST_CHECK(be->feedRecord(rr, NULL));
+   rr.qname = "replace.example.com";
+   rr.qtype = QType::A;
+   rr.qclass = QClass::IN;
+   rr.ttl = 300;
+   rr.content = "127.0.0.1";
+   BOOST_CHECK(be->feedRecord(rr, NULL));
+   be->commitTransaction();
+}
+
+BOOST_AUTO_TEST_CASE(test_method_replaceRRSet) {
+   be->startTransaction("example.com",2);
+   DNSResourceRecord rr;
+   std::vector<DNSResourceRecord> rrset;
+   BOOST_TEST_MESSAGE("Testing replaceRRSet method");
+   rr.qname = "replace.example.com";
+   rr.qtype = QType::A;
+   rr.qclass = QClass::IN;
+   rr.ttl = 300;
+   rr.content = "1.1.1.1";
+   rrset.push_back(rr);
+   BOOST_CHECK(be->replaceRRSet(2, "replace.example.com", QType(QType::A), rrset));
+   be->commitTransaction();
+}
+
+BOOST_AUTO_TEST_CASE(test_method_feedEnts) {
+   BOOST_TEST_MESSAGE("Testing feedEnts method");
+   be->startTransaction("example.com",2);
+   set<string> nonterm = boost::assign::list_of("_udp")("_sip._udp");
+   BOOST_CHECK(be->feedEnts(2, nonterm));
+   be->commitTransaction();
+}
+
+BOOST_AUTO_TEST_CASE(test_method_feedEnts3) {
+   BOOST_TEST_MESSAGE("Testing feedEnts3 method");
+   be->startTransaction("example.com",2);
+   set<string> nonterm = boost::assign::list_of("_udp")("_sip._udp");
+   BOOST_CHECK(be->feedEnts3(2, "example.com", nonterm, 1, "\xaa\xbb\xcc\xdd", 0));
+   be->commitTransaction();
+}
+
+BOOST_AUTO_TEST_CASE(test_method_abortTransaction) {
+   BOOST_TEST_MESSAGE("Testing abortTransaction method");
+   be->startTransaction("example.com",2);
+   BOOST_CHECK(be->abortTransaction());
+}
+
+BOOST_AUTO_TEST_CASE(test_method_calculateSOASerial) {
+   SOAData sd;
+   time_t serial;
+   be->getSOA("unit.test",sd);
+   BOOST_CHECK(be->calculateSOASerial("unit.test",sd,serial));
+
+   BOOST_CHECK_EQUAL(serial, 2013060300);
+}
+
+BOOST_AUTO_TEST_SUITE_END();
diff --git a/modules/remotebackend/testrunner.sh b/modules/remotebackend/testrunner.sh
new file mode 100644 (file)
index 0000000..7f687a5
--- /dev/null
@@ -0,0 +1,57 @@
+#!/bin/bash 
+
+webrick_pid=""
+
+function start_web() {
+  if [ x"$REMOTEBACKEND_HTTP" == "xyes" ]; then
+   ./unittest_$1.rb &
+   webrick_pid=$!
+   sleep 1
+  fi
+}
+
+function stop_web() {
+ if [ ! -z "$webrick_pid" ]; then
+   kill -TERM $webrick_pid
+   # wait a moment for it to die
+   i=0
+   while [ $i -lt 5 ]; do
+     sleep 1
+     kill -0 $webrick_pid 2>/dev/null
+     if [ $? -ne 0 ]; then break; fi
+     let i=i+1
+   done
+ fi
+}
+
+mode=`basename "$1"`
+
+case "$mode" in
+  test_remotebackend_pipe)
+    ./test_remotebackend_pipe
+    rv=$?
+  ;;
+  test_remotebackend_http)
+    start_web "http"
+    ./test_remotebackend_http
+    rv=$?
+    stop_web
+  ;;
+  test_remotebackend_post)
+    start_web "post"
+    ./test_remotebackend_post
+    rv=$?
+    stop_web
+  ;;
+  test_remotebackend_json)
+    start_web "json"
+    ./test_remotebackend_json
+    rv=$?
+    stop_web
+  ;;
+  *)
+     echo "Usage: $0 test_remotebackend_(pipe|http|post)"
+  ;;
+esac
+
+exit $rv
diff --git a/modules/remotebackend/unittest.rb b/modules/remotebackend/unittest.rb
new file mode 100644 (file)
index 0000000..69ae27f
--- /dev/null
@@ -0,0 +1,224 @@
+require 'rubygems'
+require 'json'
+
+# define a simple $domain
+
+$ttl = 300
+$notified_serial = 1
+
+$domain = {
+  "unit.test" => { 
+      "SOA" => ["ns.unit.test hostmaster.unit.test 1 2 3 4 5 6"],
+      "NS" => ["ns1.unit.test", "ns2.unit.test"],
+  },
+  "ns1.unit.test" => {
+       "A" => ["10.0.0.1"]
+  },
+  "ns2.unit.test" => {
+       "A" => ["10.0.0.2"]
+  }
+}
+
+$meta = {}
+
+$keys = {}
+
+class Handler
+   def initialize
+   end
+
+   def rr(qname, qtype, content, ttl, priority = 0, auth = 1, domain_id = -1)
+      {:qname => qname, :qtype => qtype, :content => content, :ttl => ttl.to_i, :priority => priority.to_i, :auth => auth.to_i, :domain_id => domain_id.to_i}
+   end
+
+   def do_initialize(*args)
+     return true, "Test bench initialized"
+   end
+
+   def do_lookup(args) 
+     ret = []
+     if $domain.has_key?(args["qname"])
+       if $domain[args["qname"]].has_key?(args["qtype"])
+         $domain[args["qname"]][args["qtype"]].each do |rd|
+            ret << rr(args["qname"], args["qtype"], rd, $ttl)
+         end
+       elsif args["qtype"] == 'ANY'
+         $domain[args["qname"]].each do |qt,qr|
+           qr.each do |rd|
+             ret << rr(args["qname"], qt, rd, $ttl)
+           end
+         end
+       end
+     end
+     [false] unless ret.size>0
+     [ret]
+   end
+
+   def do_list(args)
+     ret = []
+     if args["zonename"] == "unit.test"
+       $domain.each do |qname,rdata| 
+         rdata.each do |rtype,rc|
+          rc.each do |rd|
+            ret << rr(qname,rtype,rd,$ttl)
+          end
+         end
+        end
+     end
+     [false] unless ret.size>0
+     [ret]
+   end 
+
+   def do_getdomainmetadata(args)
+     return [ $meta[args["name"]][args["kind"]] ] if $meta.has_key?(args["name"]) and $meta[args["name"]].has_key?(args["kind"])
+     return [false]
+   end
+
+   def do_setdomainmetadata(args)
+     $meta[args["name"].to_s] = {} unless $meta.has_key? args["name"]
+     $meta[args["name"].to_s][args["kind"].to_s] = args["value"].to_a
+     [true]
+   end
+
+   def do_adddomainkey(args)
+     $keys[args["name"]] = [] unless $keys.has_key? args["name"]
+     id=$keys[args["name"]].size + 1
+     args["key"]["id"] = id
+     $keys[args["name"]] << args["key"]
+     [id]
+   end
+
+   def do_getdomainkeys(args) 
+     if $keys.has_key? args["name"]
+       return [ $keys[args["name"]] ]
+     end
+     [false]
+   end 
+
+   def do_activatedomainkey(args) 
+     args["id"] = args["id"].to_i
+     if $keys.has_key? args["name"]
+      if $keys[args["name"]][args["id"]-1]
+         $keys[args["name"]][args["id"]-1]["active"] = true
+         return [true]
+      end
+     end
+     [false]
+   end 
+
+   def do_deactivatedomainkey(args)
+     args["id"] = args["id"].to_i
+     if $keys.has_key? args["name"]
+      if $keys[args["name"]][args["id"]-1]
+         $keys[args["name"]][args["id"]-1]["active"] = false
+         return [true]
+      end
+     end
+     [false]
+   end
+
+   def do_removedomainkey(args)
+     args["id"] = args["id"].to_i
+     if $keys.has_key? args["name"]
+      if $keys[args["name"]][args["id"]-1]
+       $keys[args["name"]].delete_at args["id"]-1
+       return [true]
+      end
+     end
+     [false]
+   end 
+
+   def do_getbeforeandafternamesabsolute(args)
+     return [ { :unhashed => "middle", :before => "begin", :after => "stop" } ] if args["qname"] == 'middle.unit.test'
+     [false]
+   end
+
+   def do_gettsigkey(args) 
+     if args["name"] == "unit.test"
+       return [{:algorithm => "NULL", :content => "NULL"}]
+     end
+     [false] 
+   end
+
+   def do_setnotified(args) 
+     if args["id"].to_i == 1 
+       $notified_serial = args["serial"].to_i
+       return [true]
+     end
+     [false]
+   end
+
+   def do_getdomaininfo(args) 
+     if args["name"] == "unit.test"
+       return [{ 
+               :id => 1,
+               :zone => "unit.test",
+               :masters => ["10.0.0.1"],
+               :notified_serial => $notified_serial,
+               :serial => $notified_serial, 
+               :last_check => Time.now.to_i,
+               :kind => 'native'
+       }]
+     end
+     [false]
+   end
+
+   def do_supermasterbackend(args) 
+     $domain[args["domain"]] = {
+        "NS" => args["nsset"]
+     }
+     [true]
+   end
+
+   def do_createslavedomain(args)
+     $domain[args["domain"]] = {
+     }
+     [true]
+   end
+
+   def do_feedrecord(args)
+      args.delete "trxid"
+      rr = args["rr"]
+      name = rr["qname"]
+      qtype = rr["qtype"]
+      $domain[name] = {} unless $domain.has_key? name
+      $domain[name][qtype] = [] unless $domain[name].has_key? qtype
+      $domain[name][qtype] << rr["content"]
+      [true]
+   end
+
+   def do_replacerrset(args)
+      $domain[args["qname"]].delete args["qtype"] if $domain.has_key?(args["qname"]) and $domain[args["qname"]].has_key?(args["qtype"])
+      args["rrset"] = args["rrset"].values if args["rrset"].is_a?(Hash)
+      args["rrset"].each do |rr|
+        self.do_feedrecord({"trxid" => args["trxid"], "rr" => rr})
+      end
+      [true]
+   end 
+
+   def do_feedents(args)
+      [true]
+   end
+
+   def do_feedents3(args)
+      [true]
+   end
+
+   def do_starttransaction(args) 
+     [true]
+   end
+
+   def do_committransaction(args)
+     [true]
+   end
+
+   def do_aborttransaction(args)
+     [true]
+   end
+  
+   def do_calculatesoaserial(args)
+     return [2013060300] if args["sd"]["qname"] == "unit.test"
+     [false]
+   end
+end
+
diff --git a/modules/remotebackend/unittest_http.rb b/modules/remotebackend/unittest_http.rb
new file mode 100644 (file)
index 0000000..da10670
--- /dev/null
@@ -0,0 +1,179 @@
+#!/usr/bin/ruby1.9.1
+
+require 'json'
+require 'thread'
+require "rubygems"
+require "webrick"
+require "./unittest"
+
+class DNSBackendHandler < WEBrick::HTTPServlet::AbstractServlet
+   def initialize(server, dnsbackend)
+     @dnsbackend = dnsbackend
+     @semaphore = Mutex.new
+     @f = File.open("/tmp/tmp.txt","a")
+   end
+
+   def parse_arrays(params)
+     newparams = {}
+     params.each do |key,val|
+         if key=~/^(.*)\[(.*)\]\[(.*)\]/
+             newparams[$1] = {} unless newparams.has_key? $1
+             newparams[$1][$2] = {} unless newparams[$1].has_key? $2
+             newparams[$1][$2][$3] = val
+             params.delete key
+         elsif key=~/^(.*)\[(.*)\]/ 
+           if $2 == "" 
+             newparams[$1] = [] unless newparams.has_key? $1
+             newparams[$1] << val
+           else 
+             newparams[$1] = {} unless newparams.has_key? $1
+             newparams[$1][$2] = val
+           end
+           params.delete key
+         end
+     end
+     params.merge newparams
+   end
+
+   def parse_url(url)
+     url = url.split('/')
+     method = url.shift.downcase
+
+     # do some determining based on method names
+     args = case method
+     when "lookup"
+         {
+          "qname" => url.shift,
+          "qtype" => url.shift
+         }
+     when "list"
+        {
+          "zonename" => url.shift
+        }
+     when "getbeforeandafternamesabsolute", "getbeforeandafternames"
+        {
+           "id" => url.shift.to_i,
+           "qname" => url.shift 
+        }
+     when "getdomainmetadata", "setdomainmetadata", "getdomainkeys"
+        {
+            "name" => url.shift,
+            "kind" => url.shift
+        }
+     when "removedomainkey", "activatedomainkey", "deactivatedomainkey"
+        {
+             "id" => url.shift.to_i,
+             "name" => url.shift
+        } 
+     when "adddomainkey", "gettsigkey", "getdomaininfo"
+        {
+             "name" => url.shift
+        }
+     when "setnotified", "feedents"
+        {
+             "id" => url.shift.to_i
+        }
+     when "supermasterbackend", "createslavedomain"
+        {
+             "ip" => url.shift,
+             "domain" => url.shift
+        }
+     when "feedents3"
+        {
+             "id" => url.shift.to_i,
+             "domain" => url.shift
+        }
+     when "starttransaction"
+        {
+             "id" => url.shift.to_i,
+             "domain" => url.shift,
+            "trxid" => url.shift.to_i
+       }
+     when "committransaction", "aborttransaction"
+        {
+             "trxid" => url.shift.to_i
+        }
+     when "replacerrset"
+        {
+          "id" => url.shift.to_i,
+          "qname" => url.shift,
+          "qtype" => url.shift
+        }
+     else
+        {}
+     end
+
+     [method, args]
+   end
+
+   def do_GET(req,res)
+     req.continue
+
+     tmp = req.path[/dns\/(.*)/,1]
+     return 400, "Bad request" if (tmp.nil?)
+
+     method, args = parse_url(tmp)
+
+     method = "do_#{method}"
+    
+     # get more arguments
+     req.each do |k,v|
+        attr = k[/X-RemoteBackend-(.*)/,1]
+        if attr 
+          args[attr] = v
+        end
+     end
+
+     args = args.merge req.query
+
+     if method == "do_adddomainkey"
+        args["key"] = {
+           "flags" => args.delete("flags").to_i,
+           "active" => args.delete("active").to_i,
+           "content" => args.delete("content")
+        }
+     end
+
+     args = parse_arrays args
+
+     @f.puts method
+     @f.puts args
+
+     @semaphore.synchronize do
+       if @dnsbackend.respond_to?(method.to_sym)
+          result, log = @dnsbackend.send(method.to_sym, args)
+          body = {:result => result, :log => log}
+          res.status = 200
+          res["Content-Type"] = "application/javascript; charset=utf-8"
+          res.body = body.to_json
+        else
+          res.status = 404
+          res["Content-Type"] = "application/javascript; charset=utf-8"
+          res.body = ({:result => false, :log => ["Method not found"]}).to_json
+        end
+     end
+   end
+
+   def do_DELETE(req,res)
+     do_GET(req,res)
+   end
+   
+   def do_POST(req,res)
+     do_GET(req,res)
+   end 
+end
+
+server = WEBrick::HTTPServer.new(
+       :Port=>62434,
+       :BindAddress=>"localhost",
+#      Logger: WEBrick::Log.new("remotebackend-server.log"),
+       :AccessLog=>[ [ File.open("remotebackend-access.log", "w"), WEBrick::AccessLog::COMBINED_LOG_FORMAT ] ] 
+)
+
+be = Handler.new 
+server.mount "/dns", DNSBackendHandler, be
+
+trap('INT') { server.stop }
+trap('TERM') { server.stop }
+
+server.start
diff --git a/modules/remotebackend/unittest_json.rb b/modules/remotebackend/unittest_json.rb
new file mode 100644 (file)
index 0000000..1496bd6
--- /dev/null
@@ -0,0 +1,58 @@
+#!/usr/bin/ruby1.9.1
+
+require 'json'
+require 'thread'
+require "rubygems"
+require "webrick"
+require "./unittest"
+
+class DNSBackendHandler < WEBrick::HTTPServlet::AbstractServlet
+   def initialize(server, dnsbackend)
+     @dnsbackend = dnsbackend
+     @semaphore = Mutex.new
+     @f = File.open("/tmp/tmp.txt","a")
+   end
+
+   def do_POST(req,res)
+     req.continue
+
+     return 400, "Bad request" unless req.path == "/dns/endpoint.json"
+
+     tmp = JSON::parse(req.body)
+     method = tmp["method"].downcase
+     method = "do_#{method}"
+     args = tmp["parameters"]
+    
+     @f.puts method
+     @f.puts args
+
+     @semaphore.synchronize do
+       if @dnsbackend.respond_to?(method.to_sym)
+          result, log = @dnsbackend.send(method.to_sym, args)
+          body = {:result => result, :log => log}
+          res.status = 200
+          res["Content-Type"] = "application/javascript; charset=utf-8"
+          res.body = body.to_json
+        else
+          res.status = 404
+          res["Content-Type"] = "application/javascript; charset=utf-8"
+          res.body = ({:result => false, :log => ["Method not found"]}).to_json
+        end
+     end
+   end
+end
+
+server = WEBrick::HTTPServer.new(
+       :Port=>62434,
+       :BindAddress=>"localhost",
+#      Logger: WEBrick::Log.new("remotebackend-server.log"),
+       :AccessLog=>[ [ File.open("remotebackend-access.log", "w"), WEBrick::AccessLog::COMBINED_LOG_FORMAT ] ] 
+)
+
+be = Handler.new 
+server.mount "/dns", DNSBackendHandler, be
+
+trap('INT') { server.stop }
+trap('TERM') { server.stop }
+
+server.start
diff --git a/modules/remotebackend/unittest_pipe.rb b/modules/remotebackend/unittest_pipe.rb
new file mode 100644 (file)
index 0000000..f3c3c51
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/ruby1.9.1
+
+require 'rubygems'
+require 'json'
+require './unittest'
+
+h = Handler.new()
+f = File.open "/tmp/tmp.txt","a"
+
+STDOUT.sync = true
+begin
+  STDIN.each_line do |line|
+    f.puts line
+    # expect json
+    input = {}
+    line = line.strip
+    next if line.empty?
+    begin
+      input = JSON.parse(line)
+      method = "do_#{input["method"].downcase}"
+      args = input["parameters"]
+
+      if h.respond_to?(method.to_sym) == false
+         res = false
+      elsif args.size > 0
+         res, log = h.send(method,args)
+      else
+         res, log = h.send(method)
+      end
+      puts ({:result => res, :log => log}).to_json
+      f.puts({:result => res, :log => log}).to_json
+    rescue JSON::ParserError
+      puts ({:result => false, :log => "Cannot parse input #{line}"}).to_json
+      next
+    end
+  end
+rescue SystemExit, Interrupt
+end
diff --git a/modules/remotebackend/unittest_post.rb b/modules/remotebackend/unittest_post.rb
new file mode 100644 (file)
index 0000000..079d7ad
--- /dev/null
@@ -0,0 +1,59 @@
+#!/usr/bin/ruby1.9.1
+
+require 'json'
+require 'thread'
+require "rubygems"
+require "webrick"
+require "./unittest"
+
+class DNSBackendHandler < WEBrick::HTTPServlet::AbstractServlet
+   def initialize(server, dnsbackend)
+     @dnsbackend = dnsbackend
+     @semaphore = Mutex.new
+     @f = File.open("/tmp/tmp.txt","a")
+   end
+
+   def do_POST(req,res)
+     req.continue
+
+     tmp = req.path[/dns\/(.*)/,1]
+     return 400, "Bad request" if (tmp.nil?)
+
+     url = tmp.split('/')
+     method = url.shift.downcase
+     method = "do_#{method}"
+     args = JSON::parse(req.query["parameters"])
+    
+     @f.puts method
+     @f.puts args
+
+     @semaphore.synchronize do
+       if @dnsbackend.respond_to?(method.to_sym)
+          result, log = @dnsbackend.send(method.to_sym, args)
+          body = {:result => result, :log => log}
+          res.status = 200
+          res["Content-Type"] = "application/javascript; charset=utf-8"
+          res.body = body.to_json
+        else
+          res.status = 404
+          res["Content-Type"] = "application/javascript; charset=utf-8"
+          res.body = ({:result => false, :log => ["Method not found"]}).to_json
+        end
+     end
+   end
+end
+
+server = WEBrick::HTTPServer.new(
+       :Port=>62434,
+       :BindAddress=>"localhost",
+#      Logger: WEBrick::Log.new("remotebackend-server.log"),
+       :AccessLog=>[ [ File.open("remotebackend-access.log", "w"), WEBrick::AccessLog::COMBINED_LOG_FORMAT ] ] 
+)
+
+be = Handler.new 
+server.mount "/dns", DNSBackendHandler, be
+
+trap('INT') { server.stop }
+trap('TERM') { server.stop }
+
+server.start
index 6ebe4f83b0b99d33884ea90550b71a56cf7c8713..ab42a2df2c3cc740621527f9be81847600c3cbba 100644 (file)
@@ -19041,8 +19041,8 @@ record building scripts on his <ulink url="http://anders.com/projects/sysadmin/d
           <tbody>
             <row><entry>Native</entry><entry>Yes</entry></row>
             <row><entry>Master</entry><entry>Yes*</entry></row>
-             <row><entry>Slave</entry><entry></entry></row>
-             <row><entry>Superslave</entry><entry></entry></row>
+             <row><entry>Slave</entry><entry>Yes*</entry></row>
+             <row><entry>Superslave</entry><entry>Yes*</entry></row>
              <row><entry>Autoserial</entry><entry>Yes*</entry></row>
              <row><entry>DNSSEC</entry><entry>Yes*</entry></row>
             <row><entry>Multiple instances</entry><entry>Yes</entry></row>
@@ -19055,6 +19055,9 @@ record building scripts on his <ulink url="http://anders.com/projects/sysadmin/d
       <para>
         This backend provides unix socket / pipe / http remoting for powerdns.
       </para>
+     <sect2 id="remotebackend-notices"><title>Important notices</title>
+      <para>Please do not use remotebackend shipped before version 3.3. This version has severe bug that can crash the entire process.</para>
+     </sect2>
      <sect2 id="remotebackend-compiling"><title>Compiling</title>
         <para>
         To compile this backend, you need to configure --with-modules="remote pipe", for
@@ -19063,7 +19066,7 @@ record building scripts on his <ulink url="http://anders.com/projects/sysadmin/d
       </sect2>
       <sect2 id="remotebackend-usage"><title>Usage</title>
         <para>
-          The only configuration option for this backend is remote-connection-string. It comprises of two elements: type of backend, and parameters.
+          The only configuration optionss for backend are remote-connection-string and remote-dnssec.
         </para>
         <para>
           <programlisting>
@@ -19099,12 +19102,21 @@ remote-connection-string=unix:command=/path/to/executable,timeout=2000
       </sect3>
 
 
-      <sect3 id="remotebackend-HTTP"><title>HTTP backend</title>
+      <sect3 id="remotebackend-http"><title>HTTP backend</title>
         <para>
-          parameters: url, url-suffix, timeout (default 2000)
+          parameters: url, url-suffix, post, post_json, cafile, capath, timeout (default 2000)
         </para>
         <para>
-          HTTP backend tries to do RESTful requests to your server. See examples.
+          <programlisting>
+remote-connection-string=http:url=http://localhost:63636/dns,url-suffix=.php
+</programlisting>
+        </para>
+        <para>
+          HTTP backend tries to do RESTful requests to your server. See examples. You can also
+          use post to change behaviour so that it will send POST request to url/method + url_suffix
+          with parameters=json-formatted-parameters. If you use post and post_json, it will POST
+          url with text/javascript containing JSON formatted RPC request, just like for pipe and unix. 
+          You can use '1', 'yes', 'on' or 'true' to turn these features on. 
         </para>
         <para>
           URL should not end with /, and url-suffix is optional, but if you define it, it's
@@ -19112,6 +19124,10 @@ remote-connection-string=unix:command=/path/to/executable,timeout=2000
           URL. Timeout is divided by 1000 because libcurl only supports seconds, but this is
           given in milliseconds for consistency with other backends. 
         </para>
+        <para>
+          You can use HTTPS requests. If cafile and capath is left empty, remote SSL certificate is not checked. 
+          HTTP Authentication is not supported. SSL support requires that your cURL is compiled with it. 
+        </para>
       </sect3>
       </sect2>
 
@@ -19308,7 +19324,7 @@ to appropriate value, otherwise things can go wrong.
    <para>
      Query:
 <programlisting>
-GET /dnsapi/list/example.com HTTP/1.1
+GET /dnsapi/list/-1/example.com HTTP/1.1
 X-RemoteBackend-domain-id: -1
 </programlisting>
    </para>
@@ -19344,7 +19360,7 @@ Content-Type: text/javascript; charset=utf-8
   <term>Description</term>
 <listitem><para>
 Asks the names before and after qname. qname is given without dots or domain part. The query
-can also be hashed. Care must be taken to handle wrap-around when qname is first or last in
+will be hashed when using NSEC3. Care must be taken to handle wrap-around when qname is first or last in
 the ordered list. Do not return nil for either one. 
 </para></listitem>
  </varlistentry>
@@ -19496,7 +19512,7 @@ POST /dnsapi/setdomainmetadata/example.com/PRESIGNED HTTP/1.1
 Content-Type: application/x-www-form-urlencoded 
 Content-Length: 12
 
-value1=YES&amp;
+value[]=YES&amp;
 </programlisting>
    </para>
    <para>
@@ -20041,6 +20057,656 @@ Content-Type: text/javascript; charset=utf-8
  </varlistentry>
 </variablelist>
 </sect4>
+
+<sect4 id="remotebackend-api-method-supermasterbackend"><title>Method: superMasterBackend</title>
+ <variablelist>
+  <varlistentry>
+   <term>Mandatory:</term>
+   <listitem><para>No</para></listitem>
+ </varlistentry>
+ <varlistentry>
+   <term>Parameters:</term>
+   <listitem><para>ip,domain,nsset,account</para></listitem>
+ </varlistentry>
+ <varlistentry>
+   <term>Reply:</term>
+   <listitem><para>true for success, false for failure. can also return account=>name of account</para></listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Description</term>
+<listitem><para>
+Creates new domain with given record(s) as master servers. IP address is the address where notify is received from. nsset is array of NS resource records.
+</para></listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Example JSON/RPC:</term>
+  <listitem>
+   <para>
+    Query:
+<programlisting>
+{"method":"superMasterBackend","parameters":{"ip":"10.0.0.1","domain":"example.com","nsset":[{"qtype":"NS","qname":"example.com","qclass":1,"content":"ns1.example.com","ttl":300,"priority":0,"auth":true},{"qtype":"NS","qname":"example.com","qclass":1,"content":"ns2.example.com","ttl":300,"priority":0,"auth":true}]}}
+</programlisting>
+   </para>
+   <para>
+    Response:
+<programlisting>
+{"result":true}
+</programlisting>
+   Alternative response:
+<programlisting>
+{"result":{"account":"my account"}}
+</programlisting>
+   </para>
+  </listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Example HTTP/RPC:</term>
+  <listitem>
+   <para>
+     Query:
+<programlisting>
+POST /dnsapi/supermasterbackend/10.0.0.1/example.com
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 317
+
+nsset[1][qtype]=NS&amp;nsset[1][qname]=example.com&amp;nsset[1][qclass]=1&amp;nsset[1][content]=ns1.example.com&amp;nsset[1][ttl]=300&amp;nsset[1][priority]=0&amp;nsset[1][auth]=true&amp;nsset[2][qtype]=NS&amp;nsset[2][qname]=example.com&amp;nsset[2][qclass]=1&amp;nsset[2][content]=ns2.example.com&amp;nsset[2][ttl]=300&amp;nsset[2][priority]=0&amp;nsset[2][auth]=true
+</programlisting>
+   </para>
+   <para>
+     Response:
+<programlisting>
+HTTP/1.1 200 OK
+Content-Type: text/javascript; charset=utf-8
+
+{"result":true}
+</programlisting>
+    Alternative response
+<programlisting>
+HTTP/1.1 200 OK
+Content-Type: text/javascript; charset=utf-8
+
+{"result":{"account":"my account}}
+</programlisting>
+
+   </para>
+  </listitem>
+ </varlistentry>
+</variablelist>
+</sect4>
+
+<sect4 id="remotebackend-api-method-createslavedomain"><title>Method: createSlaveDomain</title>
+ <variablelist>
+  <varlistentry>
+   <term>Mandatory:</term>
+   <listitem><para>No</para></listitem>
+ </varlistentry>
+ <varlistentry>
+   <term>Parameters:</term>
+   <listitem><para>ip, domain</para></listitem>
+ </varlistentry>
+ <varlistentry>
+   <term>Optional parameters:</term>
+   <listitem><para>account</para></listitem>
+ </varlistentry>
+ <varlistentry>
+   <term>Reply:</term>
+   <listitem><para>true for success, false for failure</para></listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Description</term>
+<listitem><para>
+Creates new domain. This method is called when NOTIFY is received and you are superslaving. 
+</para></listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Example JSON/RPC:</term>
+  <listitem>
+   <para>
+    Query:
+<programlisting>
+{"method":"createSlaveDomain","parameters":{"ip":"10.0.0.1","domain":"pirate.unit.test"}}
+</programlisting>
+   </para>
+   <para>
+    Response:
+<programlisting>
+{"result":true}
+</programlisting>
+   </para>
+  </listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Example HTTP/RPC:</term>
+  <listitem>
+   <para>
+     Query:
+<programlisting>
+POST /dnsapi/createslavedomain/10.0.0.1/pirate.unit.test
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 0
+</programlisting>
+   </para>
+   <para>
+     Response:
+<programlisting>
+HTTP/1.1 200 OK
+Content-Type: text/javascript; charset=utf-8
+
+{"result":true}
+</programlisting>
+   </para>
+  </listitem>
+ </varlistentry>
+</variablelist>
+</sect4>
+
+<sect4 id="remotebackend-api-method-replacerrset"><title>Method: replaceRRSet</title>
+ <variablelist>
+  <varlistentry>
+   <term>Mandatory:</term>
+   <listitem><para>No</para></listitem>
+ </varlistentry>
+ <varlistentry>
+   <term>Parameters:</term>
+   <listitem><para>domain_id, qname, qtype, rrset</para></listitem>
+ </varlistentry>
+ <varlistentry>
+   <term>Reply:</term>
+   <listitem><para>true for success, false for failure</para></listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Description</term>
+<listitem><para>
+This method replaces a given resource record with new set. The new qtype can be different from the old. 
+</para></listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Example JSON/RPC:</term>
+  <listitem>
+   <para>
+    Query:
+<programlisting>
+{"method":"replaceRRSet","parameters":{"domain_id":2,"qname":"replace.example.com","qtype":"A","trxid":1370416133,"rrset":[{"qtype":"A","qname":"replace.example.com","qclass":1,"content":"1.1.1.1","ttl":300,"priority":0,"auth":true}]}}
+</programlisting>
+   </para>
+   <para>
+    Response:
+<programlisting>
+{"result":true}
+</programlisting>
+   </para>
+  </listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Example HTTP/RPC:</term>
+  <listitem>
+   <para>
+     Query:
+<programlisting>
+POST /dnsapi/replacerrset/2/replace.example.com/A
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 135
+
+trxid=1370416133&amp;rrset[qtype]=A&amp;rrset[qname]=replace.example.com&amp;rrset[qclass]=1&amp;rrset[content]=1.1.1.1&amp;rrset[priority]=0&amp;rrset[auth]=1
+</programlisting>
+   </para>
+   <para>
+     Response:
+<programlisting>
+HTTP/1.1 200 OK
+Content-Type: text/javascript; charset=utf-8
+
+{"result":true}
+</programlisting>
+   </para>
+  </listitem>
+ </varlistentry>
+</variablelist>
+</sect4>
+
+<sect4 id="remotebackend-api-method-feedrecord"><title>Method: feedRecord</title>
+ <variablelist>
+  <varlistentry>
+   <term>Mandatory:</term>
+   <listitem><para>No</para></listitem>
+ </varlistentry>
+ <varlistentry>
+   <term>Parameters:</term>
+   <listitem><para>rr, trxid</para></listitem>
+ </varlistentry>
+ <varlistentry>
+   <term>Reply:</term>
+   <listitem><para>true for success, false for failure</para></listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Description</term>
+<listitem><para>
+Asks to feed new record into system. If startTransaction was called, trxId identifies a transaction. It is not always called by PowerDNS.
+</para></listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Example JSON/RPC:</term>
+  <listitem>
+   <para>
+    Query:
+<programlisting>
+{"method":"feedRecord","parameters":{"rr":{"qtype":"A","qname":"replace.example.com","qclass":1,"content":"127.0.0.1","ttl":300,"priority":0,"auth":true},"trxid":1370416133}}
+</programlisting>
+   </para>
+   <para>
+    Response:
+<programlisting>
+{"result":true}
+</programlisting>
+   </para>
+  </listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Example HTTP/RPC:</term>
+  <listitem>
+   <para>
+     Query:
+<programlisting>
+POST /dnsapi/feedrecord/1370416133
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 117
+
+rr[qtype]=A&amp;rr[qname]=replace.example.com&amp;rr[qclass]=1&amp;rr[content]=127.0.0.1&amp;rr[ttl]=300&amp;rr[priority]=0&amp;rr[auth]=true
+</programlisting>
+   </para>
+   <para>
+     Response:
+<programlisting>
+HTTP/1.1 200 OK
+Content-Type: text/javascript; charset=utf-8
+
+{"result":true}
+</programlisting>
+   </para>
+  </listitem>
+ </varlistentry>
+</variablelist>
+</sect4>
+
+<sect4 id="remotebackend-api-method-feedents"><title>Method: feedEnts</title>
+ <variablelist>
+  <varlistentry>
+   <term>Mandatory:</term>
+   <listitem><para>No</para></listitem>
+ </varlistentry>
+ <varlistentry>
+   <term>Parameters:</term>
+   <listitem><para>nonterm, trxid</para></listitem>
+ </varlistentry>
+ <varlistentry>
+   <term>Reply:</term>
+   <listitem><para>true for success, false for failure</para></listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Description</term>
+<listitem><para>
+This method is used by pdnssec rectify-zone to populate missing non-terminals. This is used when you have, say, record like _sip._upd.example.com, but no _udp.example.com. PowerDNS requires that there exists a non-terminal in between, and this instructs you to add one. If startTransaction is called, trxid identifies a transaction. 
+</para></listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Example JSON/RPC:</term>
+  <listitem>
+   <para>
+    Query:
+<programlisting>
+{"method":"feedEnts","parameters":{"domain_id":2,"trxid":1370416133,"nonterm":["_sip._udp","_udp"]}}
+</programlisting>
+   </para>
+   <para>
+    Response:
+<programlisting>
+{"result":true}
+</programlisting>
+   </para>
+  </listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Example HTTP/RPC:</term>
+  <listitem>
+   <para>
+     Query:
+<programlisting>
+POST /dnsapi/feedents/2
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 50
+
+trxid=1370416133&amp;nonterm[]=_udp&amp;nonterm[]=_sip.udp
+</programlisting>
+   </para>
+   <para>
+     Response:
+<programlisting>
+HTTP/1.1 200 OK
+Content-Type: text/javascript; charset=utf-8
+
+{"result":true}
+</programlisting>
+   </para>
+  </listitem>
+ </varlistentry>
+</variablelist>
+</sect4>
+
+<sect4 id="remotebackend-api-method-feedents3"><title>Method: feedEnts3</title>
+ <variablelist>
+  <varlistentry>
+   <term>Mandatory:</term>
+   <listitem><para>No</para></listitem>
+ </varlistentry>
+ <varlistentry>
+   <term>Parameters:</term>
+   <listitem><para>trxid, domain_id, domain, times, salt, narrow, nonterm</para></listitem>
+ </varlistentry>
+ <varlistentry>
+   <term>Reply:</term>
+   <listitem><para>true for success, false for failure</para></listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Description</term>
+<listitem><para>
+Same as <xref linkend="remotebackend-api-method-feedents" />, but provides NSEC3 hashing parameters. Note that salt is BYTE value, and can be non-readable text.
+</para></listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Example JSON/RPC:</term>
+  <listitem>
+   <para>
+    Query:
+<programlisting>
+{"method":"feedEnts3","parameters":{"domain_id":2,"domain":"example.com","times":1,"salt":"9642","narrow":false,"trxid":1370416356,"nonterm":["_sip._udp","_udp"]}}
+</programlisting>
+   </para>
+   <para>
+    Response:
+<programlisting>
+{"result":true}
+</programlisting>
+   </para>
+  </listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Example HTTP/RPC:</term>
+  <listitem>
+   <para>
+     Query:
+<programlisting>
+POST /dnsapi/2/example.com
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 78
+
+trxid=1370416356&amp;times=1&amp;salt=9642&amp;narrow=0&amp;nonterm[]=_sip._udp&amp;nonterm[]=_udp
+</programlisting>
+   </para>
+   <para>
+     Response:
+<programlisting>
+HTTP/1.1 200 OK
+Content-Type: text/javascript; charset=utf-8
+
+{"result":true}
+</programlisting>
+   </para>
+  </listitem>
+ </varlistentry>
+</variablelist>
+</sect4>
+
+<sect4 id="remotebackend-api-method-starttransaction"><title>Method: startTransaction</title>
+ <variablelist>
+  <varlistentry>
+   <term>Mandatory:</term>
+   <listitem><para>No</para></listitem>
+ </varlistentry>
+ <varlistentry>
+   <term>Parameters:</term>
+   <listitem><para>domain_id, domain, trxid</para></listitem>
+ </varlistentry>
+ <varlistentry>
+   <term>Reply:</term>
+   <listitem><para>true for success, false for failure</para></listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Description</term>
+<listitem><para>
+Starts a new transaction. Transaction ID is chosen for you. Used to identify f.ex. AXFR transfer. 
+</para></listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Example JSON/RPC:</term>
+  <listitem>
+   <para>
+    Query:
+<programlisting>
+{"method":"startTransaction","parameters":{"trxid":1234,"domain_id":1,"domain":"example.com"}}
+</programlisting>
+   </para>
+   <para>
+    Response:
+<programlisting>
+{"result":true}
+</programlisting>
+   </para>
+  </listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Example HTTP/RPC:</term>
+  <listitem>
+   <para>
+     Query:
+<programlisting>
+POST /dnsapi/starttransaction/1/example.com
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 10
+
+trxid=1234
+</programlisting>
+   </para>
+   <para>
+     Response:
+<programlisting>
+HTTP/1.1 200 OK
+Content-Type: text/javascript; charset=utf-8
+
+{"result":true}
+</programlisting>
+   </para>
+  </listitem>
+ </varlistentry>
+</variablelist>
+</sect4>
+
+<sect4 id="remotebackend-api-method-committransaction"><title>Method: commitTransaction</title>
+ <variablelist>
+  <varlistentry>
+   <term>Mandatory:</term>
+   <listitem><para>No</para></listitem>
+ </varlistentry>
+ <varlistentry>
+   <term>Parameters:</term>
+   <listitem><para>trxid</para></listitem>
+ </varlistentry>
+ <varlistentry>
+   <term>Reply:</term>
+   <listitem><para>true for success, false for failure</para></listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Description</term>
+<listitem><para>
+Signals successful transfer and asks to commit data into permanent storage.
+</para></listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Example JSON/RPC:</term>
+  <listitem>
+   <para>
+    Query:
+<programlisting>
+{"method":"commitTransaction","parameters":{"trxid":1234}}
+</programlisting>
+   </para>
+   <para>
+    Response:
+<programlisting>
+{"result":true}
+</programlisting>
+   </para>
+  </listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Example HTTP/RPC:</term>
+  <listitem>
+   <para>
+     Query:
+<programlisting>
+POST /dnsapi/committransaction/1234
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 0
+</programlisting>
+   </para>
+   <para>
+     Response:
+<programlisting>
+HTTP/1.1 200 OK
+Content-Type: text/javascript; charset=utf-8
+
+{"result":true}
+</programlisting>
+   </para>
+  </listitem>
+ </varlistentry>
+</variablelist>
+</sect4>
+
+<sect4 id="remotebackend-api-method-aborttransaction"><title>Method: abortTransaction</title>
+ <variablelist>
+  <varlistentry>
+   <term>Mandatory:</term>
+   <listitem><para>No</para></listitem>
+ </varlistentry>
+ <varlistentry>
+   <term>Parameters:</term>
+   <listitem><para>trxid</para></listitem>
+ </varlistentry>
+ <varlistentry>
+   <term>Reply:</term>
+   <listitem><para>true for success, false for failure</para></listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Description</term>
+<listitem><para>
+Signals failed transaction, and that you should rollback any changes. 
+</para></listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Example JSON/RPC:</term>
+  <listitem>
+   <para>
+    Query:
+<programlisting>
+{"method":"abortTransaction","parameters":{"trxid":1234}}
+</programlisting>
+   </para>
+   <para>
+    Response:
+<programlisting>
+{"result":true}
+</programlisting>
+   </para>
+  </listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Example HTTP/RPC:</term>
+  <listitem>
+   <para>
+     Query:
+<programlisting>
+POST /dnsapi/aborttransaction/1234
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 0
+</programlisting>
+   </para>
+   <para>
+     Response:
+<programlisting>
+HTTP/1.1 200 OK
+Content-Type: text/javascript; charset=utf-8
+
+{"result":true}
+</programlisting>
+   </para>
+  </listitem>
+ </varlistentry>
+</variablelist>
+</sect4>
+
+<sect4 id="remotebackend-api-method-calculatesoaserial"><title>Method: calculateSOASerial</title>
+ <variablelist>
+  <varlistentry>
+   <term>Mandatory:</term>
+   <listitem><para>No</para></listitem>
+ </varlistentry>
+ <varlistentry>
+   <term>Parameters:</term>
+   <listitem><para>domain,sd</para></listitem>
+ </varlistentry>
+ <varlistentry>
+   <term>Reply:</term>
+   <listitem><para>true for success, false for failure</para></listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Description</term>
+<listitem><para>
+Asks you to calculate a new serial based on the given data and update the serial. 
+</para></listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Example JSON/RPC:</term>
+  <listitem>
+   <para>
+    Query:
+<programlisting>{"method":"calculateSOASerial","parameters":{"domain":"unit.test","sd":{"qname":"unit.test","nameserver":"ns.unit.test","hostmaster":"hostmaster.unit.test","ttl":300,"serial":1,"refresh":2,"retry":3,"expire":4,"default_ttl":5,"domain_id":-1,"scopeMask":0}}}
+</programlisting>
+   </para>
+   <para>
+    Response:
+<programlisting>
+{"result":2013060501}
+</programlisting>
+   </para>
+  </listitem>
+ </varlistentry>
+ <varlistentry>
+  <term>Example HTTP/RPC:</term>
+  <listitem>
+   <para>
+     Query:
+<programlisting>
+POST /dnsapi/calculatesoaserial/unit.test
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 198
+
+sd[qname]=unit.test&amp;sd[nameserver]=ns.unit.test&amp;sd[hostmaster]=hostmaster.unit.test&amp;sd[ttl]=300&amp;sd[serial]=1&amp;sd[refresh]=2&amp;sd[retry]=3&amp;sd[expire]=4&amp;sd[default_ttl]=5&amp;sd[domain_id]=-1&amp;sd[scopemask]=0
+</programlisting>
+   </para>
+   <para>
+     Response:
+<programlisting>
+HTTP/1.1 200 OK
+Content-Type: text/javascript; charset=utf-8
+
+{"result":2013060501}
+</programlisting>
+   </para>
+  </listitem>
+ </varlistentry>
+</variablelist>
+</sect4>
+
 </sect3>
 </sect2>