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

PowerDNS / pdns / 20618548088

31 Dec 2025 12:00PM UTC coverage: 72.648% (-0.7%) from 73.336%
20618548088

Pull #16693

github

web-flow
Merge 3f7d9a75b into 65de281db
Pull Request #16693: auth: plumbing for structured logging

39009 of 65430 branches covered (59.62%)

Branch coverage included in aggregate %.

807 of 2400 new or added lines in 58 files covered. (33.63%)

200 existing lines in 39 files now uncovered.

129187 of 166092 relevant lines covered (77.78%)

5266744.49 hits per line

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

34.63
/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
// The following requirement guarantees UnknownDomainID will get output as "-1"
29
// in the Json data for compatibility.
30
static_assert(std::is_signed<domainid_t>::value);
31

32
// The following requirement is a consequency of JsonInt not able to store
33
// anything larger than "int".
34
static_assert(sizeof(domainid_t) <= sizeof(int));
35

36
static const char* kBackendId = "[RemoteBackend]";
37

38
/**
39
 * Forwarder for value. This is just in case
40
 * we need to do some treatment to the value before
41
 * sending it downwards.
42
 */
43
bool Connector::send(Json& value)
44
{
640✔
45
  return send_message(value) > 0;
640✔
46
}
640✔
47

48
/**
49
 * Helper for handling receiving of data.
50
 * Basically what happens here is that we check
51
 * that the receiving happened ok, and extract
52
 * result. Logging is performed here, too.
53
 */
54
bool Connector::recv(Json& value)
55
{
640✔
56
  if (recv_message(value) > 0) {
640!
57
    bool retval = true;
640✔
58
    if (value["result"] == Json()) {
640!
59
      throw PDNSException("No 'result' field in response from remote process");
×
60
    }
×
61
    if (value["result"].is_bool() && !boolFromJson(value, "result", false)) {
640✔
62
      retval = false;
24✔
63
    }
24✔
64
    for (const auto& message : value["log"].array_items()) {
640✔
65
      SLOG(g_log << Logger::Info << "[remotebackend]: " << message.string_value() << std::endl,
103!
66
           d_slog->info(Logr::Info, "received", "message", Logging::Loggable(message.string_value())));
103✔
67
    }
103✔
68
    return retval;
640✔
69
  }
640✔
70
  throw PDNSException("Unknown error while receiving data");
×
71
}
640✔
72

73
void RemoteBackend::makeErrorAndThrow(Json& value)
74
{
×
75
  std::string msg = "Remote process indicated a failure";
×
76
  for (const auto& message : value["log"].array_items()) {
×
77
    msg += " '" + message.string_value() + "'";
×
78
  }
×
79
  throw PDNSException(msg);
×
80
}
×
81

82
/**
83
 * Standard ctor and dtor
84
 */
85
RemoteBackend::RemoteBackend(const std::string& suffix)
86
{
92✔
87
  setArgPrefix("remote" + suffix);
92✔
88

89
  this->d_connstr = getArg("connection-string");
92✔
90
  this->d_dnssec = mustDo("dnssec");
92✔
91

92
  build();
92✔
93
}
92✔
94

95
RemoteBackend::~RemoteBackend() = default;
76✔
96

97
bool RemoteBackend::send(Json& value)
98
{
581✔
99
  try {
581✔
100
    if (!connector->send(value)) {
581!
101
      // XXX does this work even though we throw?
102
      this->connector.reset();
×
103
      build();
×
104
      throw DBException("Could not send a message to remote process");
×
105
    }
×
106
  }
581✔
107
  catch (const PDNSException& ex) {
581✔
108
    throw DBException("Exception caught when sending: " + ex.reason);
×
109
  }
×
110
  return true;
581✔
111
}
581✔
112

113
bool RemoteBackend::recv(Json& value)
114
{
581✔
115
  try {
581✔
116
    return connector->recv(value);
581✔
117
  }
581✔
118
  catch (const PDNSException& ex) {
581✔
119
    this->connector.reset();
×
120
    build();
×
121
    throw DBException("Exception caught when receiving: " + ex.reason);
×
122
  }
×
123
  catch (const std::exception& e) {
581✔
124
    this->connector.reset();
×
125
    build();
×
126
    throw DBException("Exception caught when receiving: " + std::string(e.what()));
×
127
  }
×
128
}
581✔
129

130
/**
131
 * Builds connector based on options
132
 * Currently supports unix,pipe and http
133
 */
