]> git.ipfire.org Git - thirdparty/pdns.git/blame - modules/remotebackend/remotebackend.cc
Merge pull request #13387 from omoerbeek/rec-b-root-servers
[thirdparty/pdns.git] / modules / remotebackend / remotebackend.cc
CommitLineData
12471842
PL
1/*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
fd9af6ec 22#include <limits>
870a0fe4
AT
23#ifdef HAVE_CONFIG_H
24#include "config.h"
25#endif
bf42c817 26#include "remotebackend.hh"
fa8fd4d2 27
ff05c7e1 28static const char* kBackendId = "[RemoteBackend]";
bf42c817
PD
29
30/**
31 * Forwarder for value. This is just in case
32 * we need to do some treatment to the value before
33 * sending it downwards.
34 */
ff05c7e1
O
35bool Connector::send(Json& value)
36{
37 return send_message(value) > 0;
bf42c817
PD
38}
39
fa2dd0e6 40/**
bf42c817 41 * Helper for handling receiving of data.
fa2dd0e6 42 * Basically what happens here is that we check
bf42c817 43 * that the receiving happened ok, and extract
fa2dd0e6 44 * result. Logging is performed here, too.
bf42c817 45 */
ff05c7e1
O
46bool Connector::recv(Json& value)
47{
2dc4f42e
PL
48 if (recv_message(value) > 0) {
49 bool retval = true;
50 if (value["result"] == Json()) {
51 throw PDNSException("No 'result' field in response from remote process");
ff05c7e1 52 }
645d66cf 53 if (value["result"].is_bool() && !boolFromJson(value, "result", false)) {
2dc4f42e 54 retval = false;
bf42c817 55 }
ff05c7e1
O
56 for (const auto& message : value["log"].array_items()) {
57 g_log << Logger::Info << "[remotebackend]: " << message.string_value() << std::endl;
2dc4f42e
PL
58 }
59 return retval;
60 }
61 throw PDNSException("Unknown error while receiving data");
62}
63
ff05c7e1
O
64void RemoteBackend::makeErrorAndThrow(Json& value)
65{
2dc4f42e 66 std::string msg = "Remote process indicated a failure";
ff05c7e1 67 for (const auto& message : value["log"].array_items()) {
2dc4f42e
PL
68 msg += " '" + message.string_value() + "'";
69 }
70 throw PDNSException(msg);
bf42c817
PD
71}
72
fa2dd0e6 73/**
bbd3f8b2
AT
74 * Standard ctor and dtor
75 */
ff05c7e1 76RemoteBackend::RemoteBackend(const std::string& suffix)
bf42c817 77{
ff05c7e1 78 setArgPrefix("remote" + suffix);
4b64c6dc 79
ff05c7e1
O
80 this->d_connstr = getArg("connection-string");
81 this->d_dnssec = mustDo("dnssec");
fa2dd0e6 82
ff05c7e1 83 build();
bf42c817
PD
84}
85
645d66cf 86RemoteBackend::~RemoteBackend() = default;
bf42c817 87
ff05c7e1
O
88bool RemoteBackend::send(Json& value)
89{
2dc4f42e
PL
90 try {
91 if (!connector->send(value)) {
92 // XXX does this work work even though we throw?
93 this->connector.reset();
94 build();
95 throw DBException("Could not send a message to remote process");
96 }
ff05c7e1
O
97 }
98 catch (const PDNSException& ex) {
2dc4f42e
PL
99 throw DBException("Exception caught when sending: " + ex.reason);
100 }
101 return true;
4b64c6dc
AT
102}
103
ff05c7e1
O
104bool RemoteBackend::recv(Json& value)
105{
2dc4f42e
PL
106 try {
107 return connector->recv(value);
ff05c7e1
O
108 }
109 catch (const PDNSException& ex) {
2dc4f42e
PL
110 this->connector.reset();
111 build();
112 throw DBException("Exception caught when receiving: " + ex.reason);
ff05c7e1
O
113 }
114 catch (const std::exception& e) {
2dc4f42e
PL
115 this->connector.reset();
116 build();
117 throw DBException("Exception caught when receiving: " + std::string(e.what()));
118 }
4b64c6dc
AT
119}
120
fa2dd0e6 121/**
bf42c817
PD
122 * Builds connector based on options
123 * Currently supports unix,pipe and http
124 */
ff05c7e1
O
125int RemoteBackend::build()
126{
127 std::vector<std::string> parts;
128 std::string type;
129 std::string opts;
130 std::map<std::string, std::string> options;
131
132 // connstr is of format "type:options"
645d66cf
FM
133 size_t pos = 0;
134 pos = d_connstr.find_first_of(':');
135 if (pos == std::string::npos) {
ff05c7e1 136 throw PDNSException("Invalid connection string: malformed");
645d66cf 137 }
ff05c7e1
O
138
139 type = d_connstr.substr(0, pos);
140 opts = d_connstr.substr(pos + 1);
141
142 // tokenize the string on comma
143 stringtok(parts, opts, ",");
144
145 // find out some options and parse them while we're at it
146 for (const auto& opt : parts) {
645d66cf
FM
147 std::string key;
148 std::string val;
ff05c7e1 149 // make sure there is something else than air in the option...
645d66cf 150 if (opt.find_first_not_of(" ") == std::string::npos) {
ff05c7e1 151 continue;
645d66cf 152 }
ff05c7e1
O
153
154 // split it on '='. if not found, we treat it as "yes"
155 pos = opt.find_first_of("=");
156
157 if (pos == std::string::npos) {
158 key = opt;
159 val = "yes";
160 }
161 else {
162 key = opt.substr(0, pos);
163 val = opt.substr(pos + 1);
164 }
165 options[key] = val;
166 }
167
168 // connectors know what they are doing
169 if (type == "unix") {
2bbc9eb0 170 this->connector = std::make_unique<UnixsocketConnector>(options);
ff05c7e1
O
171 }
172 else if (type == "http") {
2bbc9eb0 173 this->connector = std::make_unique<HTTPConnector>(options);
ff05c7e1
O
174 }
175 else if (type == "zeromq") {
a7db8aa6 176#ifdef REMOTEBACKEND_ZEROMQ
2bbc9eb0 177 this->connector = std::make_unique<ZeroMQConnector>(options);
a7db8aa6 178#else
ff05c7e1 179 throw PDNSException("Invalid connection string: zeromq connector support not enabled. Recompile with --enable-remotebackend-zeromq");
72ffdde4 180#endif
ff05c7e1
O
181 }
182 else if (type == "pipe") {
2bbc9eb0 183 this->connector = std::make_unique<PipeConnector>(options);
ff05c7e1
O
184 }
185 else {
186 throw PDNSException("Invalid connection string: unknown connector");
187 }
bf42c817 188
ff05c7e1 189 return -1;
bf42c817
PD
190}
191
fa2dd0e6 192/**
bf42c817 193 * The functions here are just remote json stubs that send and receive the method call
fa2dd0e6 194 * data is mainly left alone, some defaults are assumed.
bf42c817 195 */
ff05c7e1
O
196void RemoteBackend::lookup(const QType& qtype, const DNSName& qdomain, int zoneId, DNSPacket* pkt_p)
197{
645d66cf 198 if (d_index != -1) {
ff05c7e1 199 throw PDNSException("Attempt to lookup while one running");
645d66cf 200 }
bf42c817 201
ff05c7e1
O
202 string localIP = "0.0.0.0";
203 string remoteIP = "0.0.0.0";
204 string realRemote = "0.0.0.0/0";
95666839 205
645d66cf 206 if (pkt_p != nullptr) {
ff05c7e1
O
207 localIP = pkt_p->getLocal().toString();
208 realRemote = pkt_p->getRealRemote().toString();
7181a333 209 remoteIP = pkt_p->getInnerRemote().toString();
ff05c7e1
O
210 }
211
212 Json query = Json::object{
213 {"method", "lookup"},
d5fcd583 214 {"parameters", Json::object{{"qtype", qtype.toString()}, {"qname", qdomain.toString()}, {"remote", remoteIP}, {"local", localIP}, {"real-remote", realRemote}, {"zone-id", zoneId}}}};
ff05c7e1 215
645d66cf 216 if (!this->send(query) || !this->recv(d_result)) {
ff05c7e1
O
217 return;
218 }
219
220 // OK. we have result parameters in result. do not process empty result.
645d66cf 221 if (!d_result["result"].is_array() || d_result["result"].array_items().empty()) {
ff05c7e1 222 return;
645d66cf 223 }
ff05c7e1
O
224
225 d_index = 0;
bf42c817
PD
226}
227
ff05c7e1
O
228bool RemoteBackend::list(const DNSName& target, int domain_id, bool include_disabled)
229{
645d66cf 230 if (d_index != -1) {
ff05c7e1 231 throw PDNSException("Attempt to lookup while one running");
645d66cf 232 }
ff05c7e1
O
233
234 Json query = Json::object{
235 {"method", "list"},
236 {"parameters", Json::object{{"zonename", target.toString()}, {"domain_id", domain_id}, {"include_disabled", include_disabled}}}};
237
645d66cf 238 if (!this->send(query) || !this->recv(d_result)) {
ff05c7e1 239 return false;
645d66cf
FM
240 }
241 if (!d_result["result"].is_array() || d_result["result"].array_items().empty()) {
ff05c7e1 242 return false;
645d66cf 243 }
ff05c7e1
O
244
245 d_index = 0;
246 return true;
bf42c817
PD
247}
248
ff05c7e1
O
249bool RemoteBackend::get(DNSResourceRecord& rr)
250{
645d66cf 251 if (d_index == -1) {
ff05c7e1 252 return false;
645d66cf 253 }
ff05c7e1
O
254
255 rr.qtype = stringFromJson(d_result["result"][d_index], "qtype");
256 rr.qname = DNSName(stringFromJson(d_result["result"][d_index], "qname"));
257 rr.qclass = QClass::IN;
258 rr.content = stringFromJson(d_result["result"][d_index], "content");
259 rr.ttl = d_result["result"][d_index]["ttl"].int_value();
260 rr.domain_id = intFromJson(d_result["result"][d_index], "domain_id", -1);
645d66cf
FM
261 if (d_dnssec) {
262 rr.auth = (intFromJson(d_result["result"][d_index], "auth", 1) != 0);
263 }
264 else {
265 rr.auth = true;
266 }
ff05c7e1
O
267 rr.scopeMask = d_result["result"][d_index]["scopeMask"].int_value();
268 d_index++;
269
270 // id index is out of bounds, we know the results end here.
271 if (d_index == static_cast<int>(d_result["result"].array_items().size())) {
272 d_result = Json();
273 d_index = -1;
274 }
275 return true;
c731caea
AT
276}
277
ff05c7e1
O
278bool RemoteBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
279{
280 // no point doing dnssec if it's not supported
645d66cf 281 if (!d_dnssec) {
ff05c7e1 282 return false;
645d66cf 283 }
ff05c7e1
O
284
285 Json query = Json::object{
286 {"method", "getBeforeAndAfterNamesAbsolute"},
287 {"parameters", Json::object{{"id", Json(static_cast<double>(id))}, {"qname", qname.toString()}}}};
288 Json answer;
289
645d66cf 290 if (!this->send(query) || !this->recv(answer)) {
ff05c7e1 291 return false;
645d66cf 292 }
ff05c7e1
O
293
294 unhashed = DNSName(stringFromJson(answer["result"], "unhashed"));
295 before.clear();
296 after.clear();
645d66cf 297 if (answer["result"]["before"] != Json()) {
ff05c7e1 298 before = DNSName(stringFromJson(answer["result"], "before"));
645d66cf
FM
299 }
300 if (answer["result"]["after"] != Json()) {
ff05c7e1 301 after = DNSName(stringFromJson(answer["result"], "after"));
645d66cf 302 }
ff05c7e1
O
303
304 return true;
bf42c817
PD
305}
306
ff05c7e1
O
307bool RemoteBackend::getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string>>& meta)
308{
309 Json query = Json::object{
310 {"method", "getAllDomainMetadata"},
311 {"parameters", Json::object{{"name", name.toString()}}}};
312
645d66cf 313 if (!this->send(query)) {
ff05c7e1 314 return false;
645d66cf 315 }
ff05c7e1
O
316
317 meta.clear();
318
319 Json answer;
320 // not mandatory to implement
645d66cf 321 if (!this->recv(answer)) {
ff05c7e1 322 return true;
645d66cf 323 }
ff05c7e1
O
324
325 for (const auto& pair : answer["result"].object_items()) {
326 if (pair.second.is_array()) {
645d66cf 327 for (const auto& val : pair.second.array_items()) {
ff05c7e1 328 meta[pair.first].push_back(asString(val));
645d66cf 329 }
ff05c7e1
O
330 }
331 else {
332 meta[pair.first].push_back(asString(pair.second));
333 }
334 }
335
336 return true;
bf42c817
PD
337}
338
ff05c7e1
O
339bool RemoteBackend::getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta)
340{
341 Json query = Json::object{
342 {"method", "getDomainMetadata"},
343 {"parameters", Json::object{{"name", name.toString()}, {"kind", kind}}}};
bf42c817 344
645d66cf 345 if (!this->send(query)) {
ff05c7e1 346 return false;
645d66cf 347 }
bf42c817 348
ff05c7e1 349 meta.clear();
bf42c817 350
ff05c7e1
O
351 Json answer;
352 // not mandatory to implement
645d66cf 353 if (!this->recv(answer)) {
ff05c7e1 354 return true;
645d66cf 355 }
bf42c817 356
ff05c7e1 357 if (answer["result"].is_array()) {
645d66cf 358 for (const auto& row : answer["result"].array_items()) {
ff05c7e1 359 meta.push_back(row.string_value());
645d66cf 360 }
ff05c7e1
O
361 }
362 else if (answer["result"].is_string()) {
363 meta.push_back(answer["result"].string_value());
364 }
bf42c817 365
ff05c7e1
O
366 return true;
367}
368
369bool RemoteBackend::setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta)
370{
371 Json query = Json::object{
372 {"method", "setDomainMetadata"},
373 {"parameters", Json::object{{"name", name.toString()}, {"kind", kind}, {"value", meta}}}};
bf42c817 374
ff05c7e1 375 Json answer;
645d66cf 376 if (!this->send(query) || !this->recv(answer)) {
ff05c7e1 377 return false;
645d66cf 378 }
ff05c7e1
O
379
380 return boolFromJson(answer, "result", false);
bf42c817
PD
381}
382
ff05c7e1
O
383bool RemoteBackend::getDomainKeys(const DNSName& name, std::vector<DNSBackend::KeyData>& keys)
384{
385 // no point doing dnssec if it's not supported
645d66cf 386 if (!d_dnssec) {
ff05c7e1 387 return false;
645d66cf 388 }
72ffdde4 389
ff05c7e1
O
390 Json query = Json::object{
391 {"method", "getDomainKeys"},
392 {"parameters", Json::object{{"name", name.toString()}}}};
bf42c817 393
ff05c7e1 394 Json answer;
645d66cf 395 if (!this->send(query) || !this->recv(answer)) {
ff05c7e1 396 return false;
645d66cf 397 }
bf42c817 398
ff05c7e1
O
399 keys.clear();
400
401 for (const auto& jsonKey : answer["result"].array_items()) {
402 DNSBackend::KeyData key;
403 key.id = intFromJson(jsonKey, "id");
404 key.flags = intFromJson(jsonKey, "flags");
405 key.active = asBool(jsonKey["active"]);
406 key.published = boolFromJson(jsonKey, "published", true);
407 key.content = stringFromJson(jsonKey, "content");
408 keys.push_back(key);
409 }
410
411 return true;
bf42c817
PD
412}
413
ff05c7e1
O
414bool RemoteBackend::removeDomainKey(const DNSName& name, unsigned int id)
415{
416 // no point doing dnssec if it's not supported
645d66cf 417 if (!d_dnssec) {
ff05c7e1 418 return false;
645d66cf 419 }
ff05c7e1
O
420
421 Json query = Json::object{
422 {"method", "removeDomainKey"},
423 {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(id)}}}};
424
425 Json answer;
645d66cf 426 return this->send(query) && this->recv(answer);
bf42c817
PD
427}
428
ff05c7e1
O
429bool RemoteBackend::addDomainKey(const DNSName& name, const KeyData& key, int64_t& id)
430{
431 // no point doing dnssec if it's not supported
645d66cf 432 if (!d_dnssec) {
ff05c7e1 433 return false;
645d66cf 434 }
72ffdde4 435
ff05c7e1
O
436 Json query = Json::object{
437 {"method", "addDomainKey"},
438 {"parameters", Json::object{{"name", name.toString()}, {"key", Json::object{{"flags", static_cast<int>(key.flags)}, {"active", key.active}, {"published", key.published}, {"content", key.content}}}}}};
bf42c817 439
ff05c7e1 440 Json answer;
645d66cf 441 if (!this->send(query) || !this->recv(answer)) {
ff05c7e1 442 return false;
645d66cf 443 }
bf42c817 444
ff05c7e1
O
445 id = answer["result"].int_value();
446 return id >= 0;
bf42c817
PD
447}
448
ff05c7e1
O
449bool RemoteBackend::activateDomainKey(const DNSName& name, unsigned int id)
450{
451 // no point doing dnssec if it's not supported
645d66cf 452 if (!d_dnssec) {
ff05c7e1 453 return false;
645d66cf 454 }
72ffdde4 455
ff05c7e1
O
456 Json query = Json::object{
457 {"method", "activateDomainKey"},
458 {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(id)}}}};
72ffdde4 459
ff05c7e1 460 Json answer;
645d66cf 461 return this->send(query) && this->recv(answer);
bf42c817
PD
462}
463
ff05c7e1
O
464bool RemoteBackend::deactivateDomainKey(const DNSName& name, unsigned int id)
465{
466 // no point doing dnssec if it's not supported
645d66cf 467 if (!d_dnssec) {
ff05c7e1 468 return false;
645d66cf 469 }
33918299 470
ff05c7e1
O
471 Json query = Json::object{
472 {"method", "deactivateDomainKey"},
473 {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(id)}}}};
33918299 474
ff05c7e1 475 Json answer;
645d66cf 476 return this->send(query) && this->recv(answer);
33918299
RG
477}
478
ff05c7e1
O
479bool RemoteBackend::publishDomainKey(const DNSName& name, unsigned int id)
480{
481 // no point doing dnssec if it's not supported
645d66cf 482 if (!d_dnssec) {
ff05c7e1 483 return false;
645d66cf 484 }
33918299 485
ff05c7e1
O
486 Json query = Json::object{
487 {"method", "publishDomainKey"},
488 {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(id)}}}};
33918299 489
ff05c7e1 490 Json answer;
645d66cf 491 return this->send(query) && this->recv(answer);
33918299
RG
492}
493
ff05c7e1
O
494bool RemoteBackend::unpublishDomainKey(const DNSName& name, unsigned int id)
495{
496 // no point doing dnssec if it's not supported
645d66cf 497 if (!d_dnssec) {
ff05c7e1 498 return false;
645d66cf 499 }
ff05c7e1
O
500
501 Json query = Json::object{
502 {"method", "unpublishDomainKey"},
503 {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(id)}}}};
504
505 Json answer;
645d66cf 506 return this->send(query) && this->recv(answer);
ff05c7e1 507}
33918299 508
ff05c7e1
O
509bool RemoteBackend::doesDNSSEC()
510{
511 return d_dnssec;
f4644dfc
PD
512}
513
40361bf2 514bool RemoteBackend::getTSIGKey(const DNSName& name, DNSName& algorithm, std::string& content)
ff05c7e1
O
515{
516 // no point doing dnssec if it's not supported
645d66cf 517 if (!d_dnssec) {
ff05c7e1 518 return false;
645d66cf 519 }
bf42c817 520
ff05c7e1
O
521 Json query = Json::object{
522 {"method", "getTSIGKey"},
523 {"parameters", Json::object{{"name", name.toString()}}}};
fa2dd0e6 524
ff05c7e1 525 Json answer;
645d66cf 526 if (!this->send(query) || !this->recv(answer)) {
ff05c7e1 527 return false;
645d66cf 528 }
bf42c817 529
40361bf2
KM
530 algorithm = DNSName(stringFromJson(answer["result"], "algorithm"));
531 content = stringFromJson(answer["result"], "content");
40d2dc07 532
ff05c7e1 533 return true;
bf42c817
PD
534}
535
ff05c7e1
O
536bool RemoteBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const std::string& content)
537{
538 // no point doing dnssec if it's not supported
645d66cf 539 if (!d_dnssec) {
ff05c7e1 540 return false;
645d66cf 541 }
fa2dd0e6 542
ff05c7e1
O
543 Json query = Json::object{
544 {"method", "setTSIGKey"},
545 {"parameters", Json::object{{"name", name.toString()}, {"algorithm", algorithm.toString()}, {"content", content}}}};
fa2dd0e6 546
ff05c7e1 547 Json answer;
645d66cf 548 return connector->send(query) && connector->recv(answer);
85f1a356
AT
549}
550
ff05c7e1
O
551bool RemoteBackend::deleteTSIGKey(const DNSName& name)
552{
553 // no point doing dnssec if it's not supported
645d66cf 554 if (!d_dnssec) {
ff05c7e1 555 return false;
645d66cf 556 }
ff05c7e1
O
557 Json query = Json::object{
558 {"method", "deleteTSIGKey"},
559 {"parameters", Json::object{{"name", name.toString()}}}};
560
561 Json answer;
645d66cf 562 return connector->send(query) && connector->recv(answer);
85f1a356
AT
563}
564
ff05c7e1
O
565bool RemoteBackend::getTSIGKeys(std::vector<struct TSIGKey>& keys)
566{
567 // no point doing dnssec if it's not supported
645d66cf 568 if (!d_dnssec) {
ff05c7e1 569 return false;
645d66cf 570 }
ff05c7e1
O
571 Json query = Json::object{
572 {"method", "getTSIGKeys"},
573 {"parameters", Json::object{}}};
574
575 Json answer;
645d66cf 576 if (!connector->send(query) || !connector->recv(answer)) {
ff05c7e1 577 return false;
645d66cf 578 }
ff05c7e1
O
579
580 for (const auto& jsonKey : answer["result"].array_items()) {
581 struct TSIGKey key;
582 key.name = DNSName(stringFromJson(jsonKey, "name"));
583 key.algorithm = DNSName(stringFromJson(jsonKey, "algorithm"));
584 key.key = stringFromJson(jsonKey, "content");
585 keys.push_back(key);
586 }
587
588 return true;
85f1a356
AT
589}
590
ff05c7e1 591void RemoteBackend::parseDomainInfo(const Json& obj, DomainInfo& di)
e3991e7e 592{
ff05c7e1
O
593 di.id = intFromJson(obj, "id", -1);
594 di.zone = DNSName(stringFromJson(obj, "zone"));
d525b58b
KM
595 for (const auto& primary : obj["masters"].array_items()) {
596 di.primaries.emplace_back(primary.string_value(), 53);
645d66cf 597 }
ff05c7e1
O
598
599 di.notified_serial = static_cast<unsigned int>(doubleFromJson(obj, "notified_serial", 0));
600 di.serial = static_cast<unsigned int>(obj["serial"].number_value());
601 di.last_check = static_cast<time_t>(obj["last_check"].number_value());
602
645d66cf 603 string kind;
ff05c7e1
O
604 if (obj["kind"].is_string()) {
605 kind = stringFromJson(obj, "kind");
606 }
607 if (kind == "master") {
d525b58b 608 di.kind = DomainInfo::Primary;
ff05c7e1
O
609 }
610 else if (kind == "slave") {
c02c999b 611 di.kind = DomainInfo::Secondary;
ff05c7e1
O
612 }
613 else {
614 di.kind = DomainInfo::Native;
615 }
616 di.backend = this;
e3991e7e
AT
617}
618
d73de874 619bool RemoteBackend::getDomainInfo(const DNSName& domain, DomainInfo& di, bool /* getSerial */)
ff05c7e1 620{
645d66cf 621 if (domain.empty()) {
ff05c7e1 622 return false;
645d66cf
FM
623 }
624
ff05c7e1
O
625 Json query = Json::object{
626 {"method", "getDomainInfo"},
627 {"parameters", Json::object{{"name", domain.toString()}}}};
628
629 Json answer;
645d66cf 630 if (!this->send(query) || !this->recv(answer)) {
ff05c7e1 631 return false;
645d66cf 632 }
ff05c7e1
O
633
634 this->parseDomainInfo(answer["result"], di);
635 return true;
f4644dfc
PD
636}
637
ff05c7e1
O
638void RemoteBackend::setNotified(uint32_t id, uint32_t serial)
639{
640 Json query = Json::object{
641 {"method", "setNotified"},
642 {"parameters", Json::object{{"id", static_cast<double>(id)}, {"serial", static_cast<double>(serial)}}}};
643
644 Json answer;
645d66cf 645 if (!this->send(query) || !this->recv(answer)) {
ff05c7e1
O
646 g_log << Logger::Error << kBackendId << " Failed to execute RPC for RemoteBackend::setNotified(" << id << "," << serial << ")" << endl;
647 }
e39adaaf
BH
648}
649
d525b58b 650bool RemoteBackend::autoPrimaryBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** ddb)
bbd3f8b2 651{
ff05c7e1
O
652 Json::array rrset;
653
654 for (const auto& ns : nsset) {
655 rrset.push_back(Json::object{
d5fcd583 656 {"qtype", ns.qtype.toString()},
ff05c7e1 657 {"qname", ns.qname.toString()},
2b47a8d8 658 {"qclass", QClass::IN.getCode()},
ff05c7e1
O
659 {"content", ns.content},
660 {"ttl", static_cast<int>(ns.ttl)},
661 {"auth", ns.auth}});
662 }
663
664 Json query = Json::object{
665 {"method", "superMasterBackend"},
666 {"parameters", Json::object{{"ip", ip}, {"domain", domain.toString()}, {"nsset", rrset}}}};
667
645d66cf 668 *ddb = nullptr;
ff05c7e1
O
669
670 Json answer;
645d66cf 671 if (!this->send(query) || !this->recv(answer)) {
ff05c7e1 672 return false;
645d66cf 673 }
ff05c7e1
O
674
675 // we are the backend
676 *ddb = this;
677
678 // we allow simple true as well...
679 if (answer["result"].is_object()) {
680 *account = stringFromJson(answer["result"], "account");
681 *nameserver = stringFromJson(answer["result"], "nameserver");
682 }
683
684 return true;
bbd3f8b2
AT
685}
686
c02c999b 687bool RemoteBackend::createSecondaryDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account)
ff05c7e1
O
688{
689 Json query = Json::object{
690 {"method", "createSlaveDomain"},
691 {"parameters", Json::object{
692 {"ip", ip},
693 {"domain", domain.toString()},
694 {"nameserver", nameserver},
695 {"account", account},
696 }}};
697
698 Json answer;
645d66cf 699 return this->send(query) && this->recv(answer);
bbd3f8b2
AT
700}
701
ff05c7e1
O
702bool RemoteBackend::replaceRRSet(uint32_t domain_id, const DNSName& qname, const QType& qtype, const vector<DNSResourceRecord>& rrset)
703{
704 Json::array json_rrset;
705 for (const auto& rr : rrset) {
706 json_rrset.push_back(Json::object{
d5fcd583 707 {"qtype", rr.qtype.toString()},
ff05c7e1 708 {"qname", rr.qname.toString()},
2b47a8d8 709 {"qclass", QClass::IN.getCode()},
ff05c7e1
O
710 {"content", rr.content},
711 {"ttl", static_cast<int>(rr.ttl)},
712 {"auth", rr.auth}});
713 }
714
715 Json query = Json::object{
716 {"method", "replaceRRSet"},
d5fcd583 717 {"parameters", Json::object{{"domain_id", static_cast<double>(domain_id)}, {"qname", qname.toString()}, {"qtype", qtype.toString()}, {"trxid", static_cast<double>(d_trxid)}, {"rrset", json_rrset}}}};
ff05c7e1
O
718
719 Json answer;
645d66cf 720 return this->send(query) && this->recv(answer);
bbd3f8b2
AT
721}
722
d73de874 723bool RemoteBackend::feedRecord(const DNSResourceRecord& rr, const DNSName& ordername, bool /* ordernameIsNSEC3 */)
ff05c7e1
O
724{
725 Json query = Json::object{
726 {"method", "feedRecord"},
727 {"parameters", Json::object{
d5fcd583 728 {"rr", Json::object{{"qtype", rr.qtype.toString()}, {"qname", rr.qname.toString()}, {"qclass", QClass::IN.getCode()}, {"content", rr.content}, {"ttl", static_cast<int>(rr.ttl)}, {"auth", rr.auth}, {"ordername", (ordername.empty() ? Json() : ordername.toString())}}},
ff05c7e1
O
729 {"trxid", static_cast<double>(d_trxid)},
730 }}};
731
732 Json answer;
645d66cf 733 return this->send(query) && this->recv(answer); // XXX FIXME this API should not return 'true' I think -ahu
bbd3f8b2
AT
734}
735
ff05c7e1
O
736bool RemoteBackend::feedEnts(int domain_id, map<DNSName, bool>& nonterm)
737{
738 Json::array nts;
739
645d66cf 740 for (const auto& t : nonterm) {
ff05c7e1
O
741 nts.push_back(Json::object{
742 {"nonterm", t.first.toString()},
743 {"auth", t.second}});
645d66cf 744 }
ff05c7e1
O
745
746 Json query = Json::object{
747 {"method", "feedEnts"},
748 {"parameters", Json::object{{"domain_id", domain_id}, {"trxid", static_cast<double>(d_trxid)}, {"nonterm", nts}}},
749 };
750
751 Json answer;
645d66cf 752 return this->send(query) && this->recv(answer);
bbd3f8b2
AT
753}
754
ff05c7e1
O
755bool RemoteBackend::feedEnts3(int domain_id, const DNSName& domain, map<DNSName, bool>& nonterm, const NSEC3PARAMRecordContent& ns3prc, bool narrow)
756{
757 Json::array nts;
758
645d66cf 759 for (const auto& t : nonterm) {
ff05c7e1
O
760 nts.push_back(Json::object{
761 {"nonterm", t.first.toString()},
762 {"auth", t.second}});
645d66cf 763 }
ff05c7e1
O
764
765 Json query = Json::object{
766 {"method", "feedEnts3"},
767 {"parameters", Json::object{{"domain_id", domain_id}, {"domain", domain.toString()}, {"times", ns3prc.d_iterations}, {"salt", ns3prc.d_salt}, {"narrow", narrow}, {"trxid", static_cast<double>(d_trxid)}, {"nonterm", nts}}},
768 };
769
770 Json answer;
645d66cf 771 return this->send(query) && this->recv(answer);
bbd3f8b2
AT
772}
773
ff05c7e1
O
774bool RemoteBackend::startTransaction(const DNSName& domain, int domain_id)
775{
645d66cf 776 this->d_trxid = time((time_t*)nullptr);
ff05c7e1
O
777
778 Json query = Json::object{
779 {"method", "startTransaction"},
780 {"parameters", Json::object{{"domain", domain.toString()}, {"domain_id", domain_id}, {"trxid", static_cast<double>(d_trxid)}}}};
bbd3f8b2 781
ff05c7e1 782 Json answer;
645d66cf 783 if (!this->send(query) || !this->recv(answer)) {
ff05c7e1
O
784 d_trxid = -1;
785 return false;
786 }
787 return true;
bbd3f8b2 788}
645d66cf 789
ff05c7e1
O
790bool RemoteBackend::commitTransaction()
791{
645d66cf 792 if (d_trxid == -1) {
ff05c7e1 793 return false;
645d66cf 794 }
ff05c7e1
O
795
796 Json query = Json::object{
797 {"method", "commitTransaction"},
798 {"parameters", Json::object{{"trxid", static_cast<double>(d_trxid)}}}};
799
800 d_trxid = -1;
801 Json answer;
645d66cf 802 return this->send(query) && this->recv(answer);
bbd3f8b2
AT
803}
804
ff05c7e1
O
805bool RemoteBackend::abortTransaction()
806{
645d66cf 807 if (d_trxid == -1) {
ff05c7e1 808 return false;
645d66cf 809 }
ff05c7e1
O
810
811 Json query = Json::object{
812 {"method", "abortTransaction"},
813 {"parameters", Json::object{{"trxid", static_cast<double>(d_trxid)}}}};
814
815 d_trxid = -1;
816 Json answer;
645d66cf 817 return this->send(query) && this->recv(answer);
bbd3f8b2
AT
818}
819
ff05c7e1
O
820string RemoteBackend::directBackendCmd(const string& querystr)
821{
822 Json query = Json::object{
823 {"method", "directBackendCmd"},
824 {"parameters", Json::object{{"query", querystr}}}};
fa2dd0e6 825
ff05c7e1 826 Json answer;
645d66cf 827 if (!this->send(query) || !this->recv(answer)) {
ff05c7e1 828 return "backend command failed";
645d66cf 829 }
e8ac9c39 830
ff05c7e1 831 return asString(answer["result"]);
e8ac9c39
AT
832}
833
fd9af6ec 834bool RemoteBackend::searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result)
efc5a846 835{
fd9af6ec
FM
836 const auto intMax = static_cast<decltype(maxResults)>(std::numeric_limits<int>::max());
837 if (maxResults > intMax) {
838 throw std::out_of_range("Remote backend: length of list of result (" + std::to_string(maxResults) + ") is larger than what the JSON library supports for serialization (" + std::to_string(intMax) + ")");
839 }
840
fa2dd0e6 841 Json query = Json::object{
ff05c7e1 842 {"method", "searchRecords"},
fd9af6ec 843 {"parameters", Json::object{{"pattern", pattern}, {"maxResults", static_cast<int>(maxResults)}}}};
fa2dd0e6
AT
844
845 Json answer;
645d66cf 846 if (!this->send(query) || !this->recv(answer)) {
fa2dd0e6 847 return false;
645d66cf 848 }
efc5a846 849
645d66cf 850 if (!answer["result"].is_array()) {
fa2dd0e6 851 return false;
645d66cf 852 }
efc5a846 853
ff05c7e1 854 for (const auto& row : answer["result"].array_items()) {
fa2dd0e6
AT
855 DNSResourceRecord rr;
856 rr.qtype = stringFromJson(row, "qtype");
857 rr.qname = DNSName(stringFromJson(row, "qname"));
858 rr.qclass = QClass::IN;
859 rr.content = stringFromJson(row, "content");
d77af44c 860 rr.ttl = row["ttl"].int_value();
fa2dd0e6 861 rr.domain_id = intFromJson(row, "domain_id", -1);
645d66cf
FM
862 if (d_dnssec) {
863 rr.auth = (intFromJson(row, "auth", 1) != 0);
864 }
865 else {
fa2dd0e6 866 rr.auth = 1;
645d66cf 867 }
d77af44c 868 rr.scopeMask = row["scopeMask"].int_value();
fa2dd0e6 869 result.push_back(rr);
efc5a846
AT
870 }
871
872 return true;
873}
874
fd9af6ec 875bool RemoteBackend::searchComments(const string& /* pattern */, size_t /* maxResults */, vector<Comment>& /* result */)
efc5a846
AT
876{
877 // FIXME: Implement Comment API
878 return false;
879}
2654e176 880
d73de874 881void RemoteBackend::getAllDomains(vector<DomainInfo>* domains, bool /* getSerial */, bool include_disabled)
e3991e7e
AT
882{
883 Json query = Json::object{
ff05c7e1
O
884 {"method", "getAllDomains"},
885 {"parameters", Json::object{{"include_disabled", include_disabled}}}};
e3991e7e
AT
886
887 Json answer;
645d66cf 888 if (!this->send(query) || !this->recv(answer)) {
e3991e7e 889 return;
645d66cf 890 }
e3991e7e 891
645d66cf 892 if (!answer["result"].is_array()) {
e3991e7e 893 return;
645d66cf 894 }
ff05c7e1
O
895
896 for (const auto& row : answer["result"].array_items()) {
e3991e7e
AT
897 DomainInfo di;
898 this->parseDomainInfo(row, di);
899 domains->push_back(di);
900 }
901}
902
d525b58b 903void RemoteBackend::getUpdatedPrimaries(vector<DomainInfo>& domains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
e99b4250
AT
904{
905 Json query = Json::object{
ff05c7e1
O
906 {"method", "getUpdatedMasters"},
907 {"parameters", Json::object{}},
e99b4250
AT
908 };
909
910 Json answer;
645d66cf 911 if (!this->send(query) || !this->recv(answer)) {
e99b4250 912 return;
645d66cf 913 }
e99b4250 914
645d66cf 915 if (!answer["result"].is_array()) {
e99b4250 916 return;
645d66cf 917 }
e99b4250 918
ff05c7e1 919 for (const auto& row : answer["result"].array_items()) {
e99b4250
AT
920 DomainInfo di;
921 this->parseDomainInfo(row, di);
ddeea7a6 922 domains.push_back(di);
e99b4250
AT
923 }
924}
925
c02c999b 926void RemoteBackend::getUnfreshSecondaryInfos(vector<DomainInfo>* domains)
ff05c7e1 927{
dc4edd56 928 Json query = Json::object{
ff05c7e1
O
929 {"method", "getUnfreshSlaveInfos"},
930 {"parameters", Json::object{}},
dc4edd56
PL
931 };
932
933 Json answer;
645d66cf 934 if (!this->send(query) || !this->recv(answer)) {
dc4edd56 935 return;
645d66cf 936 }
dc4edd56 937
645d66cf 938 if (!answer["result"].is_array()) {
dc4edd56 939 return;
645d66cf 940 }
dc4edd56 941
ff05c7e1 942 for (const auto& row : answer["result"].array_items()) {
dc4edd56
PL
943 DomainInfo di;
944 this->parseDomainInfo(row, di);
945 domains->push_back(di);
946 }
947}
948
ce8c5899
KM
949void RemoteBackend::setStale(uint32_t domain_id)
950{
951 Json query = Json::object{
952 {"method", "setStale"},
953 {"parameters", Json::object{{"id", static_cast<double>(domain_id)}}}};
954
955 Json answer;
645d66cf 956 if (!this->send(query) || !this->recv(answer)) {
ce8c5899
KM
957 g_log << Logger::Error << kBackendId << " Failed to execute RPC for RemoteBackend::setStale(" << domain_id << ")" << endl;
958 }
959}
960
ff05c7e1
O
961void RemoteBackend::setFresh(uint32_t domain_id)
962{
963 Json query = Json::object{
964 {"method", "setFresh"},
965 {"parameters", Json::object{{"id", static_cast<double>(domain_id)}}}};
966
967 Json answer;
645d66cf 968 if (!this->send(query) || !this->recv(answer)) {
ff05c7e1
O
969 g_log << Logger::Error << kBackendId << " Failed to execute RPC for RemoteBackend::setFresh(" << domain_id << ")" << endl;
970 }
dc4edd56
PL
971}
972
ff05c7e1 973DNSBackend* RemoteBackend::maker()
bf42c817 974{
ff05c7e1
O
975 try {
976 return new RemoteBackend();
977 }
978 catch (...) {
979 g_log << Logger::Error << kBackendId << " Unable to instantiate a remotebackend!" << endl;
645d66cf 980 return nullptr;
ff05c7e1 981 };
bf42c817
PD
982}
983
984class RemoteBackendFactory : public BackendFactory
985{
ff05c7e1
O
986public:
987 RemoteBackendFactory() :
988 BackendFactory("remote") {}
989
990 void declareArguments(const std::string& suffix = "") override
991 {
992 declare(suffix, "dnssec", "Enable dnssec support", "no");
993 declare(suffix, "connection-string", "Connection string", "");
994 }
995
996 DNSBackend* make(const std::string& suffix = "") override
997 {
998 return new RemoteBackend(suffix);
999 }
bf42c817
PD
1000};
1001
1002class RemoteLoader
1003{
dfb9a585 1004public:
ff05c7e1 1005 RemoteLoader();
dfb9a585
CH
1006};
1007
ff05c7e1
O
1008RemoteLoader::RemoteLoader()
1009{
1010 BackendMakers().report(new RemoteBackendFactory);
1011 g_log << Logger::Info << kBackendId << " This is the remote backend version " VERSION
5e6a3d93 1012#ifndef REPRODUCIBLE
ff05c7e1 1013 << " (" __DATE__ " " __TIME__ ")"
5e6a3d93 1014#endif
ff05c7e1 1015 << " reporting" << endl;
dfb9a585 1016}
bf42c817
PD
1017
1018static RemoteLoader remoteloader;