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)
--- /dev/null
+remotebackend-access.log
+test_remotebackend_http
+test_remotebackend_pipe
+test_remotebackend_json
+test_remotebackend_post
#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)
#include <fcntl.h>
#include <boost/foreach.hpp>
#include <sstream>
+#include "rapidjson/stringbuffer.h"
+#include "rapidjson/writer.h"
#ifdef REMOTEBACKEND_HTTP
#include <curl/curl.h>
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() {
}
// 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 ¶meters, const char *element, std::stringstream& ss) {
}
}
+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 ¶meters, 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 ¶meters, struct curl_slist **slist)
{
std::stringstream ss;
std::string sparam;
// 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") {
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", ¶meters["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", ¶meters["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"];
// 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();
} 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);
!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;
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));
-#!/usr/bin/ruby
+#!/usr/bin/ruby1.9.1
require 'json'
require 'sqlite3'
@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
}
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
-#!/usr/bin/ruby
+#!/usr/bin/ruby1.9.1
require "rubygems"
#require "bundler/setup"
require "webrick"
-#!/usr/bin/ruby
+#!/usr/bin/ruby1.9.1
require 'json'
require '../modules/remotebackend/regression-tests/backend'
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() {
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);
}
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());
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) {
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) {
if (connector->send(query) == false || connector->recv(answer) == false)
return false;
- return answer["result"].GetBool();
+ return true;
}
bool RemoteBackend::doesDNSSEC() {
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 {
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 ¶meters, 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 ¶meters, struct curl_slist **slist);
+ void post_requestbuilder(const rapidjson::Document &input, struct curl_slist **slist);
void addUrlComponent(const rapidjson::Value ¶meters, const char *element, std::stringstream& ss);
};
#endif
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();
Connector *connector;
bool d_dnssec;
rapidjson::Document *d_result;
- int d_index;
+ int d_index;
+ int64_t d_trxid;
};
#endif
--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+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=") };
--- /dev/null
+#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 );
+
--- /dev/null
+#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
--- /dev/null
+#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();
--- /dev/null
+#!/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
--- /dev/null
+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
+
--- /dev/null
+#!/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
--- /dev/null
+#!/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
--- /dev/null
+#!/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
--- /dev/null
+#!/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
<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>
<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
</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>
</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
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>
<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>
<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>
Content-Type: application/x-www-form-urlencoded
Content-Length: 12
-value1=YES&
+value[]=YES&
</programlisting>
</para>
<para>
</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&nsset[1][qname]=example.com&nsset[1][qclass]=1&nsset[1][content]=ns1.example.com&nsset[1][ttl]=300&nsset[1][priority]=0&nsset[1][auth]=true&nsset[2][qtype]=NS&nsset[2][qname]=example.com&nsset[2][qclass]=1&nsset[2][content]=ns2.example.com&nsset[2][ttl]=300&nsset[2][priority]=0&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&rrset[qtype]=A&rrset[qname]=replace.example.com&rrset[qclass]=1&rrset[content]=1.1.1.1&rrset[priority]=0&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&rr[qname]=replace.example.com&rr[qclass]=1&rr[content]=127.0.0.1&rr[ttl]=300&rr[priority]=0&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&nonterm[]=_udp&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&times=1&salt=9642&narrow=0&nonterm[]=_sip._udp&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&sd[nameserver]=ns.unit.test&sd[hostmaster]=hostmaster.unit.test&sd[ttl]=300&sd[serial]=1&sd[refresh]=2&sd[retry]=3&sd[expire]=4&sd[default_ttl]=5&sd[domain_id]=-1&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>