134
int RemoteBackend::build()
135
{
92✔
136
  std::vector<std::string> parts;
92✔
137
  std::string type;
92✔
138
  std::string opts;
92✔
139
  std::map<std::string, std::string> options;
92✔
140

141
  // connstr is of format "type:options"
142
  size_t pos = 0;
92✔
143
  pos = d_connstr.find_first_of(':');
92✔
144
  if (pos == std::string::npos) {
92!
145
    throw PDNSException("Invalid connection string: malformed");
×
146
  }
×
147

148
  type = d_connstr.substr(0, pos);
92✔
149
  opts = d_connstr.substr(pos + 1);
92✔
150

151
  // tokenize the string on comma
152
  stringtok(parts, opts, ",");
92✔
153

154
  // find out some options and parse them while we're at it
155
  for (const auto& opt : parts) {
140✔
156
    std::string key;
140✔
157
    std::string val;
140✔
158
    // make sure there is something else than air in the option...
159
    if (opt.find_first_not_of(" ") == std::string::npos) {
140!
160
      continue;
×
161
    }
×
162

163
    // split it on '='. if not found, we treat it as "yes"
164
    pos = opt.find_first_of("=");
140✔
165

166
    if (pos == std::string::npos) {
140!
167
      key = opt;
×
168
      val = "yes";
×
169
    }
×
170
    else {
140✔
171
      key = opt.substr(0, pos);
140✔
172
      val = opt.substr(pos + 1);
140✔
173
    }
140✔
174
    options[key] = std::move(val);
140✔
175
  }
140✔
176

177
  // connectors know what they are doing
178
  if (type == "unix") {
92✔
179
    this->connector = std::make_unique<UnixsocketConnector>(d_slog, options);
23✔
180
  }
23✔
181
  else if (type == "http") {
69✔
182
    this->connector = std::make_unique<HTTPConnector>(d_slog, options);
23✔
183
  }
23✔
184
  else if (type == "zeromq") {
46✔
185
#ifdef REMOTEBACKEND_ZEROMQ
23✔
186
    this->connector = std::make_unique<ZeroMQConnector>(d_slog, options);
23✔
187
#else
188
    throw PDNSException("Invalid connection string: zeromq connector support not enabled. Recompile with --enable-remotebackend-zeromq");
189
#endif
190
  }
23✔
191
  else if (type == "pipe") {
23!
192
    this->connector = std::make_unique<PipeConnector>(d_slog, options);
23✔
193
  }
23✔
194
  else {
×
195
    throw PDNSException("Invalid connection string: unknown connector");
×
196
  }
×
197

198
  return -1;
92✔
199
}
92✔
200

201
/**
202
 * The functions here are just remote json stubs that send and receive the method call
203
 * data is mainly left alone, some defaults are assumed.
204
 */
205
void RemoteBackend::lookup(const QType& qtype, const DNSName& qdomain, domainid_t zoneId, DNSPacket* pkt_p)
206
{
148✔
207
  if (d_index != -1) {
148!
208
    throw PDNSException("Attempt to lookup while one running");
×
209
  }
×
210

211
  string localIP = "0.0.0.0";
148✔
212
  string remoteIP = "0.0.0.0";
148✔
213
  string realRemote = "0.0.0.0/0";
148✔
214

215
  if (pkt_p != nullptr) {
148✔
216
    localIP = pkt_p->getLocal().toString();
56✔
217
    realRemote = pkt_p->getRealRemote().toString();
56✔
218
    remoteIP = pkt_p->getInnerRemote().toString();
56✔
219
  }
56✔
220

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

225
  if (!this->send(query) || !this->recv(d_result)) {
148!
226
    return;
×
227
  }
×
228

229
  // OK. we have result parameters in result. do not process empty result.
230
  if (!d_result["result"].is_array() || d_result["result"].array_items().empty()) {
148!
231
    return;
60✔
232
  }
60✔
233

234
  d_index = 0;
88✔
235
}
88✔
236

237
// Similar to lookup above, but passes an extra include_disabled parameter.
238
void RemoteBackend::APILookup(const QType& qtype, const DNSName& qdomain, domainid_t zoneId, bool include_disabled)
239
{
×
240
  if (d_index != -1) {
×
241
    throw PDNSException("Attempt to lookup while one running");
×
242
  }
×
243

244
  string localIP = "0.0.0.0";
×
245
  string remoteIP = "0.0.0.0";
×
246
  string realRemote = "0.0.0.0/0";
×
247

248
  Json query = Json::object{
×
249
    {"method", "APILookup"},
×
250
    {"parameters", Json::object{{"qtype", qtype.toString()}, {"qname", qdomain.toString()}, {"remote", remoteIP}, {"local", localIP}, {"real-remote", realRemote}, {"zone-id", zoneId}, {"include-disabled", include_disabled}}}};
×
251

252
  if (!this->send(query) || !this->recv(d_result)) {
×
253
    return;
×
254
  }
×
255

256
  // OK. we have result parameters in result. do not process empty result.
257
  if (!d_result["result"].is_array() || d_result["result"].array_items().empty()) {
×
258
    return;
×
259
  }
×
260

261
  d_index = 0;
×
262
}
×
263

264
bool RemoteBackend::list(const ZoneName& target, domainid_t domain_id, bool include_disabled)
265
{
16✔
266
  if (d_index != -1) {
16!
267
    throw PDNSException("Attempt to lookup while one running");
×
268
  }
×
269

270
  Json query = Json::object{
16✔
271
    {"method", "list"},
16✔
272
    {"parameters", Json::object{{"zonename", target.toString()}, {"domain_id", domain_id}, {"include_disabled", include_disabled}}}};
16✔
273

274
  if (!this->send(query) || !this->recv(d_result)) {
16!
275
    return false;
×
276
  }
×
277
  if (!d_result["result"].is_array() || d_result["result"].array_items().empty()) {
16!
278
    return false;
×
279
  }
×
280

281
  d_index = 0;
16✔
282
  return true;
16✔
283
}
16✔
284

