• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PowerDNS / pdns / 9800261886

pending completion
9800261886

push

github

web-flow
Merge pull request #14420 from rgacogne/fix-provenance-copy

build-package: Fix copy of the provenance attestations

44818 of 119180 branches covered (37.61%)

Branch coverage included in aggregate %.

134401 of 174222 relevant lines covered (77.14%)

4437177.47 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

35.27
/modules/remotebackend/remotebackend.cc
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
 */
22
#include <limits>
23
#ifdef HAVE_CONFIG_H
24
#include "config.h"
25
#endif
26
#include "remotebackend.hh"
27

28
static const char* kBackendId = "[RemoteBackend]";
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
 */
35
bool Connector::send(Json& value)
36
{
644✔
37
  return send_message(value) > 0;
644✔
38
}
644✔
39

40
/**
41
 * Helper for handling receiving of data.
42
 * Basically what happens here is that we check
43
 * that the receiving happened ok, and extract
44
 * result. Logging is performed here, too.
45
 */
46
bool Connector::recv(Json& value)
47
{
644✔
48
  if (recv_message(value) > 0) {
644✔
49
    bool retval = true;
643✔
50
    if (value["result"] == Json()) {
643!
51
      throw PDNSException("No 'result' field in response from remote process");
×
52
    }
×
53
    if (value["result"].is_bool() && !boolFromJson(value, "result", false)) {
643✔
54
      retval = false;
148✔
55
    }
148✔
56
    for (const auto& message : value["log"].array_items()) {
643✔
57
      g_log << Logger::Info << "[remotebackend]: " << message.string_value() << std::endl;
16✔
58
    }
16✔
59
    return retval;
643✔
60
  }
643✔
61
  throw PDNSException("Unknown error while receiving data");
1✔
62
}
644✔
63

64
void RemoteBackend::makeErrorAndThrow(Json& value)
65
{
×
66
  std::string msg = "Remote process indicated a failure";
×
67
  for (const auto& message : value["log"].array_items()) {
×
68
    msg += " '" + message.string_value() + "'";
×
69
  }
×
70
  throw PDNSException(msg);
×
71
}
×
72

73
/**
74
 * Standard ctor and dtor
75
 */
76
RemoteBackend::RemoteBackend(const std::string& suffix)
77
{
100✔
78
  setArgPrefix("remote" + suffix);
100✔
79

80
  this->d_connstr = getArg("connection-string");
100✔
81
  this->d_dnssec = mustDo("dnssec");
100✔
82

83
  build();
100✔
84
}
100✔
85

86
RemoteBackend::~RemoteBackend() = default;
84✔
87

88
bool RemoteBackend::send(Json& value)
89
{
583✔
90
  try {
583✔
91
    if (!connector->send(value)) {
583!
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
    }
×
97
  }
583✔
98
  catch (const PDNSException& ex) {
583✔
99
    throw DBException("Exception caught when sending: " + ex.reason);
×
100
  }
×
101
  return true;
583✔
102
}
583✔
103

104
bool RemoteBackend::recv(Json& value)
105
{
583✔
106
  try {
583✔
107
    return connector->recv(value);
583✔
108
  }
583✔
109
  catch (const PDNSException& ex) {
583✔
110
    this->connector.reset();
×
111
    build();
×
112
    throw DBException("Exception caught when receiving: " + ex.reason);
×
113
  }
×
114
  catch (const std::exception& e) {
583✔
115
    this->connector.reset();
×
116
    build();
×
117
    throw DBException("Exception caught when receiving: " + std::string(e.what()));
×
118
  }
×
119
}
583✔
120

121
/**
122
 * Builds connector based on options
123
 * Currently supports unix,pipe and http
124
 */
