From: Peter van Dijk Date: Fri, 5 Oct 2012 08:04:59 +0000 (+0000) Subject: add remotebackend, by Aki Tuomi X-Git-Tag: auth-3.2-rc1~127 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bf42c81738795d53daee615224194213dd320250;p=thirdparty%2Fpdns.git add remotebackend, by Aki Tuomi git-svn-id: svn://svn.powerdns.com/pdns/trunk/pdns@2755 d19b8d6e-7fed-0310-83ef-9ca221ded41b --- diff --git a/configure.ac b/configure.ac index c082c23024..2e48cdee8a 100644 --- a/configure.ac +++ b/configure.ac @@ -666,4 +666,5 @@ modules/mongodbbackend/Makefile \ 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) diff --git a/modules/Makefile.am b/modules/Makefile.am index af5bccbd85..5aebbe1d90 100644 --- a/modules/Makefile.am +++ b/modules/Makefile.am @@ -1,2 +1,2 @@ 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 diff --git a/modules/remotebackend/Makefile.am b/modules/remotebackend/Makefile.am new file mode 100644 index 0000000000..72d96c0f45 --- /dev/null +++ b/modules/remotebackend/Makefile.am @@ -0,0 +1,13 @@ +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 diff --git a/modules/remotebackend/OBJECTFILES b/modules/remotebackend/OBJECTFILES new file mode 100644 index 0000000000..d46943f2da --- /dev/null +++ b/modules/remotebackend/OBJECTFILES @@ -0,0 +1 @@ +remotebackend.o unixconnector.o httpconnector.o pipeconnector.o diff --git a/modules/remotebackend/OBJECTLIBS b/modules/remotebackend/OBJECTLIBS new file mode 100644 index 0000000000..3b2a6a0927 --- /dev/null +++ b/modules/remotebackend/OBJECTLIBS @@ -0,0 +1 @@ +-lboost_system -ljsoncpp -lcurl diff --git a/modules/remotebackend/README b/modules/remotebackend/README new file mode 100644 index 0000000000..e310344fac --- /dev/null +++ b/modules/remotebackend/README @@ -0,0 +1,181 @@ +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=:=,=... + +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 +Optional values: domain_id and scopeMask + +Method: list +Parameters: zonename, domain_id +Reply: array of +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 + +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 + diff --git a/modules/remotebackend/TODO b/modules/remotebackend/TODO new file mode 100644 index 0000000000..02a1c742a3 --- /dev/null +++ b/modules/remotebackend/TODO @@ -0,0 +1,3 @@ +- 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 diff --git a/modules/remotebackend/httpconnector.cc b/modules/remotebackend/httpconnector.cc new file mode 100644 index 0000000000..2433efbe8d --- /dev/null +++ b/modules/remotebackend/httpconnector.cc @@ -0,0 +1,206 @@ +#include "remotebackend.hh" +#include +#include +#include +#include +#include +#include +#include +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX 108 +#endif + +HTTPConnector::HTTPConnector(std::map 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(userp); + std::string tmp(reinterpret_cast(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(input.asUInt()); + else if (input.isInt()) output = lexical_cast(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 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 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; +} diff --git a/modules/remotebackend/pipeconnector.cc b/modules/remotebackend/pipeconnector.cc new file mode 100644 index 0000000000..13a97c5f9f --- /dev/null +++ b/modules/remotebackend/pipeconnector.cc @@ -0,0 +1,71 @@ +#include "remotebackend.hh" + +PipeConnector::PipeConnector(std::map 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::iterator i = options.begin(); i != options.end(); i++) + init["parameters"][i->first] = i->second; + + this->send(init); + if (this->recv(res)==false) { + L<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< 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 diff --git a/modules/remotebackend/regression-tests/dnsbackend.rb b/modules/remotebackend/regression-tests/dnsbackend.rb new file mode 100644 index 0000000000..427446f1d6 --- /dev/null +++ b/modules/remotebackend/regression-tests/dnsbackend.rb @@ -0,0 +1,55 @@ +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 diff --git a/modules/remotebackend/regression-tests/http-backend.rb b/modules/remotebackend/regression-tests/http-backend.rb new file mode 100755 index 0000000000..a0e0529b5a --- /dev/null +++ b/modules/remotebackend/regression-tests/http-backend.rb @@ -0,0 +1,14 @@ +#!/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 diff --git a/modules/remotebackend/regression-tests/pipe-backend.rb b/modules/remotebackend/regression-tests/pipe-backend.rb new file mode 100755 index 0000000000..0e4125f87e --- /dev/null +++ b/modules/remotebackend/regression-tests/pipe-backend.rb @@ -0,0 +1,34 @@ +#!/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 diff --git a/modules/remotebackend/regression-tests/test-schema.sql b/modules/remotebackend/regression-tests/test-schema.sql new file mode 100644 index 0000000000..ba64010f90 --- /dev/null +++ b/modules/remotebackend/regression-tests/test-schema.sql @@ -0,0 +1,74 @@ +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; diff --git a/modules/remotebackend/regression-tests/unix-backend.rb b/modules/remotebackend/regression-tests/unix-backend.rb new file mode 120000 index 0000000000..ebb5e0c25c --- /dev/null +++ b/modules/remotebackend/regression-tests/unix-backend.rb @@ -0,0 +1 @@ +pipe-backend.rb \ No newline at end of file diff --git a/modules/remotebackend/remotebackend.cc b/modules/remotebackend/remotebackend.cc new file mode 100644 index 0000000000..29d7a50b67 --- /dev/null +++ b/modules/remotebackend/remotebackend.cc @@ -0,0 +1,411 @@ +#include "remotebackend.hh" +#include + +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<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 parts; + std::string type; + std::string opts; + std::map 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(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& 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& 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& 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< +#include "pdns/namespaces.hh" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../pipebackend/coprocess.hh" +#include + +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 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 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 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 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& meta); + virtual bool getDomainKeys(const std::string& name, unsigned int kind, std::vector& 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 >& 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 diff --git a/modules/remotebackend/test_backend.rb b/modules/remotebackend/test_backend.rb new file mode 100755 index 0000000000..424c765ae4 --- /dev/null +++ b/modules/remotebackend/test_backend.rb @@ -0,0 +1,199 @@ +#!/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 diff --git a/modules/remotebackend/unixconnector.cc b/modules/remotebackend/unixconnector.cc new file mode 100644 index 0000000000..a1216e0aeb --- /dev/null +++ b/modules/remotebackend/unixconnector.cc @@ -0,0 +1,181 @@ +#include "remotebackend.hh" +#include +#include +#include +#include +#include + +#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<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<(&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< 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::iterator i = options.begin(); i != options.end(); i++) + init["parameters"][i->first] = i->second; + this->send(init); + if (this->recv(res) == false) + L<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; +}