285
bool RemoteBackend::get(DNSResourceRecord& rr)
286
{
478✔
287
  if (d_index == -1) {
478✔
288
    return false;
164✔
289
  }
164✔
290

291
  rr.qtype = stringFromJson(d_result["result"][d_index], "qtype");
314✔
292
  rr.qname = DNSName(stringFromJson(d_result["result"][d_index], "qname"));
314✔
293
  rr.qclass = QClass::IN;
314✔
294
  rr.content = stringFromJson(d_result["result"][d_index], "content");
314✔
295
  rr.ttl = d_result["result"][d_index]["ttl"].int_value();
314✔
296
  rr.domain_id = static_cast<domainid_t>(intFromJson(d_result["result"][d_index], "domain_id", UnknownDomainID));
314✔
297
  if (d_dnssec) {
314✔
298
    rr.auth = (intFromJson(d_result["result"][d_index], "auth", 1) != 0);
216✔
299
  }
216✔
300
  else {
98✔
301
    rr.auth = true;
98✔
302
  }
98✔
303
  rr.scopeMask = d_result["result"][d_index]["scopeMask"].int_value();
314✔
304
  d_index++;
314✔
305

306
  // id index is out of bounds, we know the results end here.
307
  if (d_index == static_cast<int>(d_result["result"].array_items().size())) {
314✔
308
    d_result = Json();
104✔
309
    d_index = -1;
104✔
310
  }
104✔
311
  return true;
314✔
312
}
478✔
313

314
// NOLINTNEXTLINE(readability-identifier-length)
315
bool RemoteBackend::getBeforeAndAfterNamesAbsolute(domainid_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
316
{
261✔
317
  // no point doing dnssec if it's not supported
318
  if (!d_dnssec) {
261!
319
    return false;
×
320
  }
×
321

322
  Json query = Json::object{
261✔
323
    {"method", "getBeforeAndAfterNamesAbsolute"},
261✔
324
    {"parameters", Json::object{{"id", Json(id)}, {"qname", qname.toString()}}}};
261✔
325
  Json answer;
261✔
326

327
  if (!this->send(query) || !this->recv(answer)) {
261!
328
    return false;
×
329
  }
×
330

331
  unhashed = DNSName(stringFromJson(answer["result"], "unhashed"));
261✔
332
  before.clear();
261✔
333
  after.clear();
261✔
334
  if (answer["result"]["before"] != Json()) {
261!
335
    before = DNSName(stringFromJson(answer["result"], "before"));
261✔
336
  }
261✔
337
  if (answer["result"]["after"] != Json()) {
261!
338
    after = DNSName(stringFromJson(answer["result"], "after"));
261✔
339
  }
261✔
340

341
  return true;
261✔
342
}
261✔
343

344
bool RemoteBackend::getAllDomainMetadata(const ZoneName& name, std::map<std::string, std::vector<std::string>>& meta)
345
{
48✔
346
  Json query = Json::object{
48✔
347
    {"method", "getAllDomainMetadata"},
48✔
348
    {"parameters", Json::object{{"name", name.toString()}}}};
48✔
349

350
  if (!this->send(query)) {
48!
351
    return false;
×
352
  }
×
353

354
  meta.clear();
48✔
355

356
  Json answer;
48✔
357
  // not mandatory to implement
358
  if (!this->recv(answer)) {
48!
359
    return true;
×
360
  }
×
361

362
  for (const auto& pair : answer["result"].object_items()) {
48!
363
    if (pair.second.is_array()) {
×
364
      for (const auto& val : pair.second.array_items()) {
×
365
        meta[pair.first].push_back(asString(val));
×
366
      }
×
367
    }
×
368
    else {
×
369
      meta[pair.first].push_back(asString(pair.second));
×
370
    }
×
371
  }
×
372

373
  return true;
48✔
374
}
48✔
375

376
bool RemoteBackend::getDomainMetadata(const ZoneName& name, const std::string& kind, std::vector<std::string>& meta)
377
{
16✔
378
  Json query = Json::object{
16✔
379
    {"method", "getDomainMetadata"},
16✔
380
    {"parameters", Json::object{{"name", name.toString()}, {"kind", kind}}}};
16✔
381

382
  if (!this->send(query)) {
16!
383
    return false;
×
384
  }
×
385

386
  meta.clear();
16✔
387

388
  Json answer;
16✔
389
  // not mandatory to implement
390
  if (!this->recv(answer)) {
16!
391
    return true;
×
392
  }
×
393

394
  if (answer["result"].is_array()) {
16!
395
    for (const auto& row : answer["result"].array_items()) {
16!
396
      meta.push_back(row.string_value());
×
397
    }
×
398
  }
16✔
399
  else if (answer["result"].is_string()) {
×
400
    meta.push_back(answer["result"].string_value());
×
401
  }
×
402

403
  return true;
16✔
404
}
16✔
405

406
bool RemoteBackend::setDomainMetadata(const ZoneName& name, const std::string& kind, const std::vector<std::string>& meta)
407
{
×
408
  Json query = Json::object{
×
409
    {"method", "setDomainMetadata"},
×
410
    {"parameters", Json::object{{"name", name.toString()}, {"kind", kind}, {"value", meta}}}};
×
411

412
  Json answer;
×
413
  if (!this->send(query) || !this->recv(answer)) {
×
414
    return false;
×
415
  }
×
416

417
  return boolFromJson(answer, "result", false);
×
418
}
×
419

420
bool RemoteBackend::getDomainKeys(const ZoneName& name, std::vector<DNSBackend::KeyData>& keys)
421
{
48✔
422
  // no point doing dnssec if it's not supported
423
  if (!d_dnssec) {
48✔
424
    return false;
12✔
425
  }
12✔
426

427
  Json query = Json::object{
36✔
428
    {"method", "getDomainKeys"},
36✔
429
    {"parameters", Json::object{{"name", name.toString()}}}};
36✔
430

431
  Json answer;
36✔
432
  if (!this->send(query) || !this->recv(answer)) {
36!
433
    return false;
8✔
434
  }
8✔
435

436
  keys.clear();
28✔
437

438
  for (const auto& jsonKey : answer["result"].array_items()) {
28✔
439
    DNSBackend::KeyData key;
28✔
440
    key.id = intFromJson(jsonKey, "id");
28✔
441
    key.flags = intFromJson(jsonKey, "flags");
28✔
442
    key.active = asBool(jsonKey["active"]);
28✔
443
    key.published = boolFromJson(jsonKey, "published", true);
28✔
444
    key.content = stringFromJson(jsonKey, "content");
28✔
445
    keys.emplace_back(std::move(key));
28✔
446
  }
28✔
447

448
  return true;
28✔
449
}
36✔
450

451
bool RemoteBackend::removeDomainKey(const ZoneName& name, unsigned int keyId)
452
{
×
453
  // no point doing dnssec if it's not supported
454
  if (!d_dnssec) {
×
455
    return false;
×
456
  }
×
457

458
  Json query = Json::object{
×
459
    {"method", "removeDomainKey"},
×
460
    {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(keyId)}}}};