125
int RemoteBackend::build()
126
{
100✔
127
  std::vector<std::string> parts;
100✔
128
  std::string type;
100✔
129
  std::string opts;
100✔
130
  std::map<std::string, std::string> options;
100✔
131

132
  // connstr is of format "type:options"
133
  size_t pos = 0;
100✔
134
  pos = d_connstr.find_first_of(':');
100✔
135
  if (pos == std::string::npos) {
100!
136
    throw PDNSException("Invalid connection string: malformed");
×
137
  }
×
138

139
  type = d_connstr.substr(0, pos);
100✔
140
  opts = d_connstr.substr(pos + 1);
100✔
141

142
  // tokenize the string on comma
143
  stringtok(parts, opts, ",");
100✔
144

145
  // find out some options and parse them while we're at it
146
  for (const auto& opt : parts) {
156✔
147
    std::string key;
156✔
148
    std::string val;
156✔
149
    // make sure there is something else than air in the option...
150
    if (opt.find_first_not_of(" ") == std::string::npos) {
156!
151
      continue;
×
152
    }
×
153

154
    // split it on '='. if not found, we treat it as "yes"
155
    pos = opt.find_first_of("=");
156✔
156

157
    if (pos == std::string::npos) {
156!
158
      key = opt;
×
159
      val = "yes";
×
160
    }
×
161
    else {
156✔
162
      key = opt.substr(0, pos);
156✔
163
      val = opt.substr(pos + 1);
156✔
164
    }
156✔
165
    options[key] = std::move(val);
156✔
166
  }
156✔
167

168
  // connectors know what they are doing
169
  if (type == "unix") {
100✔
170
    this->connector = std::make_unique<UnixsocketConnector>(options);
25✔
171
  }
25✔
172
  else if (type == "http") {
75✔
173
    this->connector = std::make_unique<HTTPConnector>(options);
25✔
174
  }
25✔
175
  else if (type == "zeromq") {
50✔
176
#ifdef REMOTEBACKEND_ZEROMQ
25✔
177
    this->connector = std::make_unique<ZeroMQConnector>(options);
25✔
178
#else
179
    throw PDNSException("Invalid connection string: zeromq connector support not enabled. Recompile with --enable-remotebackend-zeromq");
180
#endif
181
  }
25✔
182
  else if (type == "pipe") {
25!
183
    this->connector = std::make_unique<PipeConnector>(options);
25✔
184
  }
25✔
185
  else {
×
186
    throw PDNSException("Invalid connection string: unknown connector");
×
187
  }
×
188

189
  return -1;
100✔
190
}
100✔
191

192
/**
193
 * The functions here are just remote json stubs that send and receive the method call
194
 * data is mainly left alone, some defaults are assumed.
195
 */
196
void RemoteBackend::lookup(const QType& qtype, const DNSName& qdomain, int zoneId, DNSPacket* pkt_p)
197
{
148✔
198
  if (d_index != -1) {
148!
199
    throw PDNSException("Attempt to lookup while one running");
×
200
  }
×
201

202
  string localIP = "0.0.0.0";
148✔
203
  string remoteIP = "0.0.0.0";
148✔
204
  string realRemote = "0.0.0.0/0";
148✔
205

206
  if (pkt_p != nullptr) {
148✔
207
    localIP = pkt_p->getLocal().toString();
56✔
208
    realRemote = pkt_p->getRealRemote().toString();
56✔
209
    remoteIP = pkt_p->getInnerRemote().toString();
56✔
210
  }
56✔
211

212
  Json query = Json::object{
148✔
213
    {"method", "lookup"},
148✔
214
    {"parameters", Json::object{{"qtype", qtype.toString()}, {"qname", qdomain.toString()}, {"remote", remoteIP}, {"local", localIP}, {"real-remote", realRemote}, {"zone-id", zoneId}}}};
148✔
215

216
  if (!this->send(query) || !this->recv(d_result)) {
148!
217
    return;
60✔
218
  }
60✔
219

220
  // OK. we have result parameters in result. do not process empty result.
221
  if (!d_result["result"].is_array() || d_result["result"].array_items().empty()) {
88!
222
    return;
×
223
  }
×
224

225
  d_index = 0;
88✔
226
}
88✔
227

