modules/gpgsqlbackend/Makefile modules/ldapbackend/Makefile \
modules/gsqlitebackend/Makefile modules/gsqlite3backend/Makefile \
modules/goraclebackend/Makefile modules/mydnsbackend/Makefile \
-modules/luabackend/Makefile modules/tinydnsbackend/Makefile)
+modules/luabackend/Makefile modules/tinydnsbackend/Makefile \
+modules/remotebackend/Makefile)
SUBDIRS=@moduledirs@
-DIST_SUBDIRS=db2backend geobackend gmysqlbackend godbcbackend goraclebackend gpgsqlbackend gsqlite3backend gsqlitebackend ldapbackend luabackend mongodbbackend mydnsbackend opendbxbackend oraclebackend pipebackend xdbbackend tinydnsbackend
+DIST_SUBDIRS=db2backend geobackend gmysqlbackend godbcbackend goraclebackend gpgsqlbackend gsqlite3backend gsqlitebackend ldapbackend luabackend mongodbbackend mydnsbackend opendbxbackend oraclebackend pipebackend xdbbackend tinydnsbackend remotebackend
--- /dev/null
+AM_CPPFLAGS=@THREADFLAGS@ $(BOOST_CPPFLAGS)
+#if !ALLSTATIC
+#install-exec-local:
+# install .lib/libremotebackend.so.0.0.0 @libdir@
+#endif
+
+EXTRA_DIST=OBJECTFILES OBJECTLIBS
+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=-lboost_system -ljsoncpp -lcurl
--- /dev/null
+remotebackend.o unixconnector.o httpconnector.o pipeconnector.o
--- /dev/null
+-lboost_system -ljsoncpp -lcurl
--- /dev/null
+Remote backend for PowerDNS.
+
+This backend provides unix socket / pipe / http remoting for powerdns.
+
+NB! THIS BACKEND IS EXPERIMENTAL - DO NOT USE IN PRODUCTION ENVIRONMENT
+
+This backend is provided under the same licensing terms as PowerDNS itself.
+
+1. Compiling
+
+Install following libraries for dependencies: libjsoncpp, libcurl
+
+To compile this backend, you need to configure --with-modules="remote pipe".
+
+Also, you need to apply the patch 0001-Added-remotebackend-for-compile.patch
+to enable compiling the backend. Then you need to run ./bootstrap and
+configure.
+
+2. Usage
+
+The only configuration option for this backend is remote-connection-string.
+
+It comprises of two elements: type of backend, and parameters
+
+remote-connection-string=<type>:<param>=<value>,<param>=<value>...
+
+You can pass as many parameters as you want, for unix and pipe backends, these
+are passed along to the remote end as initialization. See API.
+
+2.1. Unix backend
+
+parameters: path
+
+remote-connection-string=unix:path=/path/to/socket
+
+2.2. Pipe backend
+
+parameters: command
+
+remote-connection-string=unix:command=/path/to/executable
+
+2.3. HTTP backend
+
+parameters: url, url-suffix
+
+HTTP backend tries to do RESTful requests to your server. See examples.
+
+URL should not end with /, and url-suffix is optional, but if you define it, it's
+up to you to write the ".php" or ".json". Lack of dot causes lack of dot in
+URL.
+
+3. API
+
+3.1. Queries
+
+Unix and Pipe backend sends JSON formatted string to the remote end. Each
+JSON query has two sections, 'method' and 'parameters'.
+
+HTTP backend calls methods based on URL and has parameters in the query string.
+The calls are always GET calls.
+
+3.2. Replies
+
+You *must* always reply with JSON hash with at least one key, 'result'. This
+must be false if the query failed. Otherwise it must conform to the expected
+result.
+
+You can optionally add 'log' array, each line in this array will be logged in
+PowerDNS.
+
+3.3. Methods
+
+The following methods are used:
+
+Method: lookup
+Parameters: qtype, domain, remote, local, real-remote, zone_id
+Reply: array of <qtype,qname,content,ttl,domaind_id,priority,scopeMask>
+Optional values: domain_id and scopeMask
+
+Method: list
+Parameters: zonename, domain_id
+Reply: array of <qtype,qname,content,ttl,domaind_id,priority,scopeMask>
+Optional values: domain_id and scopeMask
+
+Method: getBeforeAndAfterNamesAbsolute
+Parameters: id, qname
+Reply: unhashed, before, after
+
+Method: getBeforeAndAfterNames
+Parameters: id, zonename, qname
+Reply: before, after
+
+Method: getDomainMetadata
+Parameters: name, kind
+Reply: array of strings
+
+Method: getDomainKeys
+Parameters: name, kind
+Reply: array of domain keys <id,flags,active,content>
+
+Method: getTSIGKey
+Parameters: name
+Reply: algorithm, content
+
+Method: setDomainMetadata
+Parameters: name, kind, value
+Reply: true or false
+
+Method: addDomainKey
+Parameters: flags, active, content
+Reply: id-of-key
+
+Method: remoteDomainKey
+Parameters: name, id
+Reply: true or false
+
+Method: activateDomainKey
+Parameters: name, id
+Reply: true or false
+
+Method: deactivateDomainKey
+Parameters: name, id
+Reply: true or false
+
+4. EXAMPLES
+
+Scenario: SOA lookup via pipe or unix
+
+Query:
+
+{
+ "method": "lookup",
+ "parameters": {
+ "qname": "example.com",
+ "qtype": "SOA",
+ "zone_id": "-1"
+ }
+}
+
+Reply:
+
+{
+ "result":
+ [
+ { "qtype": "SOA",
+ "qname": "example.com",
+ "content": "dns1.icann.org. hostmaster.icann.org. 2012080849 7200 3600 1209600 3600",
+ "ttl": 3600,
+ "priority": 0,
+ "domain_id": -1
+ }
+ ]
+}
+
+
+Scenario: SOA lookup via HTTP backend
+
+Query:
+
+/dns/lookup/example.com/SOA
+
+Reply:
+
+{
+ "result":
+ [
+ { "qtype": "SOA",
+ "qname": "example.com",
+ "content": "dns1.icann.org. hostmaster.icann.org. 2012080849 7200 3600 1209600 3600",
+ "ttl": 3600,
+ "priority": 0,
+ "domain_id": -1
+ }
+ ]
+}
+
+5. TODO
+
+ - Improve error handling and reply validation
+ - Code coverage
+
--- /dev/null
+- detect jsoncpp and libcurl in configure
+- move coprocess from pipebackend to core so remotebackend can run without pipebackend
+- make ruby scripts compatible with 1.8
--- /dev/null
+#include "remotebackend.hh"
+#include <sys/socket.h>
+#include <unistd.h>
+#include <sys/select.h>
+#include <fcntl.h>
+#include <curl/curl.h>
+#include <boost/foreach.hpp>
+#include <sstream>
+#ifndef UNIX_PATH_MAX
+#define UNIX_PATH_MAX 108
+#endif
+
+HTTPConnector::HTTPConnector(std::map<std::string,std::string> options) {
+ this->d_url = options.find("url")->second;
+ if (options.find("url-suffix") != options.end()) {
+ this->d_url_suffix = options.find("url-suffix")->second;
+ } else {
+ this->d_url_suffix = "";
+ }
+}
+
+HTTPConnector::~HTTPConnector() {
+ this->d_c = NULL;
+}
+
+// friend method for writing data into our buffer
+size_t httpconnector_write_data(void *buffer, size_t size, size_t nmemb, void *userp) {
+ HTTPConnector *tc = reinterpret_cast<HTTPConnector*>(userp);
+ std::string tmp(reinterpret_cast<char *>(buffer), size*nmemb);
+ tc->d_data += tmp;
+ return nmemb;
+}
+
+// converts json value into string
+void HTTPConnector::json2string(const Json::Value &input, std::string &output) {
+ if (input.isString()) output = input.asString();
+ else if (input.isNull()) output = "";
+ else if (input.isUInt()) output = lexical_cast<std::string>(input.asUInt());
+ else if (input.isInt()) output = lexical_cast<std::string>(input.asInt());
+ else output = "inconvertible value";
+}
+
+// builds our request
+void HTTPConnector::requestbuilder(const std::string &method, const Json::Value ¶meters, struct curl_slist **slist)
+{
+ std::stringstream ss;
+ Json::Value param;
+ std::string sparam;
+ char *tmpstr;
+
+ // check for certain elements
+ std::vector<std::string> members = parameters.getMemberNames();
+
+ // special names are qname, name, zonename, kind, others go to headers
+
+ ss << d_url;
+
+ ss << "/" << method;
+
+ // add the url components, if found, in following order
+
+ if ((param = parameters.get("zonename", Json::Value())).isNull() == false) {
+ json2string(param, sparam);
+ ss << "/" << sparam;
+ }
+
+ if ((param = parameters.get("qname", Json::Value())).isNull() == false) {
+ json2string(param, sparam);
+ ss << "/" << sparam;
+ }
+
+ if ((param = parameters.get("name", Json::Value())).isNull() == false) {
+ json2string(param, sparam);
+ ss << "/" << sparam;
+ }
+
+ if ((param = parameters.get("kind", Json::Value())).isNull() == false) {
+ json2string(param, sparam);
+ ss << "/" << sparam;
+ }
+ if ((param = parameters.get("qtype", Json::Value())).isNull() == false) {
+ json2string(param, sparam);
+ ss << "/" << sparam;
+ }
+
+ if ((param = parameters.get("id", Json::Value())).isNull() == false) {
+ json2string(param, sparam);
+ ss << "/" << sparam;
+ }
+
+ // 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") {
+ // create an empty post
+ curl_easy_setopt(d_c, CURLOPT_POST, 1);
+ curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, 0);
+ } else if (method == "addDomainKey") {
+ // create post with keydata
+ std::stringstream ss2;
+ param = parameters["key"];
+ ss2 << "flags" << param["flags"].asUInt() << "&active" << (param["active"].asBool() ? 1 : 0) << "&content=";
+ tmpstr = curl_easy_escape(d_c, param["content"].asCString(), 0);
+ ss2 << tmpstr;
+ sparam = ss2.str();
+ curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, sparam.size());
+ curl_easy_setopt(d_c, CURLOPT_COPYPOSTFIELDS, sparam.c_str());
+ curl_free(tmpstr);
+ } else if (method == "setDomainMetadata") {
+ // copy all metadata values into post
+ std::stringstream ss2;
+ param = parameters["value"];
+ curl_easy_setopt(d_c, CURLOPT_POST, 1);
+ // this one has values too
+ if (param.isArray()) {
+ for(Json::ValueIterator i = param.begin(); i != param.end(); i++) {
+ ss2 << "value[]=" << (*i).asString() << "&";
+ }
+ }
+ sparam = ss2.str();
+ curl_easy_setopt(d_c, CURLOPT_POSTFIELDSIZE, sparam.size());
+ curl_easy_setopt(d_c, CURLOPT_COPYPOSTFIELDS, sparam.c_str());
+ } else if (method == "removeDomainKey") {
+ // this one is delete
+ curl_easy_setopt(d_c, CURLOPT_CUSTOMREQUEST, "DELETE");
+ } else {
+ // perform normal get
+ curl_easy_setopt(d_c, CURLOPT_HTTPGET, 1);
+ }
+
+ // put everything else into headers
+ BOOST_FOREACH(std::string member, members) {
+ char header[1024];
+ // these are not put into headers for obvious reasons
+ if (member == "zonename" || member == "qname" ||
+ member == "name" || member == "kind" ||
+ member == "qtype" || member == "id" ||
+ member == "key" ) continue;
+ json2string(parameters[member], sparam);
+ snprintf(header, sizeof header, "X-RemoteBackend-%s: %s", member.c_str(), sparam.c_str());
+ (*slist) = curl_slist_append((*slist), header);
+ };
+
+ // store headers into request
+ curl_easy_setopt(d_c, CURLOPT_HTTPHEADER, *slist);
+}
+
+int HTTPConnector::send_message(const Json::Value &input) {
+ int rv;
+ long rcode;
+ struct curl_slist *slist;
+
+ std::vector<std::string> members;
+ std::string method;
+
+ // initialize curl
+ d_c = curl_easy_init();
+ d_data = "";
+ curl_easy_setopt(d_c, CURLOPT_NOSIGNAL, 1);
+ curl_easy_setopt(d_c, CURLOPT_TIMEOUT, 2);
+ slist = NULL;
+
+ // build request
+ requestbuilder(input["method"].asString(), input["parameters"], &slist);
+
+ // setup write function helper
+ curl_easy_setopt(d_c, CURLOPT_WRITEFUNCTION, &(httpconnector_write_data));
+ curl_easy_setopt(d_c, CURLOPT_WRITEDATA, this);
+
+ // then we actually do it
+ if (curl_easy_perform(d_c) != CURLE_OK) {
+ // boo, it failed
+ rv = -1;
+ } else {
+ // ensure the result was OK
+ if (curl_easy_getinfo(d_c, CURLINFO_RESPONSE_CODE, &rcode) != CURLE_OK || rcode < 200 || rcode > 299) {
+ rv = -1;
+ } else {
+ // ok. if d_data == 0 but rcode is 2xx then result:true
+ if (this->d_data.size() == 0)
+ this->d_data = "{\"result\": true}";
+ rv = this->d_data.size();
+ }
+ }
+
+ // clean up resources
+ curl_slist_free_all(slist);
+ curl_easy_cleanup(d_c);
+
+ return rv;
+}
+
+int HTTPConnector::recv_message(Json::Value &output) {
+ Json::Reader r;
+ int rv = -1;
+
+ // offer whatever we read in send_message
+ if (r.parse(d_data, output) == true)
+ rv = d_data.size();
+
+ d_data = ""; // cleanup here
+ return rv;
+}
--- /dev/null
+#include "remotebackend.hh"
+
+PipeConnector::PipeConnector(std::map<std::string,std::string> options) {
+ this->command = options.find("command")->second;
+ this->options = options;
+ this->coproc = NULL;
+ launch();
+}
+
+PipeConnector::~PipeConnector(){
+ if (this->coproc != NULL)
+ delete coproc;
+}
+
+void PipeConnector::launch() {
+ if (coproc != NULL) return;
+
+ Json::Value init,res;
+ coproc = new CoProcess(this->command, 2);
+ init["method"] = "initialize";
+ init["parameters"] = Json::Value();
+
+ for(std::map<std::string,std::string>::iterator i = options.begin(); i != options.end(); i++)
+ init["parameters"][i->first] = i->second;
+
+ this->send(init);
+ if (this->recv(res)==false) {
+ L<<Logger::Error<<"Failed to initialize coprocess"<<std::endl;
+ }
+}
+
+int PipeConnector::send_message(const Json::Value &input)
+{
+ std::string data;
+ Json::FastWriter writer;
+ data = writer.write(input);
+
+ launch();
+ try {
+ coproc->send(data);
+ return 1;
+ }
+ catch(AhuException &ae) {
+ delete coproc;
+ coproc=NULL;
+ throw;
+ }
+}
+
+int PipeConnector::recv_message(Json::Value &output)
+{
+ Json::Reader r;
+ std::string tmp;
+ std::string data;
+ launch();
+ try {
+ std::string line;
+ while(1) {
+ coproc->receive(tmp);
+ data.append(tmp);
+ if (r.parse(data,output) == true)
+ return data.size();
+ }
+ }
+ catch(AhuException &ae) {
+ L<<Logger::Warning<<"[pipeconnector] "<<" unable to receive data from coprocess. "<<ae.reason<<endl;
+ delete coproc;
+ coproc = NULL;
+ throw;
+ }
+}
--- /dev/null
+source 'https://rubygems.org'
+
+gem 'webrick'
+gem 'sqlite3'
--- /dev/null
+GEM
+ remote: https://rubygems.org/
+ specs:
+ sqlite3 (1.3.6)
+ webrick (1.3.1)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ sqlite3
+ webrick
--- /dev/null
+#!/usr/bin/ruby1.9.1
+
+require 'json'
+require 'sqlite3'
+
+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
+
+class Handler
+ attr :db
+
+ def initialize(dbpath)
+ @db = SQLite3::Database.new dbpath
+ end
+
+ def do_initialize(*args)
+ return true, "Test bench initialized"
+ end
+
+ def do_getbeforeandafternamesabsolute(args)
+ before = @db.get_first_value("SELECT ordername FROM records WHERE ordername < ? AND domain_id = ?", args["qname"], args["id"])
+ after = @db.get_first_value("SELECT ordername FROM records WHERE ordername > ? AND domain_id = ?", args["qname"], args["id"])
+ return [{:before => before, :after => after, :unhashed => args["qname"]}, nil]
+ end
+
+ def do_getbeforeandafternames(args)
+ before = @db.get_first_value("SELECT ordername FROM records WHERE ordername < ? AND domain_id = ?", args["qname"], args["id"])
+ after = @db.get_first_value("SELECT ordername FROM records WHERE ordername > ? AND domain_id = ?", args["qname"], args["id"])
+ return [{:before => before, :after => after, :unhashed => args["qname"]}, nil]
+ end
+
+ def do_getdomainkeys(args)
+ ret = []
+ @db.execute("SELECT flags,active,content FROM domains JOIN cryptokeys ON domains.id = cryptokeys.domain_id WHERE domains.name = ?", args["name"]) do |row|
+ ret << {:flags => row[0].to_i, :active => !(row[1].to_i.zero?), :content => row[2]}
+ end
+
+ return false if ret.empty?
+ return [ret,nil]
+ end
+
+ def do_lookup(args)
+ ret = []
+ loop do
+ begin
+ sargs = {}
+ if (args["qtype"] == "ANY")
+ sql = "SELECT domain_id,name,type,content,ttl,prio,auth FROM records WHERE name = :qname"
+ sargs["qname"] = args["qname"]
+ else
+ sql = "SELECT domain_id,name,type,content,ttl,prio,auth FROM records WHERE name = :qname AND type = :qtype"
+ sargs["qname"] = args["qname"]
+ sargs["qtype"] = args["qtype"]
+ end
+ db.execute(sql, sargs) do |row|
+ ret << rr(row[1], row[2], row[3], row[4], row[5], row[6], row[0])
+ end
+ rescue Exception => e
+ e.backtrace
+ end
+ break
+ end
+ return false unless ret.size > 0
+ return [ret,nil]
+ end
+
+ def do_list(args)
+ target = args["target"]
+ ret = []
+ loop do
+ begin
+ d_id = db.get_first_value("SELECT id FROM domains WHERE name = ?", target)
+ return false if d_id.nil?
+ db.execute("SELECT domain_id,name,type,content,ttl,prio,auth FROM records WHERE domain_id = ?", d_id) do |row|
+ ret << rr(row[1], row[2], row[3], row[4], row[5], row[6], row[0])
+ end
+ rescue Exception => e
+ e.backtrace
+ end
+ break
+ end
+ return false unless ret.size > 0
+ return [ret,nil]
+ end
+
+ def do_getdomainmetadata(args)
+ return false
+ end
+
+ def do_setdomainmetadata(args)
+ return false
+ end
+end
--- /dev/null
+require 'json'
+
+class DNSBackendHandler < WEBrick::HTTPServlet::AbstractServlet
+ def initialize(server, dnsbackend)
+ @dnsbackend = dnsbackend
+ end
+
+ def do_GET(req,res)
+ tmp = req.path[/dns\/(.*)/,1]
+ return 400, "Bad request" if (tmp.nil?)
+ tmp = tmp.split("/")
+ method = "do_#{tmp.shift}".downcase
+ args = {}
+
+ if tmp.size > 0
+ args["qname"] = tmp[0]
+ args["name"] = tmp[0]
+ args["target"] = tmp.shift
+ end
+ if tmp.size > 0
+ args["kind"] = tmp[0]
+ args["qtype"] = tmp[0]
+ args["id"] = tmp.shift
+ end
+
+ # get more arguments
+ req.each do |k,v|
+ attr = k[/X-RemoteBackend-(.*)/,1]
+ if attr
+ args[attr] = v
+ end
+ end
+
+ 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
+
+ def do_DELETE(req,res)
+ end
+
+ def do_POST(req,res)
+ req.continue
+
+ # get method name and args
+ end
+end
--- /dev/null
+#!/usr/bin/ruby1.9.1
+require "rubygems"
+#require "bundler/setup"
+require "webrick"
+require "../modules/remotebackend/regression-tests/dnsbackend"
+require "../modules/remotebackend/regression-tests/backend"
+
+server = WEBrick::HTTPServer.new :Port => 62434
+
+be = Handler.new("../modules/remotebackend/regression-tests/remote.sqlite3")
+
+server.mount "/dns", DNSBackendHandler, be
+trap('INT') { server.stop }
+server.start
--- /dev/null
+#!/usr/bin/ruby1.9.1
+
+require 'json'
+require '../modules/remotebackend/regression-tests/backend'
+
+h = Handler.new("../modules/remotebackend/regression-tests/remote.sqlite3")
+
+STDOUT.sync = true
+begin
+ STDIN.each_line do |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
+ rescue JSON::ParserError
+ send_failure "Cannot parse input #{line}"
+ next
+ end
+ end
+rescue SystemExit, Interrupt
+end
--- /dev/null
+begin transaction;
+
+create table domains (
+ id INTEGER PRIMARY KEY,
+ name VARCHAR(255) NOT NULL COLLATE NOCASE,
+ master VARCHAR(128) DEFAULT NULL,
+ last_check INTEGER DEFAULT NULL,
+ type VARCHAR(6) NOT NULL,
+ notified_serial INTEGER DEFAULT NULL,
+ account VARCHAR(40) DEFAULT NULL
+);
+
+CREATE UNIQUE INDEX name_index ON domains(name);
+
+CREATE TABLE records (
+ id INTEGER PRIMARY KEY,
+ domain_id INTEGER DEFAULT NULL,
+ name VARCHAR(255) DEFAULT NULL,
+ type VARCHAR(10) DEFAULT NULL,
+ content VARCHAR(65535) DEFAULT NULL,
+ ttl INTEGER DEFAULT NULL,
+ prio INTEGER DEFAULT NULL,
+ change_date INTEGER DEFAULT NULL
+);
+
+CREATE INDEX rec_name_index ON records(name);
+CREATE INDEX nametype_index ON records(name,type);
+CREATE INDEX domain_id ON records(domain_id);
+
+create table supermasters (
+ ip VARCHAR(25) NOT NULL,
+ nameserver VARCHAR(255) NOT NULL COLLATE NOCASE,
+ account VARCHAR(40) DEFAULT NULL
+);
+
+alter table records add ordername VARCHAR(255);
+alter table records add auth bool;
+create index orderindex on records(ordername);
+
+create table domainmetadata (
+ id INTEGER PRIMARY KEY,
+ domain_id INT NOT NULL,
+ kind VARCHAR(16) COLLATE NOCASE,
+ content TEXT
+);
+
+create index domainmetaidindex on domainmetadata(domain_id);
+
+create table cryptokeys (
+ id INTEGER PRIMARY KEY,
+ domain_id INT NOT NULL,
+ flags INT NOT NULL,
+ active BOOL,
+ content TEXT
+);
+
+create index domainidindex on cryptokeys(domain_id);
+
+create table tsigkeys (
+ id INTEGER PRIMARY KEY,
+ name VARCHAR(255) COLLATE NOCASE,
+ algorithm VARCHAR(50) COLLATE NOCASE,
+ secret VARCHAR(255)
+);
+
+create unique index namealgoindex on tsigkeys(name, algorithm);
+insert into domains (name,type) VALUES('delegated.dnssec-parent.com','NATIVE');
+insert into domains (name,type) VALUES('dnssec-parent.com','NATIVE');
+insert into domains (name,type) VALUES('example.com','NATIVE');
+insert into domains (name,type) VALUES('minimal.com','NATIVE');
+insert into domains (name,type) VALUES('test.com','NATIVE');
+insert into domains (name,type) VALUES('wtest.com','NATIVE');
+
+commit;
--- /dev/null
+pipe-backend.rb
\ No newline at end of file
--- /dev/null
+#include "remotebackend.hh"
+#include <boost/foreach.hpp>
+
+static const char *kBackendId = "[RemoteBackend]";
+
+/**
+ * Forwarder for value. This is just in case
+ * we need to do some treatment to the value before
+ * sending it downwards.
+ */
+bool Connector::send(Json::Value &value) {
+ return send_message(value);
+}
+
+/**
+ * Helper for handling receiving of data.
+ * Basically what happens here is that we check
+ * that the receiving happened ok, and extract
+ * result. Logging is performed here, too.
+ */
+bool Connector::recv(Json::Value &value) {
+ Json::Value input;
+ if (recv_message(input)>0) {
+ bool rv = true;
+ // check for error
+ value = input.get("result",Json::Value());
+ if (value.isNull() || (value.isBool() && value.asBool() == false)) {
+ rv = false;
+ value = Json::Value(false);
+ }
+ Json::Value messages = input.get("log", Json::Value());
+ if (messages.isArray()) {
+ // log em all
+ for(Json::ValueIterator iter = messages.begin(); iter != messages.end(); iter++) {
+ L<<Logger::Info<<"[remotebackend]:"<< (*iter).asString()<<std::endl;
+ }
+ }
+ return rv;
+ }
+ return false;
+}
+
+RemoteBackend::RemoteBackend(const std::string &suffix)
+{
+ setArgPrefix("remote"+suffix);
+ build(getArg("connection-string"));
+ this->d_dnssec = mustDo("dnssec");
+ this->d_index = -1;
+}
+
+RemoteBackend::~RemoteBackend() {
+ if (connector != NULL) {
+ delete connector;
+ }
+}
+
+/**
+ * Builds connector based on options
+ * Currently supports unix,pipe and http
+ */
+int RemoteBackend::build(const std::string &connstr) {
+ std::vector<std::string> parts;
+ std::string type;
+ std::string opts;
+ std::map<std::string, std::string> options;
+
+ // connstr is of format "type:options"
+ size_t pos;
+ pos = connstr.find_first_of(":");
+ if (pos == std::string::npos)
+ throw new AhuException("Invalid connection string: malformed");
+
+ type = connstr.substr(0, pos);
+ opts = connstr.substr(pos+1);
+
+ // tokenize the string on comma
+ stringtok(parts, opts, ",");
+
+ // find out some options and parse them while we're at it
+ BOOST_FOREACH(std::string opt, parts) {
+ std::string key,val;
+ // make sure there is something else than air in the option...
+ if (opt.find_first_not_of(" ") == std::string::npos) continue;
+
+ // split it on '='. if not found, we treat it as "yes"
+ pos = opt.find_first_of("=");
+
+ if (pos == std::string::npos) {
+ key = opt;
+ val = "yes";
+ } else {
+ key = opt.substr(0,pos);
+ val = opt.substr(pos+1);
+ }
+ options[key] = val;
+ }
+
+ // connectors know what they are doing
+ if (type == "http") {
+ this->connector = new HTTPConnector(options);
+ } else if (type == "unix") {
+ this->connector = new UnixsocketConnector(options);
+ } else if (type == "pipe") {
+ this->connector = new PipeConnector(options);
+ } else {
+ throw new AhuException("Invalid connection string: unknown connector");
+ }
+
+ return -1;
+}
+
+/**
+ * The functions here are just remote json stubs that send and receive the method call
+ * data is mainly left alone, some defaults are assumed.
+ */
+void RemoteBackend::lookup(const QType &qtype, const std::string &qdomain, DNSPacket *pkt_p, int zoneId) {
+ Json::Value query,args;
+
+ if (d_index != -1)
+ throw AhuException("Attempt to lookup while one running");
+
+ args["qtype"] = qtype.getName();
+ args["qname"] = qdomain;
+ if (pkt_p != NULL) {
+ args["remote"] = pkt_p->getRemote();
+ args["local"] = pkt_p->getLocal();
+ args["real-remote"] = pkt_p->getRealRemote().toString();
+ }
+ args["zone-id"] = zoneId;
+ query["method"] = "lookup";
+ query["parameters"] = args;
+
+ if (connector->send(query) == false || connector->recv(d_result) == false) return;
+
+ // OK. we have result values in result
+ if (d_result.isArray() == false) return;
+ d_index = 0;
+}
+
+bool RemoteBackend::get(DNSResourceRecord &rr) {
+ if (d_index == -1) return false;
+
+ Json::Value empty("");
+ Json::Value emptyint(-1);
+
+ rr.qtype = d_result[d_index].get("qtype",empty).asString();
+ rr.qname = d_result[d_index].get("qname",empty).asString();
+ rr.qclass = QClass::IN;
+ rr.content = d_result[d_index].get("content",empty).asString();
+ rr.ttl = d_result[d_index].get("ttl",emptyint).asInt();
+ rr.domain_id = d_result[d_index].get("domain_id",emptyint).asInt();
+ rr.priority = d_result[d_index].get("priority",emptyint).asInt();
+ if (d_dnssec)
+ rr.auth = d_result[d_index].get("auth", Json::Value(1)).asInt();
+ else
+ rr.auth = 1;
+ rr.scopeMask = d_result[d_index].get("scopeMask",Json::Value(0)).asInt();
+
+ d_index++;
+
+ // id index is out of bounds, we know the results end here.
+ if (d_index == static_cast<int>(d_result.size())) {
+ d_result = Json::Value();
+ d_index = -1;
+ }
+ return true;
+}
+
+bool RemoteBackend::list(const std::string &target, int domain_id) {
+ Json::Value query;
+
+ if (d_index != -1)
+ throw AhuException("Attempt to lookup while one running");
+
+ query["method"] = "list";
+ query["parameters"] = Json::Value();
+ query["parameters"]["zonename"] = target;
+ query["parameters"]["domain-id"] = domain_id;
+
+ if (connector->send(query) == false || connector->recv(d_result) == false)
+ return false;
+ d_index = 0;
+
+ return true;
+}
+
+bool RemoteBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const std::string& qname, std::string& unhashed, std::string& before, std::string& after) {
+ Json::Value query,answer;
+ // no point doing dnssec if it's not supported
+ if (d_dnssec == false) return false;
+
+ query["method"] = "getBeforeAndAfterNamesAbsolute";
+ query["parameters"] = Json::Value();
+ query["parameters"]["id"] = id;
+ query["parameters"]["qname"] = qname;
+
+ if (connector->send(query) == false || connector->recv(answer) == false)
+ return false;
+
+ unhashed = answer["unhashed"].asString();
+ before = answer["before"].asString();
+ after = answer["after"].asString();
+
+ return true;
+}
+
+bool RemoteBackend::getBeforeAndAfterNames(uint32_t id, const std::string& zonename, const std::string& qname, std::string& before, std::string& after) {
+ Json::Value query,answer;
+
+ // no point doing dnssec if it's not supported
+ if (d_dnssec == false) return false;
+
+ query["method"] = "getBeforeAndAfterNames";
+ query["parameters"] = Json::Value();
+ query["parameters"]["id"] = id;
+ query["parameters"]["zonename"] = zonename;
+ query["parameters"]["qname"] = qname;
+
+ if (connector->send(query) == false || connector->recv(answer) == false)
+ return false;
+
+ before = answer["before"].asString();
+ after = answer["after"].asString();
+
+ return true;
+}
+
+bool RemoteBackend::getDomainMetadata(const std::string& name, const std::string& kind, std::vector<std::string>& meta) {
+ Json::Value query,answer;
+
+ query["method"] = "getDomainMetadata";
+ query["parameters"] = Json::Value();
+ query["parameters"]["name"] = name;
+ query["parameters"]["kind"] = kind;
+ if (connector->send(query) == false || connector->recv(answer) == false)
+ return false;
+
+ meta.clear();
+
+ for(Json::ValueIterator iter = answer.begin(); iter != answer.end(); iter++) {
+ meta.push_back((*iter).asString());
+ }
+
+ return true;
+}
+
+bool RemoteBackend::setDomainMetadata(const string& name, const std::string& kind, const std::vector<std::string>& meta) {
+ Json::Value query,answer;
+ query["method"] = "setDomainMetadata";
+ query["parameters"] = Json::Value();
+ query["parameters"]["name"] = name;
+ query["parameters"]["kind"] = kind;
+ query["parameters"]["value"] = Json::Value();
+ BOOST_FOREACH(std::string value, meta) {
+ query["parameters"]["value"].append(value);
+ }
+
+ if (connector->send(query) == false || connector->recv(answer) == false)
+ return false;
+
+ return answer.asBool();
+}
+
+
+bool RemoteBackend::getDomainKeys(const std::string& name, unsigned int kind, std::vector<DNSBackend::KeyData>& keys) {
+ Json::Value query,answer;
+ // no point doing dnssec if it's not supported
+ if (d_dnssec == false) return false;
+
+ query["method"] = "getDomainKeys";
+ query["parameters"] = Json::Value();
+ query["parameters"]["name"] = name;
+ query["parameters"]["kind"] = kind;
+
+ if (connector->send(query) == false || connector->recv(answer) == false)
+ return false;
+
+ keys.clear();
+
+ for(Json::ValueIterator iter = answer.begin(); iter != answer.end(); iter++) {
+ DNSBackend::KeyData key;
+ key.id = (*iter)["id"].asUInt();
+ key.flags = (*iter)["flags"].asUInt();
+ key.active = (*iter)["active"].asBool();
+ key.content = (*iter)["content"].asString();
+ keys.push_back(key);
+ }
+
+ return true;
+}
+
+bool RemoteBackend::removeDomainKey(const string& name, unsigned int id) {
+ Json::Value query,answer;
+ // no point doing dnssec if it's not supported
+ if (d_dnssec == false) return false;
+ query["method"] = "remoteDomainKey";
+ query["parameters"] = Json::Value();
+ query["parameters"]["name"] = name;
+ query["parameters"]["id"] = id;
+
+ if (connector->send(query) == false || connector->recv(answer) == false)
+ return false;
+
+ return answer.asBool();
+}
+
+int RemoteBackend::addDomainKey(const string& name, const KeyData& key) {
+ Json::Value query,answer;
+
+ // no point doing dnssec if it's not supported
+ if (d_dnssec == false) return false;
+ query["method"] = "addDomainKey";
+ query["parameters"] = Json::Value();
+ query["parameters"]["name"] = name;
+ query["parameters"]["key"] = Json::Value();
+ query["parameters"]["key"]["flags"] = key.flags;
+ query["parameters"]["key"]["active"] = key.active;
+ query["parameters"]["key"]["content"] = key.content;
+
+ if (connector->send(query) == false || connector->recv(answer) == false)
+ return false;
+
+ return answer.asInt();
+}
+
+bool RemoteBackend::activateDomainKey(const string& name, unsigned int id) {
+ Json::Value query,answer;
+ // no point doing dnssec if it's not supported
+ if (d_dnssec == false) return false;
+ query["method"] = "activateDomainKey";
+ query["parameters"] = Json::Value();
+ query["parameters"]["name"] = name;
+ query["parameters"]["id"] = id;
+
+ if (connector->send(query) == false || connector->recv(answer) == false)
+ return false;
+
+ return answer.asBool();
+}
+
+bool RemoteBackend::deactivateDomainKey(const string& name, unsigned int id) {
+ Json::Value query,answer;
+ // no point doing dnssec if it's not supported
+ if (d_dnssec == false) return false;
+ query["method"] = "deactivateDomainKey";
+ query["parameters"] = Json::Value();
+ query["parameters"]["name"] = name;
+ query["parameters"]["id"] = id;
+ if (connector->send(query) == false || connector->recv(answer) == false)
+ return false;
+
+ return answer.asBool();
+}
+
+bool RemoteBackend::getTSIGKey(const std::string& name, std::string* algorithm, std::string* content) {
+ Json::Value query,answer;
+ query["method"] = "getTSIGKey";
+ query["parameters"] = Json::Value();
+ query["parameters"]["name"] = name;
+
+ if (connector->send(query) == false || connector->recv(answer) == false)
+ return false;
+
+ if (algorithm != NULL)
+ algorithm->assign(answer["algorithm"].asString());
+ if (content != NULL)
+ content->assign(answer["content"].asString());
+
+ return true;
+}
+
+DNSBackend *RemoteBackend::maker()
+{
+ try {
+ return new RemoteBackend();
+ }
+ catch(...) {
+ L<<Logger::Error<<kBackendId<<" Unable to instantiate a remotebackend!"<<endl;
+ return 0;
+ };
+}
+
+class RemoteBackendFactory : public BackendFactory
+{
+ public:
+ RemoteBackendFactory() : BackendFactory("remote") {}
+
+ void declareArguments(const std::string &suffix="")
+ {
+ declare(suffix,"dnssec","Enable dnssec support","no");
+ declare(suffix,"connection-string","Connection string","");
+ }
+
+ DNSBackend *make(const std::string &suffix="")
+ {
+ return new RemoteBackend(suffix);
+ }
+};
+
+class RemoteLoader
+{
+ public:
+ RemoteLoader()
+ {
+ curl_global_init(CURL_GLOBAL_ALL);
+ BackendMakers().report(new RemoteBackendFactory);
+ L<<Logger::Notice<<kBackendId<<" This is the remotebackend version "VERSION" ("__DATE__", "__TIME__") reporting"<<endl;
+ }
+};
+
+static RemoteLoader remoteloader;
--- /dev/null
+#ifndef REMOTEBACKEND_REMOTEBACKEND_HH
+
+#include <string>
+#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 <jsoncpp/json/json.h>
+#include "../pipebackend/coprocess.hh"
+#include <curl/curl.h>
+
+class Connector {
+ public:
+ virtual ~Connector() {};
+ bool send(Json::Value &value);
+ bool recv(Json::Value &value);
+ virtual int send_message(const Json::Value &input) = 0;
+ virtual int recv_message(Json::Value &output) = 0;
+};
+
+// fwd declarations
+class UnixsocketConnector: public Connector {
+ public:
+ UnixsocketConnector(std::map<std::string,std::string> options);
+ virtual ~UnixsocketConnector();
+ virtual int send_message(const Json::Value &input);
+ virtual int recv_message(Json::Value &output);
+};
+
+class HTTPConnector: public Connector {
+ public:
+
+ HTTPConnector(std::map<std::string,std::string> options);
+ ~HTTPConnector();
+
+ virtual int send_message(const Json::Value &input);
+ virtual int recv_message(Json::Value &output);
+ friend size_t ::httpconnector_write_data(void*, size_t, size_t, void*);
+
+ private:
+ std::string d_url;
+ std::string d_url_suffix;
+ CURL *d_c;
+ std::string d_data;
+ void json2string(const Json::Value &input, std::string &output);
+ void requestbuilder(const std::string &method, const Json::Value ¶meters, struct curl_slist **slist);
+};
+
+class PipeConnector: public Connector {
+ public:
+
+ PipeConnector(std::map<std::string,std::string> options);
+ ~PipeConnector();
+
+ virtual int send_message(const Json::Value &input);
+ virtual int recv_message(Json::Value &output);
+
+ private:
+
+ void launch();
+ CoProcess *coproc;
+ std::string command;
+ std::map<std::string,std::string> options;
+};
+
+class RemoteBackend : public DNSBackend
+{
+ public:
+ RemoteBackend(const std::string &suffix="");
+ ~RemoteBackend();
+
+ void lookup(const QType &qtype, const std::string &qdomain, DNSPacket *pkt_p=0, int zoneId=-1);
+ bool get(DNSResourceRecord &rr);
+ bool list(const std::string &target, int domain_id);
+
+ virtual bool getDomainMetadata(const std::string& name, const std::string& kind, std::vector<std::string>& meta);
+ virtual bool getDomainKeys(const std::string& name, unsigned int kind, std::vector<DNSBackend::KeyData>& keys);
+ virtual bool getTSIGKey(const std::string& name, std::string* algorithm, std::string* content);
+ virtual bool getBeforeAndAfterNamesAbsolute(uint32_t id, const std::string& qname, std::string& unhashed, std::string& before, std::string& after);
+ virtual bool getBeforeAndAfterNames(uint32_t id, const std::string& zonename, const std::string& qname, std::string& before, std::string& after);
+ virtual bool setDomainMetadata(const string& name, const string& kind, const std::vector<std::basic_string<char> >& meta);
+ virtual bool removeDomainKey(const string& name, unsigned int id);
+ virtual int addDomainKey(const string& name, const KeyData& key);
+ virtual bool activateDomainKey(const string& name, unsigned int id);
+ virtual bool deactivateDomainKey(const string& name, unsigned int id);
+
+ static DNSBackend *maker();
+
+ private:
+ int build(const std::string &connstr);
+ Connector *connector;
+ bool d_dnssec;
+ Json::Value d_result;
+ int d_index;
+};
+#endif
--- /dev/null
+#!/usr/bin/ruby
+
+require 'json'
+
+prefix = "2001:06e8:0500:0000"
+
+# domain keys
+
+$domain_keys = [
+ {
+ :id => 8,
+ :flags => 257,
+ :active => true,
+ :content => "Private-key-format: v1.2
+Algorithm: 8 (RSASHA256)
+Modulus: nFfl3jgFE+KET9PFPVmRCVmGZ+o+Stnqq7AZPAmhSYf96BPqK3t22vKHJhmVla+td94if2XUSggRUDz8YSeIybVNDTCmhF4xNyhAYAeh+HxJQU7CL8jQLs0b86Hd/Ua/s2pmSFSX4vzoAg+1lucLT3AiGYWjrvciFFpWnMyYftqcfTy3Vu/TMA9Kr7tr75CsJ2mzu1rywHsvOErCY8xH6dGd2LCY9ozuOwsgAlou+rOF5W71UPIuomM9QcIdVD2nq8+aHSsKeWk8gabLEsKU0SFFutO/R41lYXiUaFv+zY7r7NmP+d0Wmr0rgnzq2vlV0USQhlDPAsPSVstAuMrjgw==
+PublicExponent: AQAB
+PrivateExponent: kSdnrMbKe7dqfcpDG9WMgYp6BS88A5/a2F2ysv+Ds+Q9cAENXTpPn2yNDd4+sd3pgcmYsjsS6deTr9/YFRXw9bKBKLI80JFdZhKng5hMvYqnBGQmt/Dz4/RE87PVLNIC4/mrFE4Bn2vStnRTu3WY/wURXf3YkgI+IdXsfQTsBNloLiRoVV1QmB4kkHtRXsr02dg4kMhaXA13kT7lsrquCvGoxP++keVJ88AaQA8si30/E+CSgrbCHThUXeL6ErYz6myI5O2RJqodlcEQeXYi2cNkzQJnO3yjXcaSJy2u3tVNE2wXCXf1pwHXyTA4YT1qMbMIABq9TiVW8Q2Kg4sN
+Prime1: wrNo1cyyL/LpO+Das2EQzMbX0ctdy0OPvg3kWZqt2ZEwJjcVe1Vav5ZzSWKQWuT0IytUst7qdTE7lKpWWKDAbxJ4IMcMiMN2iQqE9V/dhIJluIrshupQzdhnXR46MfbazkDuvMTM1Fk4ZOgm3lrJoRvl6EOxl7e9veTwCjKqrA8=
+Prime2: zZDvTPq/Ea7aKa+CNmP7oOYxpvE1fpPV8MRjQ+IMi8VIDHpE72NoSSVoVuSL7fgl7qAFlbJMEtQGIlqUFQkC0sbI6ddb1/Ane+xhH4aPaljtIJQs0GlM/ECz30qZCreyHqaVzXlk9ZrN7HlnIOQ//DV3lCsS8EE1djeQxFThrU0=
+Exponent1: S9uW1uX/7sqXsKq0yvrgjshSQf0YOB/Em2nSNE8duQzmU51Wk0z4JHk7xbXPRHq73A//2gkcFDjwW8XaCoHnN99cSnkDGy38uvwMPYXySrR7aWFHMnGMtgbAjvk990WUjpOh8I5Et99jJ32D11JMCKdT9iCZyuDd3mSaWX7QHGU=
+Exponent2: hiaslG8a3B5gz01zS62KHCG9i3XkdDtkJeDz6uwNRfW0JDhy3krgVsPryLETxHPpxUV2/49A6BSoACledC/SQN1rZnedv1lBWzUS2PEGjN+FuHoamNPvYruS5wiWwZDJ1AjgwBwVz9Z7xnQf4i4yt5Po+q/1hwb3LbPrbMT8Fg0=
+Coefficient: zZDvTPq/Ea7aKa+CNmP7oOYxpvE1fpPV8MRjQ+IMi8VIDHpE72NoSSVoVuSL7fgl7qAFlbJMEtQGIlqUFQkC0sbI6ddb1/Ane+xhH4aPaljtIJQs0GlM/ECz30qZCreyHqaVzXlk9ZrN7HlnIOQ//DV3lCsS8EE1djeQxFThrU0="
+ },
+ {
+ :id => 9,
+ :flags => 256,
+ :active => true,
+ :content => "Private-key-format: v1.2
+Algorithm: 8 (RSASHA256)
+Modulus: 0fcOKzIHcE5K3B3zwu0iAm4Z/cRBH9JX0mMhl9h58i0C8QEK3fK+425MEI/q4P7qGkwB9hJ9TefC5ny3b+F2iSIDTqcrMoRr6/lJO2YH7MyeuAv1T5HMsC7DoCkdaq0xuoOkOtKaVz5/FwSFmCIlCNeboFX3Fq4kCoWsa1m63AE=
+PublicExponent: AQAB
+PrivateExponent: BWc0q6LlcxvorEJvD/CXQ/W+YHvo6x84GFdpuWUeOj+zSC1tMKn7BJJFjdWOR0z4DEYxdLokFFmm99R0ygHE0ZWh7WS1OX5AzqoFczeC0BDLvoAYu6XMvwlYp78ffqlI2qv4ohfew5TYzWCugxIufFsC55i2FoworGBLSFUloaM=
+Prime1: +aIMyvDUAzL/JZDHsPLnawLhy7sfZB4BbtARAYxZ6HSE9MAnFKbQhXQ9yRdGLSZEhe8g5b9tXBvxT8TPIGhwcw==
+Prime2: 11H/gYNtYkloQuRcL2xd3ElxosgncgFvXKY0y4sGyJuEhHJfttaxXCmNfVN9fg5lX18kfxCv4s8rqI+M+rZouw==
+Exponent1: SmrNp34NpfqA52D2tsBizproVwSsgfsT8EXkm/KMJuj9bb0OqXBlPzN868Kdb/41dTvpMbRUVJ4b3OzN1lpsEw==
+Exponent2: zPRiTzd48SuKsNGJ5iIynbLTFe2LjntLM1eJvY2SYXWXCDOOZA2sOVvcMEU+mLS/Ta7UoJaTtUMZ/ZLW0Pa8bQ==
+Coefficient: 11H/gYNtYkloQuRcL2xd3ElxosgncgFvXKY0y4sGyJuEhHJfttaxXCmNfVN9fg5lX18kfxCv4s8rqI+M+rZouw=="
+ },
+ {
+ :id => 10,
+ :flags => 256,
+ :active => false,
+ :content => "Private-key-format: v1.2
+Algorithm: 8 (RSASHA256)
+Modulus: rzJLZNmV+xQLkYWT3PytSYDeE8SovXi9+WCp9MVkWcSj1BgrBbugregqsSQLvz+rGsgc2nK3kqygDhypRMExmd2TRHeIrtcLHZ3nq+mmL/GfE0K6KWS2FJx06ol7v9xG8esLe+TUSAENo2xFR0BFmkiJN+R5hq06Rb0Y7OMSBs8=
+PublicExponent: AQAB
+PrivateExponent: FwH6jMABG6bCQ4DQrlDbS6/felEf/TdXcOHqRU7houMEG4fLqUZpZO1Rzfmh4U0x4fktxjJn5pyXrcLDKAMHHv+DjtPSC4B+49HFOZEHl/522VYw6NyJ+Firk41hbFxIStSFYh47qxKyAwbI/LuyTJE0Au8D+4dl8oFwfh0x+aE=
+Prime1: 0sDZukxVtL4zI9JPvEdBUAXLu4I98GS7xhxiLNeb+iF2+Vvw06igV7+CccMldz/r3gqr9y+NdAloErFvkieLRQ==
+Prime2: 1M822AT25uYwdKiiYuBPQlUIKVuP1paa8y4rh+uDuoHpm9E29hwHr2uLXESOO5YxB0oc8yauNOdH+HGTg8GhAw==
+Exponent1: IOq2Fv7tM/mxCxtCEOogLVt6YqMJAY76NQsh2lciqYKojnHpv2VLBemHejU8mM+HC3snOMhYk5MUijbkcjNy8Q==
+Exponent2: V0LUkUWP3GQ9MEjJtVOXDHMDkrnZxDsjNF4VOXmoHT0SBnOGXupleFfX4DC4RdSzK/MG5elRe53ulAA2Zctq8w==
+Coefficient: 1M822AT25uYwdKiiYuBPQlUIKVuP1paa8y4rh+uDuoHpm9E29hwHr2uLXESOO5YxB0oc8yauNOdH+HGTg8GhAw=="
+ }
+]
+
+def to32(number)
+ number.to_s(32).tr "0123456789abcdefghijklmnopqrstuv", "ybndrfg8ejkmcpqxot1uwisza345h769"
+end
+
+def from32(str)
+ str.tr("0123456789abcdefghijklmnopqrstuv", "ybndrfg8ejkmcpqxot1uwisza345h769").to_i(32)
+end
+
+def rr(qname, qtype, content, ttl, priority = 0, auth = 1)
+ {:qname => qname, :qtype => qtype, :content => content, :ttl => ttl, :priority => priority, :auth => auth}
+end
+
+def send_result(result, log = nil)
+ out = {:result => result}
+ if log.class != Array
+ log = [log]
+ end
+ out[:log] = log unless log.nil?
+ print out.to_json
+ print "\n"
+end
+
+def send_failure(msg)
+ send_result false, [msg]
+end
+
+class Handler
+ attr :prefix, :plen, :suffix, :domain
+
+ def initialize(prefix)
+ @prefix = prefix
+ end
+
+ def do_initialize(*args)
+ # precalculate some things
+ tmp = self.prefix.gsub(/[^0-9a-f]/,'')
+ @plen = tmp.size
+ @suffix = tmp.split(//).reverse.join('.')+".ip6.arpa"
+ @domain = "dyn.example.com"
+ send_result true, "Autorev v6 backend initialized"
+ end
+
+ def do_getbeforeandafternamesabsolute(args)
+ qname = args["qname"]
+ # assume prefix
+ name = qname.gsub(/ /,'')
+ nlen = 32-self.plen
+
+ name_a = nil
+ name_b = nil
+ unhashed = nil
+
+ if name == ''
+ name_a = sprintf("%0#{nlen}x",0).split(//).join(' ')
+ elsif name.size < nlen
+ name_a = sprintf("%0#{nlen}x",0).split(//).join(' ')
+ else
+ unhashed = sprintf("%0#{nlen}x",name.to_i(16)).split(//).join(' ')
+ if (name.to_i(16) > 0)
+ name_b = sprintf("%0#{nlen}x",(name.to_i(16)-1)).split(//).join ' '
+ end
+ unless (name[/[^f]/].nil?)
+ name_a = sprintf("%0#{nlen}x",(name.to_i(16)+1)).split(//).join ' '
+ end
+ end
+ send_result ({:before => name_b, :after => name_a, :unhashed => unhashed})
+ end
+
+ def do_getbeforeandafternames(args)
+ self.do_getbeforeandafternamesabsolute(args)
+ end
+
+ def do_getdomainkeys(args)
+ if args["name"] == self.suffix
+ send_result $domain_keys
+ else
+ send_result false
+ end
+ end
+
+ def do_lookup(args)
+ qtype = args["qtype"]
+ qname = args["qname"]
+
+ if qname[/.ip6.arpa$/]
+ if qname == self.suffix
+ ret = []
+ if (qtype != "SOA")
+ ret << rr(qname, "NS", "ns1.example.com",300)
+ ret << rr(qname, "NS", "ns2.example.com",300)
+ ret << rr(qname, "NS", "ns3.example.com",300)
+ end
+ ret << rr(qname,"SOA","ns1.example.com hostmaster.example.com #{Time.now.strftime("%Y%m%d%H")} 28800 7200 1209600 300",300)
+ return send_result ret
+ elsif qtype == "ANY" or qtype == "PTR"
+ # assume suffix
+ name = qname.gsub(self.suffix,"").split(".").reverse.join("")
+ if name.empty? or name.size != 32-plen
+ return send_result false
+ end
+ name = to32(name.to_i(16))
+ return send_result [rr(qname, "PTR", "node-#{name}.#{self.domain}", 300)]
+ end
+ end
+ send_result false
+ end
+
+ def do_getdomainmetadata(args)
+ name = args["name"]
+ kind = args["kind"]
+ send_result false
+ end
+end
+
+h = Handler.new(prefix)
+
+STDOUT.sync = true
+begin
+ STDIN.each_line do |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
+ send_result res, nil
+ elsif args.size > 0
+ h.send(method,args)
+ else
+ h.send(method)
+ end
+ rescue JSON::ParserError
+ send_failure "Cannot parse input #{line}"
+ next
+ end
+ end
+rescue SystemExit, Interrupt
+end
--- /dev/null
+#include "remotebackend.hh"
+#include <sys/socket.h>
+#include <pdns/lock.hh>
+#include <unistd.h>
+#include <sys/select.h>
+#include <fcntl.h>
+
+#ifndef UNIX_PATH_MAX
+#define UNIX_PATH_MAX 108
+#endif
+
+
+// Singleton class to maintain client connection
+// via single unix socket connection
+static int n_unix_socket_connection;
+static pthread_mutex_t unix_build_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t unix_mutex = PTHREAD_MUTEX_INITIALIZER;
+class UnixsocketConnection {
+ public:
+ UnixsocketConnection(const std::string &path)
+ {
+ this->path = path;
+ connected = false;
+ };
+
+ ~UnixsocketConnection() {
+ L<<Logger::Info<<"closing socket connection"<<endl;
+ close(fd);
+ };
+
+ ssize_t read(std::string &data) {
+ ssize_t nread;
+ char buf[1500] = {0};
+ Lock scoped_lock(&unix_mutex);
+
+ reconnect();
+ if (!connected) return -1;
+ nread = ::read(this->fd, buf, sizeof buf);
+ // just try again later...
+ if (nread==-1 && errno == EAGAIN) return 0;
+
+ if (nread==-1) {
+ connected = false;
+ close(fd);
+ return -1;
+ }
+
+ data.append(buf, nread);
+ return nread;
+ };
+
+ ssize_t write(const std::string &data) {
+ ssize_t nwrite, nbuf;
+ char buf[1500];
+ Lock scoped_lock(&unix_mutex);
+
+ reconnect();
+ if (!connected) return -1;
+ nbuf = data.copy(buf, sizeof buf); // copy data and write
+ nwrite = ::write(fd, buf, nbuf);
+ if (nwrite == -1) {
+ connected = false;
+ close(fd);
+ return -1;
+ }
+ return nwrite;
+ };
+
+ private:
+ int fd;
+ bool connected;
+ std::string path;
+
+ void reconnect() {
+ struct sockaddr_un sock;
+ struct timeval tv;
+ fd_set rd;
+
+ if (connected) return; // no point reconnecting if connected...
+ connected = true;
+
+ L<<Logger::Info<<"Reconnecting to backend" << std::endl;
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0) {
+ connected = false;
+ L<<Logger::Error<<"Cannot create socket: " << strerror(errno) << std::endl;;
+ return;
+ }
+ sock.sun_family = AF_UNIX;
+ memset(sock.sun_path, 0, UNIX_PATH_MAX);
+ path.copy(sock.sun_path, UNIX_PATH_MAX, 0);
+ fcntl(fd, F_SETFL, O_NONBLOCK, &fd);
+
+ while(connect(fd, reinterpret_cast<struct sockaddr*>(&sock), sizeof sock)==-1 && (errno == EINPROGRESS)) {
+ tv.tv_sec = 0;
+ tv.tv_usec = 500;
+ FD_ZERO(&rd);
+ FD_SET(fd, &rd);
+ select(fd+1,&rd,NULL,NULL,&tv); // wait a moment
+ };
+ if (errno != EISCONN && errno != 0) {
+ L<<Logger::Error<<"Cannot connect to socket: " << strerror(errno) << std::endl;
+ close(fd);
+ connected = false;
+ return;
+ }
+ };
+};
+
+static UnixsocketConnection *unix_socket_connection;
+
+UnixsocketConnector::UnixsocketConnector(std::map<std::string,std::string> options) {
+ Lock scoped_lock(&unix_build_mutex);
+
+ if (unix_socket_connection == NULL) {
+ Json::Value init,res;
+ unix_socket_connection = new UnixsocketConnection(options.find("path")->second);
+ n_unix_socket_connection = 1;
+ init["method"] = "initialize";
+ init["parameters"] = Json::Value();
+ for(std::map<std::string,std::string>::iterator i = options.begin(); i != options.end(); i++)
+ init["parameters"][i->first] = i->second;
+ this->send(init);
+ if (this->recv(res) == false)
+ L<<Logger::Warning << "Failed to initialize backend" << std::endl;
+ } else {
+ n_unix_socket_connection++;
+ }
+}
+
+UnixsocketConnector::~UnixsocketConnector() {
+ Lock scoped_lock(&unix_build_mutex);
+
+ n_unix_socket_connection--;
+ if (n_unix_socket_connection == 0) {
+ delete unix_socket_connection;
+ unix_socket_connection = NULL;
+ }
+}
+
+int UnixsocketConnector::send_message(const Json::Value &input) {
+ std::string data;
+ Json::FastWriter writer;
+ int rv;
+ data = writer.write(input);
+ //i make sure we got nothing waiting there.
+ std::string temp;
+ while(unix_socket_connection->read(temp)>0) { temp = ""; }
+ rv = unix_socket_connection->write(data);
+ if (rv == -1)
+ throw AhuException("Failed to write to socket");
+ return rv;
+}
+
+int UnixsocketConnector::recv_message(Json::Value &output) {
+ int rv,nread;
+ std::string s_output;
+ Json::Reader r;
+ time_t t0;
+
+ nread = 0;
+ t0 = time(NULL);
+ s_output = "";
+
+ while(time(NULL) - t0 < 2) { // 2 second timeout
+ std::string temp;
+ rv = unix_socket_connection->read(temp);
+ if (rv == -1)
+ throw AhuException("Failed to read from socket");
+
+ if (rv>0) {
+ nread += rv;
+ s_output.append(temp);
+ if (r.parse(s_output,output)==true) {
+ return nread;
+ }
+ }
+ }
+
+ return -1;
+}