×
461

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

466
bool RemoteBackend::addDomainKey(const ZoneName& name, const KeyData& key, int64_t& keyId)
467
{
8✔
468
  // no point doing dnssec if it's not supported
469
  if (!d_dnssec) {
8!
470
    return false;
×
471
  }
×
472

473
  Json query = Json::object{
8✔
474
    {"method", "addDomainKey"},
8✔
475
    {"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✔
476

477
  Json answer;
8✔
478
  if (!this->send(query) || !this->recv(answer)) {
8!
479
    return false;
×
480
  }
×
481

482
  keyId = answer["result"].int_value();
8✔
483
  return keyId >= 0;
8✔
484
}
8✔
485

486
bool RemoteBackend::activateDomainKey(const ZoneName& name, unsigned int keyId)
487
{
×
488
  // no point doing dnssec if it's not supported
489
  if (!d_dnssec) {
×
490
    return false;
×
491
  }
×
492

493
  Json query = Json::object{
×
494
    {"method", "activateDomainKey"},
×
495
    {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(keyId)}}}};
×
496

497
  Json answer;
×
498
  return this->send(query) && this->recv(answer);
×
499
}
×
500

501
bool RemoteBackend::deactivateDomainKey(const ZoneName& name, unsigned int keyId)
502
{
×
503
  // no point doing dnssec if it's not supported
504
  if (!d_dnssec) {
×
505
    return false;
×
506
  }
×
507

508
  Json query = Json::object{
×
509
    {"method", "deactivateDomainKey"},
×
510
    {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(keyId)}}}};
×
511

512
  Json answer;
×
513
  return this->send(query) && this->recv(answer);
×
514
}
×
515

516
bool RemoteBackend::publishDomainKey(const ZoneName& name, unsigned int keyId)
517
{
×
518
  // no point doing dnssec if it's not supported
519
  if (!d_dnssec) {
×
520
    return false;
×
521
  }
×
522

523
  Json query = Json::object{
×
524
    {"method", "publishDomainKey"},
×
525
    {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(keyId)}}}};
×
526

527
  Json answer;
×
528
  return this->send(query) && this->recv(answer);
×
529
}
×
530

531
bool RemoteBackend::unpublishDomainKey(const ZoneName& name, unsigned int keyId)
532
{
×
533
  // no point doing dnssec if it's not supported
534
  if (!d_dnssec) {
×
535
    return false;
×
536
  }
×
537

538
  Json query = Json::object{
×
539
    {"method", "unpublishDomainKey"},
×
540
    {"parameters", Json::object{{"name", name.toString()}, {"id", static_cast<int>(keyId)}}}};
×
541

542
  Json answer;
×
543
  return this->send(query) && this->recv(answer);
×
544
}
×
545

546
unsigned int RemoteBackend::getCapabilities()
547
{
233✔
548
  unsigned int caps = CAP_DIRECT | CAP_LIST | CAP_SEARCH;
233✔
549
  if (d_dnssec) {
233✔
550
    caps |= CAP_DNSSEC;
229✔
551
  }
229✔
552
  return caps;
233✔
553
}
233✔
554