228
bool RemoteBackend::list(const DNSName& target, int domain_id, bool include_disabled)
229
{
16✔
230
  if (d_index != -1) {
16!
231
    throw PDNSException("Attempt to lookup while one running");
×
232
  }
×
233

234
  Json query = Json::object{
16✔
235
    {"method", "list"},
16✔
236
    {"parameters", Json::object{{"zonename", target.toString()}, {"domain_id", domain_id}, {"include_disabled", include_disabled}}}};
16✔
237

238
  if (!this->send(query) || !this->recv(d_result)) {
16!
239
    return false;
×
240
  }
×
241
  if (!d_result["result"].is_array() || d_result["result"].array_items().empty()) {
16!
242
    return false;
×
243
  }
×
244

245
  d_index = 0;
16✔
246
  return true;
16✔
247
}
16✔
248

249
bool RemoteBackend::get(DNSResourceRecord& rr)
250
{
468✔
251
  if (d_index == -1) {
468✔
252
    return false;
164✔
253
  }
164✔
254

255
  rr.qtype = stringFromJson(d_result["result"][d_index], "qtype");
304✔
256
  rr.qname = DNSName(stringFromJson(d_result["result"][d_index], "qname"));
304✔
257
  rr.qclass = QClass::IN;
304✔
258
  rr.content = stringFromJson(d_result["result"][d_index], "content");
304✔
259
  rr.ttl = d_result["result"][d_index]["ttl"].int_value();
304✔
260
  rr.domain_id = intFromJson(d_result["result"][d_index], "domain_id", -1);
304✔
261
  if (d_dnssec) {
304✔
262
    rr.auth = (intFromJson(d_result["result"][d_index], "auth", 1) != 0);
208✔
263
  }
208✔
264
  else {
96✔
265
    rr.auth = true;
96✔
266
  }
96✔
267
  rr.scopeMask = d_result["result"][d_index]["scopeMask"].int_value();
304✔
268
  d_index++;
304✔
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())) {
304✔
272
    d_result = Json();
104✔
273
    d_index = -1;
104✔
274
  }
104✔
275
  return true;
304✔
276
}
468✔
277

278
bool RemoteBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
279
{
263✔
280
  // no point doing dnssec if it's not supported
281
  if (!d_dnssec) {
263!
282
    return false;
×
283
  }
×
284

285
  Json query = Json::object{
263✔
286
    {"method", "getBeforeAndAfterNamesAbsolute"},
263✔
287
    {"parameters", Json::object{{"id", Json(static_cast<double>(id))}, {"qname", qname.toString()}}}};
263✔
288
  Json answer;
263✔
289

290
  if (!this->send(query) || !this->recv(answer)) {
263!
291
    return false;
×
292
  }
×
293

294
  unhashed = DNSName(stringFromJson(answer["result"], "unhashed"));
263✔
295
  before.clear();
263✔
296
  after.clear();
263✔
297
  if (answer["result"]["before"] != Json()) {
263✔
298
    before = DNSName(stringFromJson(answer["result"], "before"));
262✔
299
  }
262✔
300
  if (answer["result"]["after"] != Json()) {
263✔
301
    after = DNSName(stringFromJson(answer["result"], "after"));
238✔
302
  }
238✔
303

304
  return true;
263✔
305
}
263✔
306

307
bool RemoteBackend::getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string>>& meta)
308
{
48✔
309
  Json query = Json::object{
48✔
310
    {"method", "getAllDomainMetadata"},
48✔
311
    {"parameters", Json::object{{"name", name.toString()}}}};
48✔
312

313
  if (!this->send(query)) {
48!
314
    return false;
×
315
  }
×
316

317
  meta.clear();
48✔
318

319
  Json answer;
48✔
320
  // not mandatory to implement
321
  if (!this->recv(answer)) {
48!
322
    return true;
48✔
323
  }
48✔
324

325
  for (const auto& pair : answer["result"].object_items()) {
×
326
    if (pair.second.is_array()) {
×
327
      for (const auto& val : pair.second.array_items()) {
×
328
        meta[pair.first].push_back(asString(val));
×
329
      }
×
330
    }
×
331
    else {
×
332
      meta[pair.first].push_back(asString(pair.second));
×
333
    }
×
334
  }
×
335

336
  return true;
×
337
}
48✔
338