555
bool RemoteBackend::getTSIGKey(const DNSName& name, DNSName& algorithm, std::string& content)
556
{
×
557
  // no point doing dnssec if it's not supported
558
  if (!d_dnssec) {
×
559
    return false;
×
560
  }
×
561

562
  Json query = Json::object{
×
563
    {"method", "getTSIGKey"},
×
564
    {"parameters", Json::object{{"name", name.toString()}}}};
×
565

566
  Json answer;
×
567
  if (!this->send(query) || !this->recv(answer)) {
×
568
    return false;
×
569
  }
×
570

571
  algorithm = DNSName(stringFromJson(answer["result"], "algorithm"));
×
572
  content = stringFromJson(answer["result"], "content");
×
573

574
  return true;
×
575
}
×
576

577
bool RemoteBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const std::string& content)
578
{
×
579
  // no point doing dnssec if it's not supported
580
  if (!d_dnssec) {
×
581
    return false;
×
582
  }
×
583

584
  Json query = Json::object{
×
585
    {"method", "setTSIGKey"},
×
586
    {"parameters", Json::object{{"name", name.toString()}, {"algorithm", algorithm.toString()}, {"content", content}}}};
×
587

588
  Json answer;
×
589
  return connector->send(query) && connector->recv(answer);
×
590
}
×
591

592
bool RemoteBackend::deleteTSIGKey(const DNSName& name)
593
{
×
594
  // no point doing dnssec if it's not supported
595
  if (!d_dnssec) {
×
596
    return false;
×
597
  }
×
598
  Json query = Json::object{
×
599
    {"method", "deleteTSIGKey"},
×
600
    {"parameters", Json::object{{"name", name.toString()}}}};
×
601

602
  Json answer;
×
603
  return connector->send(query) && connector->recv(answer);
×
604
}
×
605

606
bool RemoteBackend::getTSIGKeys(std::vector<struct TSIGKey>& keys)
607
{
×
608
  // no point doing dnssec if it's not supported
609
  if (!d_dnssec) {
×
610
    return false;
×
611
  }
×
612
  Json query = Json::object{
×
613
    {"method", "getTSIGKeys"},
×
614
    {"parameters", Json::object{}}};
×
615

616
  Json answer;
×
617
  if (!connector->send(query) || !connector->recv(answer)) {
×
618
    return false;
×
619
  }
×
620

621
  for (const auto& jsonKey : answer["result"].array_items()) {
×
622
    struct TSIGKey key;
×
623
    key.name = DNSName(stringFromJson(jsonKey, "name"));
×
624
    key.algorithm = DNSName(stringFromJson(jsonKey, "algorithm"));
×
625
    key.key = stringFromJson(jsonKey, "content");
×
626
    keys.push_back(key);
×
627
  }
×
628

629
  return true;
×
630
}
×
631

632
void RemoteBackend::parseDomainInfo(const Json& obj, DomainInfo& di)
633
{
24✔
634
  di.id = static_cast<domainid_t>(intFromJson(obj, "id", UnknownDomainID));
24✔
635
  di.zone = ZoneName(stringFromJson(obj, "zone"));
24✔
636
  for (const auto& primary : obj["masters"].array_items()) {
24!
637
    di.primaries.emplace_back(primary.string_value(), 53);
×
638
  }
×
639

640
  di.notified_serial = static_cast<unsigned int>(doubleFromJson(obj, "notified_serial", 0));
24✔
641
  di.serial = static_cast<unsigned int>(obj["serial"].number_value());
24✔
642
  di.last_check = static_cast<time_t>(obj["last_check"].number_value());
24✔
643

644
  string kind;
24✔
645
  if (obj["kind"].is_string()) {
24!
646
    kind = stringFromJson(obj, "kind");
24✔
647
  }
24✔
648
  if (kind == "master") {
24!
649
    di.kind = DomainInfo::Primary;
×
650
  }
×
651
  else if (kind == "slave") {
24!
652
    di.kind = DomainInfo::Secondary;
×
653
  }
×
654
  else {
24✔
655
    di.kind = DomainInfo::Native;
24✔
656
  }
24✔
657
  di.backend = this;
24✔
658
}
24✔
659

660
bool RemoteBackend::getDomainInfo(const ZoneName& domain, DomainInfo& info, bool /* getSerial */)
661
{
24✔
662
  if (domain.empty()) {
24!
663
    return false;
×
664
  }
×
665

666
  Json query = Json::object{
24✔
667
    {"method", "getDomainInfo"},
24✔
668
    {"parameters", Json::object{{"name", domain.toString()}}}};
24✔
669

670
  Json answer;
24✔
671
  if (!this->send(query) || !this->recv(answer)) {
24!
672
    return false;
×
673
  }
×
674

675
  this->parseDomainInfo(answer["result"], info);
24✔
676
  return true;
24✔
677
}
24✔
678

679
// NOLINTNEXTLINE(readability-identifier-length)
680
void RemoteBackend::setNotified(domainid_t id, uint32_t serial)
681
{
×
682
  Json query = Json::object{
×
683
    {"method", "setNotified"},
×
684
    {"parameters", Json::object{{"id", id}, {"serial", static_cast<double>(serial)}}}};
×
685

686
  Json answer;
×
687
  if (!this->send(query) || !this->recv(answer)) {
×
NEW
688
    SLOG(g_log << Logger::Error << kBackendId << " Failed to execute RPC for RemoteBackend::setNotified(" << id << "," << serial << ")" << endl,
×
NEW
689
         d_slog->info(Logr::Error, "Failed to execute RPC for RemoteBackend::setNotified", "domain_id", Logging::Loggable(id), "serial", Logging::Loggable(serial)));
×
690
  }
×
691
}
×
692

693
bool RemoteBackend::autoPrimaryBackend(const string& ipAddress, const ZoneName& domain, const vector<DNSResourceRecord>& nsset, string* nameserver, string* account, DNSBackend** ddb)
694
{
×
695
  Json::array rrset;
×
696

697
  for (const auto& ns : nsset) {
×
698
    rrset.push_back(Json::object{
×
699
      {"qtype", ns.qtype.toString()},
×
700
      {"qname", ns.qname.toString()},
×
701
      {"qclass", QClass::IN.getCode()},
×
702
      {"content", ns.content},
×
703
      {"ttl", static_cast<int>(ns.ttl)},
×
704
      {"auth", ns.auth}});
×
705
  }
×
706

707
  Json query = Json::object{
×
708
    {"method", "superMasterBackend"},
×
709
    {"parameters", Json::object{{"ip", ipAddress}, {"domain", domain.toString()}, {"nsset", rrset}}}};
×
710

711
  *ddb = nullptr;
×
712

713
  Json answer;
×
714
  if (!this->send(query) || !this->recv(answer)) {
×
715
    return false;
×
716
  }
×
717

718
  // we are the backend
719
  *ddb = this;
×
720

721
  // we allow simple true as well...
722
  if (answer["result"].is_object()) {
×
723
    *account = stringFromJson(answer["result"], "account");
×
724
    *nameserver = stringFromJson(answer["result"], "nameserver");
×
725
  }
×
726

727
  return true;
×
728
}
×
729

730
bool RemoteBackend::createSecondaryDomain(const string& ipAddress, const ZoneName& domain, const string& nameserver, const string& account)
731
{
×
732
  Json query = Json::object{
×
733
    {"method", "createSlaveDomain"},
×
734
    {"parameters", Json::object{
×
735
                     {"ip", ipAddress},
×
736
                     {"domain", domain.toString()},
×
737
                     {"nameserver", nameserver},
×
738
                     {"account", account},
×
739
                   }}};
×
740

741
  Json answer;
×
742
  return this->send(query) && this->recv(answer);
×
743
}
×
744

745
bool RemoteBackend::replaceRRSet(domainid_t domain_id, const DNSName& qname, const QType& qtype, const vector<DNSResourceRecord>& rrset)
746
{
×
747
  Json::array json_rrset;
×
748
  for (const auto& rr : rrset) {
×
749
    json_rrset.push_back(Json::object{
×
750
      {"qtype", rr.qtype.toString()},
×
751
      {"qname", rr.qname.toString()},
×
752
      {"qclass", QClass::IN.getCode()},
×
753
      {"content", rr.content},
×
754
      {"ttl", static_cast<int>(rr.ttl)},
×
755
      {"auth", rr.auth}});
×
756
  }
×
757

758
  Json query = Json::object{
×
759
    {"method", "replaceRRSet"},
×
760
    {"parameters", Json::object{{"domain_id", domain_id}, {"qname", qname.toString()}, {"qtype", qtype.toString()}, {"trxid", static_cast<double>(d_trxid)}, {"rrset", json_rrset}}}};
×
761

762
  Json answer;
×
763
  return this->send(query) && this->recv(answer);
×
764
}
×
765