339
bool RemoteBackend::getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta)
340
{
16✔
341
  Json query = Json::object{
16✔
342
    {"method", "getDomainMetadata"},
16✔
343
    {"parameters", Json::object{{"name", name.toString()}, {"kind", kind}}}};
16✔
344

345
  if (!this->send(query)) {
16!
346
    return false;
×
347
  }
×
348

349
  meta.clear();
16✔
350

351
  Json answer;
16✔
352
  // not mandatory to implement
353
  if (!this->recv(answer)) {
16!
354
    return true;
16✔
355
  }
16✔
356

357
  if (answer["result"].is_array()) {
×
358
    for (const auto& row : answer["result"].array_items()) {
×
359
      meta.push_back(row.string_value());
×
360
    }
×
361
  }
×
362
  else if (answer["result"].is_string()) {
×
363
    meta.push_back(answer["result"].string_value());
×
364
  }
×
365

366
  return true;
×
367
}
16✔
368

369
bool 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}}}};
×
374

375
  Json answer;
×
376
  if (!this->send(query) || !this->recv(answer)) {
×
377
    return false;
×
378
  }
×
379

380
  return boolFromJson(answer, "result", false);
×
381
}
×
382

383
bool RemoteBackend::getDomainKeys(const DNSName& name, std::vector<DNSBackend::KeyData>& keys)
384
{
48✔
385
  // no point doing dnssec if it's not supported
386
  if (!d_dnssec) {
48✔
387
    return false;
12✔
388
  }
12✔
389

390
  Json query = Json::object{
36✔
391
    {"method", "getDomainKeys"},
36✔
392
    {"parameters", Json::object{{"name", name.toString()}}}};
36✔
393

394
  Json answer;
36✔
395
  if (!this->send(query) || !this->recv(answer)) {
36!
396
    return false;
8✔
397
  }
8✔
398

399
  keys.clear();
28✔
400

401
  for (const auto& jsonKey : answer["result"].array_items()) {
28✔
402
    DNSBackend::KeyData key;
28✔
403
    key.id = intFromJson(jsonKey, "id");
28✔
404
    key.flags = intFromJson(jsonKey, "flags");
28✔
405
    key.active = asBool(jsonKey["active"]);
28✔
406
    key.published = boolFromJson(jsonKey, "published", true);
28✔
407
    key.content = stringFromJson(jsonKey, "content");
28✔
408
    keys.push_back(key);
28✔
409
  }
28✔
410

411
  return true;
28✔
412
}
36✔
413

414
bool RemoteBackend::removeDomainKey(const DNSName& name, unsigned int id)
415
{
×
416
  // no point doing dnssec if it's not supported
417
  if (!d_dnssec) {
×
418
    return false;
×
419
  }
×
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;
×
426
  return this->send(query) && this->recv(answer);
×
427
}
×
428

429
bool RemoteBackend::addDomainKey(const DNSName& name, const KeyData& key, int64_t& id)
430
{
8✔
431
  // no point doing dnssec if it's not supported
432
  if (!d_dnssec) {
8!
433
    return false;
×
434
  }
×
435

436
  Json query = Json::object{
8✔
437
    {"method", "addDomainKey"},
8✔
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}}}}}};
8✔
439

440
  Json answer;
8✔
441
  if (!this->send(query) || !this->recv(answer)) {
8!
442
    return false;
×
443
  }
×
444

445
  id = answer["result"].int_value();
8✔
446
  return id >= 0;
8✔
447
}
8✔
448

449
bool RemoteBackend::activateDomainKey(const DNSName& name, unsigned int id)
450
{
×
451
  // no point doing dnssec if it's not supported
452
  if (!d_dnssec) {
×
453
    return false;
×
454
  }
×
455

456
  Json query = Json::object{
×
457
    {"method", "activateDomainKey"},
×
458
    {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(id)}}}};
×
459

460
  Json answer;
×
461
  return this->send(query) && this->recv(answer);
×
462
}
×
463