766
bool RemoteBackend::feedRecord(const DNSResourceRecord& rr, const DNSName& ordername, bool /* ordernameIsNSEC3 */)
767
{
×
768
  Json query = Json::object{
×
769
    {"method", "feedRecord"},
×
770
    {"parameters", Json::object{
×
771
                     {"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())}}},
×
772
                     {"trxid", static_cast<double>(d_trxid)},
×
773
                   }}};
×
774

775
  Json answer;
×
776
  return this->send(query) && this->recv(answer); // XXX FIXME this API should not return 'true' I think -ahu
×
777
}
×
778

779
bool RemoteBackend::feedEnts(domainid_t domain_id, map<DNSName, bool>& nonterm)
780
{
×
781
  Json::array nts;
×
782

783
  for (const auto& t : nonterm) {
×
784
    nts.push_back(Json::object{
×
785
      {"nonterm", t.first.toString()},
×
786
      {"auth", t.second}});
×
787
  }
×
788

789
  Json query = Json::object{
×
790
    {"method", "feedEnts"},
×
791
    {"parameters", Json::object{{"domain_id", domain_id}, {"trxid", static_cast<double>(d_trxid)}, {"nonterm", nts}}},
×
792
  };
×
793

794
  Json answer;
×
795
  return this->send(query) && this->recv(answer);
×
796
}
×
797

798
bool RemoteBackend::feedEnts3(domainid_t domain_id, const DNSName& domain, map<DNSName, bool>& nonterm, const NSEC3PARAMRecordContent& ns3prc, bool narrow)
799
{
×
800
  Json::array nts;
×
801

802
  for (const auto& t : nonterm) {
×
803
    nts.push_back(Json::object{
×
804
      {"nonterm", t.first.toString()},
×
805
      {"auth", t.second}});
×
806
  }
×
807

808
  Json query = Json::object{
×
809
    {"method", "feedEnts3"},
×
810
    {"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}}},
×
811
  };
×
812

813
  Json answer;
×
814
  return this->send(query) && this->recv(answer);
×
815
}
×
816

817
bool RemoteBackend::startTransaction(const ZoneName& domain, domainid_t domain_id)
818
{
16✔
819
  this->d_trxid = time((time_t*)nullptr);
16✔
820

821
  Json query = Json::object{
16✔
822
    {"method", "startTransaction"},
16✔
823
    {"parameters", Json::object{{"domain", domain.toString()}, {"domain_id", domain_id}, {"trxid", static_cast<double>(d_trxid)}}}};
16✔
824

825
  Json answer;
16✔
826
  if (!this->send(query) || !this->recv(answer)) {
16!
827
    d_trxid = -1;
16✔
828
    return false;
16✔
829
  }
16✔
830
  return true;
×
831
}
16✔
832

833
bool RemoteBackend::commitTransaction()
834
{
16✔
835
  if (d_trxid == -1) {
16!
836
    return false;
16✔
837
  }
16✔
838

839
  Json query = Json::object{
×
840
    {"method", "commitTransaction"},
×
841
    {"parameters", Json::object{{"trxid", static_cast<double>(d_trxid)}}}};
×
842

843
  d_trxid = -1;
×
844
  Json answer;
×
845
  return this->send(query) && this->recv(answer);
×
846
}
16✔
847

848
bool RemoteBackend::abortTransaction()
849
{
×
850
  if (d_trxid == -1) {
×
851
    return false;
×
852
  }
×
853

854
  Json query = Json::object{
×
855
    {"method", "abortTransaction"},
×
856
    {"parameters", Json::object{{"trxid", static_cast<double>(d_trxid)}}}};
×
857

858
  d_trxid = -1;
×
859
  Json answer;
×
860
  return this->send(query) && this->recv(answer);
×
861
}
×
862

863
string RemoteBackend::directBackendCmd(const string& querystr)
864
{
8✔
865
  Json query = Json::object{
8✔
866
    {"method", "directBackendCmd"},
8✔
867
    {"parameters", Json::object{{"query", querystr}}}};
8✔
868

869
  Json answer;
8✔
870
  if (!this->send(query) || !this->recv(answer)) {
8!
871
    return "backend command failed";
×
872
  }
×
873

874
  return asString(answer["result"]);
8✔
875
}
8✔
876

877
bool RemoteBackend::searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result)
878
{
×
879
  const auto intMax = static_cast<decltype(maxResults)>(std::numeric_limits<int>::max());
×
880
  if (maxResults > intMax) {
×
881
    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) + ")");
×
882
  }
×
883

884
  Json query = Json::object{
×
885
    {"method", "searchRecords"},
×
886
    {"parameters", Json::object{{"pattern", pattern}, {"maxResults", static_cast<int>(maxResults)}}}};
×
887

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

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

897
  for (const auto& row : answer["result"].array_items()) {
×
898
    DNSResourceRecord rr;
×
899
    rr.qtype = stringFromJson(row, "qtype");
×
900
    rr.qname = DNSName(stringFromJson(row, "qname"));
×
901
    rr.qclass = QClass::IN;
×
902
    rr.content = stringFromJson(row, "content");
×
903
    rr.ttl = row["ttl"].int_value();
×
904
    rr.domain_id = static_cast<domainid_t>(intFromJson(row, "domain_id", UnknownDomainID));
×
905
    if (d_dnssec) {
×
906
      rr.auth = (intFromJson(row, "auth", 1) != 0);
×
907
    }
×
908
    else {
×
909
      rr.auth = 1;
×
910
    }
×
911
    rr.scopeMask = row["scopeMask"].int_value();
×
912
    result.push_back(rr);
×
913
  }
×
914

915
  return true;
×
916
}
×
917

918
bool RemoteBackend::searchComments(const string& /* pattern */, size_t /* maxResults */, vector<Comment>& /* result */)
919
{
×
920
  // FIXME: Implement Comment API
921
  return false;
×
922
}
×
923

924
void RemoteBackend::getAllDomains(vector<DomainInfo>* domains, bool /* getSerial */, bool include_disabled)
925
{
×
926
  Json query = Json::object{
×
927
    {"method", "getAllDomains"},
×
928
    {"parameters", Json::object{{"include_disabled", include_disabled}}}};
×
929

930
  Json answer;
×
931
  if (!this->send(query) || !this->recv(answer)) {
×
932
    return;
×
933
  }
×
934

935
  if (!answer["result"].is_array()) {
×
936
    return;
×
937
  }
×
938

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

946
void RemoteBackend::getUpdatedPrimaries(vector<DomainInfo>& domains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
947
{
×
948
  Json query = Json::object{
×
949
    {"method", "getUpdatedMasters"},
×
950
    {"parameters", Json::object{}},
×
951
  };
×
952

953
  Json answer;
×
954
  if (!this->send(query) || !this->recv(answer)) {
×
955
    return;
×
956
  }
×
957

958
  if (!answer["result"].is_array()) {
×
959
    return;
×
960
  }
×
961

962
  for (const auto& row : answer["result"].array_items()) {
×
963
    DomainInfo di;
×
964
    this->parseDomainInfo(row, di);
×
965
    domains.push_back(di);
×
966
  }
×
967
}
×
968

969
void RemoteBackend::getUnfreshSecondaryInfos(vector<DomainInfo>* domains)
970
{
×
971
  Json query = Json::object{
×
972
    {"method", "getUnfreshSlaveInfos"},
×
973
    {"parameters", Json::object{}},
×
974
  };
×
975

976
  Json answer;
×
977
  if (!this->send(query) || !this->recv(answer)) {
×
978
    return;
×
979
  }
×
980

981
  if (!answer["result"].is_array()) {
×
982
    return;
×
983
  }
×
984

985
  for (const auto& row : answer["result"].array_items()) {
×
986
    DomainInfo di;
×
987
    this->parseDomainInfo(row, di);
×
988
    domains->push_back(di);
×
989
  }
×
990
}
×
991

992
void RemoteBackend::setStale(domainid_t domain_id)
993
{
×
994
  Json query = Json::object{
×
995
    {"method", "setStale"},
×
996
    {"parameters", Json::object{{"id", domain_id}}}};
×
997

998
  Json answer;
×
999
  if (!this->send(query) || !this->recv(answer)) {
×
NEW
1000
    SLOG(g_log << Logger::Error << kBackendId << " Failed to execute RPC for RemoteBackend::setStale(" << domain_id << ")" << endl,
×
NEW
1001
         d_slog->info(Logr::Error, "Failed to execute RPC for RemoteBackend::setStale", "domain_id", Logging::Loggable(domain_id)));
×
1002
  }
×
1003
}
×
1004

1005
void RemoteBackend::setFresh(domainid_t domain_id)
1006
{
×
1007
  Json query = Json::object{
×
1008
    {"method", "setFresh"},
×
1009
    {"parameters", Json::object{{"id", domain_id}}}};
×
1010

1011
  Json answer;
×
1012
  if (!this->send(query) || !this->recv(answer)) {
×
NEW
1013
    SLOG(g_log << Logger::Error << kBackendId << " Failed to execute RPC for RemoteBackend::setFresh(" << domain_id << ")" << endl,
×
NEW
1014
         d_slog->info(Logr::Error, "Failed to execute RPC for RemoteBackend::setFresh", "domain_id", Logging::Loggable(domain_id)));
×
1015
  }
×
1016
}
×
1017

1018
class RemoteBackendFactory : public BackendFactory
1019
{
1020
public:
1021
  RemoteBackendFactory() :
1022
    BackendFactory("remote") {}
5,904✔
1023

1024
  void declareArguments(const std::string& suffix = "") override
1025
  {
32✔
1026
    declare(suffix, "dnssec", "Enable dnssec support", "no");
32✔
1027
    declare(suffix, "connection-string", "Connection string", "");
32✔
1028
  }
32✔
1029

1030
  DNSBackend* make(const std::string& suffix = "") override
1031
  {
92✔
1032
    return new RemoteBackend(suffix);
92✔
1033
  }
92✔
1034
};
1035

1036
class RemoteLoader
1037
{
1038
public:
1039
  RemoteLoader();
1040
};
1041

1042
RemoteLoader::RemoteLoader()
1043
{
5,904✔
1044
  BackendMakers().report(std::make_unique<RemoteBackendFactory>());
5,904✔
1045
  // If this module is not loaded dynamically at runtime, this code runs
1046
  // as part of a global constructor, before the structured logger has a
1047
  // chance to be set up, so fallback to simple logging in this case.
1048
  if (!g_slogStructured || !g_slog) {
5,904!
1049
    g_log << Logger::Info << kBackendId << " This is the remote backend version " VERSION
5,904✔
1050
#ifndef REPRODUCIBLE
5,904✔
1051
          << " (" __DATE__ " " __TIME__ ")"
5,904✔
1052
#endif
5,904✔
1053
          << " reporting" << endl;
5,904✔
1054
  }
5,904✔
NEW
1055
  else {
×
NEW
1056
    g_slog->withName("remotebackend")->info(Logr::Info, "remote backend starting", "version", Logging::Loggable(VERSION)
×
NEW
1057
#ifndef REPRODUCIBLE
×
NEW
1058
                                                                                                ,
×
NEW
1059
                                            "build date", Logging::Loggable(__DATE__ " " __TIME__)
×
NEW
1060
#endif
×
NEW
1061
    );
×
NEW
1062
  }
×
1063
}
5,904✔
1064

1065
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