464
bool RemoteBackend::deactivateDomainKey(const DNSName& name, unsigned int id)
465
{
×
466
  // no point doing dnssec if it's not supported
467
  if (!d_dnssec) {
×
468
    return false;
×
469
  }
×
470

471
  Json query = Json::object{
×
472
    {"method", "deactivateDomainKey"},
×
473
    {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(id)}}}};
×
474

475
  Json answer;
×
476
  return this->send(query) && this->recv(answer);
×
477
}
×
478

479
bool RemoteBackend::publishDomainKey(const DNSName& name, unsigned int id)
480
{
×
481
  // no point doing dnssec if it's not supported
482
  if (!d_dnssec) {
×
483
    return false;
×
484
  }
×
485

486
  Json query = Json::object{
×
487
    {"method", "publishDomainKey"},
×
488
    {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(id)}}}};
×
489

490
  Json answer;
×
491
  return this->send(query) && this->recv(answer);
×
492
}
×
493

494
bool RemoteBackend::unpublishDomainKey(const DNSName& name, unsigned int id)
495
{
×
496
  // no point doing dnssec if it's not supported
497
  if (!d_dnssec) {
×
498
    return false;
×
499
  }
×
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;
×
506
  return this->send(query) && this->recv(answer);
×
507
}
×
508

509
bool RemoteBackend::doesDNSSEC()
510
{
68✔
511
  return d_dnssec;
68✔
512
}
68✔
513

514
bool RemoteBackend::getTSIGKey(const DNSName& name, DNSName& algorithm, std::string& content)
515
{
×
516
  // no point doing dnssec if it's not supported
517
  if (!d_dnssec) {
×
518
    return false;
×
519
  }
×
520

521
  Json query = Json::object{
×
522
    {"method", "getTSIGKey"},
×
523
    {"parameters", Json::object{{"name", name.toString()}}}};
×
524

525
  Json answer;
×
526
  if (!this->send(query) || !this->recv(answer)) {
×
527
    return false;
×
528
  }
×
529

530
  algorithm = DNSName(stringFromJson(answer["result"], "algorithm"));
×
531
  content = stringFromJson(answer["result"], "content");
×
532

533
  return true;
×
534
}
×
535

536
bool RemoteBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const std::string& content)
537
{
×
538
  // no point doing dnssec if it's not supported
539
  if (!d_dnssec) {
×
540
    return false;
×
541
  }
×
542

543
  Json query = Json::object{
×
544
    {"method", "setTSIGKey"},
×
545
    {"parameters", Json::object{{"name", name.toString()}, {"algorithm", algorithm.toString()}, {"content", content}}}};
×
546

547
  Json answer;
×
548
  return connector->send(query) && connector->recv(answer);
×
549
}
×
550

551
bool RemoteBackend::deleteTSIGKey(const DNSName& name)
552
{
×
553
  // no point doing dnssec if it's not supported
554
  if (!d_dnssec) {
×
555
    return false;
×
556
  }
×
557
  Json query = Json::object{
×
558
    {"method", "deleteTSIGKey"},
×
559
    {"parameters", Json::object{{"name", name.toString()}}}};
×
560

561
  Json answer;
×
562
  return connector->send(query) && connector->recv(answer);
×
563
}
×
564

565
bool RemoteBackend::getTSIGKeys(std::vector<struct TSIGKey>& keys)
566
{
×
567
  // no point doing dnssec if it's not supported
568
  if (!d_dnssec) {
×
569
    return false;
×
570
  }
×
571
  Json query = Json::object{
×
572
    {"method", "getTSIGKeys"},
×
573
    {"parameters", Json::object{}}};
×
574

575
  Json answer;
×
576
  if (!connector->send(query) || !connector->recv(answer)) {
×
577
    return false;
×
578
  }
×
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;
×
589
}
×
590

591
void RemoteBackend::parseDomainInfo(const Json& obj, DomainInfo& di)
592
{
24✔
593
  di.id = intFromJson(obj, "id", -1);
24✔
594
  di.zone = DNSName(stringFromJson(obj, "zone"));
24✔
595
  for (const auto& primary : obj["masters"].array_items()) {
24!
596
    di.primaries.emplace_back(primary.string_value(), 53);
×
597
  }
×
598

599
  di.notified_serial = static_cast<unsigned int>(doubleFromJson(obj, "notified_serial", 0));
24✔
600
  di.serial = static_cast<unsigned int>(obj["serial"].number_value());
24✔
601
  di.last_check = static_cast<time_t>(obj["last_check"].number_value());
24✔
602

603
  string kind;
24✔
604
  if (obj["kind"].is_string()) {
24!
605
    kind = stringFromJson(obj, "kind");
24✔
606
  }
24✔
607
  if (kind == "master") {
24!
608
    di.kind = DomainInfo::Primary;
×
609
  }
×
610
  else if (kind == "slave") {
24!
611
    di.kind = DomainInfo::Secondary;
×
612
  }
×
613
  else {
24✔
614
    di.kind = DomainInfo::Native;
24✔
615
  }
24✔
616
  di.backend = this;
24✔
617
}
24✔
618

619
bool RemoteBackend::getDomainInfo(const DNSName& domain, DomainInfo& di, bool /* getSerial */)
620
{
24✔
621
  if (domain.empty()) {
24!
622
    return false;
×
623
  }
×
624

625
  Json query = Json::object{
24✔
626
    {"method", "getDomainInfo"},
24✔
627
    {"parameters", Json::object{{"name", domain.toString()}}}};
24✔
628

629
  Json answer;
24✔
630
  if (!this->send(query) || !this->recv(answer)) {
24!
631
    return false;
×
632
  }
×
633

634
  this->parseDomainInfo(answer["result"], di);
24✔
635
  return true;
24✔
636
}
24✔
637

638
void 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;
×
645
  if (!this->send(query) || !this->recv(answer)) {
×
646
    g_log << Logger::Error << kBackendId << " Failed to execute RPC for RemoteBackend::setNotified(" << id << "," << serial << ")" << endl;
×
647
  }
×
648
}
×
649

650
bool RemoteBackend::autoPrimaryBackend(const string& ip, const DNSName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** ddb)
651
{
×
652
  Json::array rrset;
×
653

654
  for (const auto& ns : nsset) {
×
655
    rrset.push_back(Json::object{
×
656
      {"qtype", ns.qtype.toString()},
×
657
      {"qname", ns.qname.toString()},
×
658
      {"qclass", QClass::IN.getCode()},
×
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

668
  *ddb = nullptr;
×
669

670
  Json answer;
×
671
  if (!this->send(query) || !this->recv(answer)) {
×
672
    return false;
×
673
  }
×
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;
×
685
}
×
686

687
bool RemoteBackend::createSecondaryDomain(const string& ip, const DNSName& domain, const string& nameserver, const string& account)
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;
×
699
  return this->send(query) && this->recv(answer);
×
700
}
×
701

702
bool 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{
×
707
      {"qtype", rr.qtype.toString()},
×
708
      {"qname", rr.qname.toString()},
×
709
      {"qclass", QClass::IN.getCode()},
×
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"},
×
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}}}};
×
718

719
  Json answer;
×
720
  return this->send(query) && this->recv(answer);
×
721
}
×
722

723
bool RemoteBackend::feedRecord(const DNSResourceRecord& rr, const DNSName& ordername, bool /* ordernameIsNSEC3 */)
724
{
×
725
  Json query = Json::object{
×
726
    {"method", "feedRecord"},
×
727
    {"parameters", Json::object{
×
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())}}},
×
729
                     {"trxid", static_cast<double>(d_trxid)},
×
730
                   }}};
×
731

732
  Json answer;
×
733
  return this->send(query) && this->recv(answer); // XXX FIXME this API should not return 'true' I think -ahu
×
734
}
×
735

736
bool RemoteBackend::feedEnts(int domain_id, map<DNSName, bool>& nonterm)
737
{
×
738
  Json::array nts;
×
739

740
  for (const auto& t : nonterm) {
×
741
    nts.push_back(Json::object{
×
742
      {"nonterm", t.first.toString()},
×
743
      {"auth", t.second}});
×
744
  }
×
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;
×
752
  return this->send(query) && this->recv(answer);
×
753
}
×
754

755
bool RemoteBackend::feedEnts3(int domain_id, const DNSName& domain, map<DNSName, bool>& nonterm, const NSEC3PARAMRecordContent& ns3prc, bool narrow)
756
{
×
757
  Json::array nts;
×
758

759
  for (const auto& t : nonterm) {
×
760
    nts.push_back(Json::object{
×
761
      {"nonterm", t.first.toString()},
×
762
      {"auth", t.second}});
×
763
  }
×
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;
×
771
  return this->send(query) && this->recv(answer);
×
772
}
×
773

774
bool RemoteBackend::startTransaction(const DNSName& domain, int domain_id)
775
{
16✔
776
  this->d_trxid = time((time_t*)nullptr);
16✔
777

778
  Json query = Json::object{
16✔
779
    {"method", "startTransaction"},
16✔
780
    {"parameters", Json::object{{"domain", domain.toString()}, {"domain_id", domain_id}, {"trxid", static_cast<double>(d_trxid)}}}};
16✔
781

782
  Json answer;
16✔
783
  if (!this->send(query) || !this->recv(answer)) {
16!
784
    d_trxid = -1;
16✔
785
    return false;
16✔
786
  }
16✔
787
  return true;
×
788
}
16✔
789

790
bool RemoteBackend::commitTransaction()
791
{
16✔
792
  if (d_trxid == -1) {
16!
793
    return false;
16✔
794
  }
16✔
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;
×
802
  return this->send(query) && this->recv(answer);
×
803
}
16✔
804

805
bool RemoteBackend::abortTransaction()
806
{
×
807
  if (d_trxid == -1) {
×
808
    return false;
×
809
  }
×
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;
×
817
  return this->send(query) && this->recv(answer);
×
818
}
×
819

820
string RemoteBackend::directBackendCmd(const string& querystr)
821
{
8✔
822
  Json query = Json::object{
8✔
823
    {"method", "directBackendCmd"},
8✔
824
    {"parameters", Json::object{{"query", querystr}}}};
8✔
825

826
  Json answer;
8✔
827
  if (!this->send(query) || !this->recv(answer)) {
8!
828
    return "backend command failed";
×
829
  }
×
830

831
  return asString(answer["result"]);
8✔
832
}
8✔
833

834
bool RemoteBackend::searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result)
835
{
×
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

841
  Json query = Json::object{
×
842
    {"method", "searchRecords"},
×
843
    {"parameters", Json::object{{"pattern", pattern}, {"maxResults", static_cast<int>(maxResults)}}}};
×
844

845
  Json answer;
×
846
  if (!this->send(query) || !this->recv(answer)) {
×
847
    return false;
×
848
  }
×
849

850
  if (!answer["result"].is_array()) {
×
851
    return false;
×
852
  }
×
853

854
  for (const auto& row : answer["result"].array_items()) {
×
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");
×
860
    rr.ttl = row["ttl"].int_value();
×
861
    rr.domain_id = intFromJson(row, "domain_id", -1);
×
862
    if (d_dnssec) {
×
863
      rr.auth = (intFromJson(row, "auth", 1) != 0);
×
864
    }
×
865
    else {
×
866
      rr.auth = 1;
×
867
    }
×
868
    rr.scopeMask = row["scopeMask"].int_value();
×
869
    result.push_back(rr);
×
870
  }
×
871

872
  return true;
×
873
}
×
874

875
bool RemoteBackend::searchComments(const string& /* pattern */, size_t /* maxResults */, vector<Comment>& /* result */)
876
{
×
877
  // FIXME: Implement Comment API
878
  return false;
×
879
}
×
880

881
void RemoteBackend::getAllDomains(vector<DomainInfo>* domains, bool /* getSerial */, bool include_disabled)
882
{
×
883
  Json query = Json::object{
×
884
    {"method", "getAllDomains"},
×
885
    {"parameters", Json::object{{"include_disabled", include_disabled}}}};
×
886

887
  Json answer;
×
888
  if (!this->send(query) || !this->recv(answer)) {
×
889
    return;
×
890
  }
×
891

892
  if (!answer["result"].is_array()) {
×
893
    return;
×
894
  }
×
895

896
  for (const auto& row : answer["result"].array_items()) {
×
897
    DomainInfo di;
×
898
    this->parseDomainInfo(row, di);
×
899
    domains->push_back(di);
×
900
  }
×
901
}
×
902

903
void RemoteBackend::getUpdatedPrimaries(vector<DomainInfo>& domains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
904
{
×
905
  Json query = Json::object{
×
906
    {"method", "getUpdatedMasters"},
×
907
    {"parameters", Json::object{}},
×
908
  };
×
909

910
  Json answer;
×
911
  if (!this->send(query) || !this->recv(answer)) {
×
912
    return;
×
913
  }
×
914

915
  if (!answer["result"].is_array()) {
×
916
    return;
×
917
  }
×
918

919
  for (const auto& row : answer["result"].array_items()) {
×
920
    DomainInfo di;
×
921
    this->parseDomainInfo(row, di);
×
922
    domains.push_back(di);
×
923
  }
×
924
}
×
925

926
void RemoteBackend::getUnfreshSecondaryInfos(vector<DomainInfo>* domains)
927
{
×
928
  Json query = Json::object{
×
929
    {"method", "getUnfreshSlaveInfos"},
×
930
    {"parameters", Json::object{}},
×
931
  };
×
932

933
  Json answer;
×
934
  if (!this->send(query) || !this->recv(answer)) {
×
935
    return;
×
936
  }
×
937

938
  if (!answer["result"].is_array()) {
×
939
    return;
×
940
  }
×
941

942
  for (const auto& row : answer["result"].array_items()) {
×
943
    DomainInfo di;
×
944
    this->parseDomainInfo(row, di);
×
945
    domains->push_back(di);
×
946
  }
×
947
}
×
948

949
void 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;
×
956
  if (!this->send(query) || !this->recv(answer)) {
×
957
    g_log << Logger::Error << kBackendId << " Failed to execute RPC for RemoteBackend::setStale(" << domain_id << ")" << endl;
×
958
  }
×
959
}
×
960

961
void 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;
×
968
  if (!this->send(query) || !this->recv(answer)) {
×
969
    g_log << Logger::Error << kBackendId << " Failed to execute RPC for RemoteBackend::setFresh(" << domain_id << ")" << endl;
×
970
  }
×
971
}
×
972

973
DNSBackend* RemoteBackend::maker()
974
{
×
975
  try {
×
976
    return new RemoteBackend();
×
977
  }
×
978
  catch (...) {
×
979
    g_log << Logger::Error << kBackendId << " Unable to instantiate a remotebackend!" << endl;
×
980
    return nullptr;
×
981
  };
×
982
}
×
983

984
class RemoteBackendFactory : public BackendFactory
985
{
986
public:
987
  RemoteBackendFactory() :
988
    BackendFactory("remote") {}
376✔
989

990
  void declareArguments(const std::string& suffix = "") override
991
  {
32✔
992
    declare(suffix, "dnssec", "Enable dnssec support", "no");
32✔
993
    declare(suffix, "connection-string", "Connection string", "");
32✔
994
  }
32✔
995

996
  DNSBackend* make(const std::string& suffix = "") override
997
  {
100✔
998
    return new RemoteBackend(suffix);
100✔
999
  }
100✔
1000
};
1001

1002
class RemoteLoader
1003
{
1004
public:
1005
  RemoteLoader();
1006
};
1007

1008
RemoteLoader::RemoteLoader()
1009
{
376✔
1010
  BackendMakers().report(std::make_unique<RemoteBackendFactory>());
376✔
1011
  g_log << Logger::Info << kBackendId << " This is the remote backend version " VERSION
376✔
1012
#ifndef REPRODUCIBLE
376✔
1013
        << " (" __DATE__ " " __TIME__ ")"
376✔
1014
#endif
376✔
1015
        << " reporting" << endl;
376✔
1016
}
376✔
1017

1018
static RemoteLoader remoteloader;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc