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

PowerDNS / pdns / 15248974941

26 May 2025 07:43AM UTC coverage: 58.653%. First build
15248974941

Pull #15512

github

web-flow
Merge 4f8a7d797 into 4298fe292
Pull Request #15512: Bind-style views

35711 of 93904 branches covered (38.03%)

Branch coverage included in aggregate %.

847 of 1365 new or added lines in 32 files covered. (62.05%)

111383 of 156882 relevant lines covered (71.0%)

5872400.68 hits per line

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

60.93
/modules/bindbackend/bindbackend2.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

23
#ifdef HAVE_CONFIG_H
24
#include "config.h"
25
#endif
26
#include <cerrno>
27
#include <string>
28
#include <set>
29
#include <sys/types.h>
30
#include <sys/stat.h>
31
#include <unistd.h>
32
#include <fstream>
33
#include <fcntl.h>
34
#include <sstream>
35
#include <boost/algorithm/string.hpp>
36
#include <system_error>
37
#include <unordered_map>
38
#include <unordered_set>
39

40
#include "pdns/dnsseckeeper.hh"
41
#include "pdns/dnssecinfra.hh"
42
#include "pdns/base32.hh"
43
#include "pdns/namespaces.hh"
44
#include "pdns/dns.hh"
45
#include "pdns/dnsbackend.hh"
46
#include "bindbackend2.hh"
47
#include "pdns/dnspacket.hh"
48
#include "pdns/zoneparser-tng.hh"
49
#include "pdns/bindparserclasses.hh"
50
#include "pdns/logger.hh"
51
#include "pdns/arguments.hh"
52
#include "pdns/qtype.hh"
53
#include "pdns/misc.hh"
54
#include "pdns/dynlistener.hh"
55
#include "pdns/lock.hh"
56
#include "pdns/auth-zonecache.hh"
57
#include "pdns/auth-caches.hh"
58

59
/*
60
   All instances of this backend share one s_state, which is indexed by zone name and zone id.
61
   The s_state is protected by a read/write lock, and the goal it to only interact with it briefly.
62
   When a query comes in, we take a read lock and COPY the best zone to answer from s_state (BB2DomainInfo object)
63
   All answers are served from this copy.
64

65
   To interact with s_state, use safeGetBBDomainInfo (search on name or id), safePutBBDomainInfo (to update)
66
   or safeRemoveBBDomainInfo. These all lock as they should.
67

68
   Several functions need to traverse s_state to get data for the rest of PowerDNS. When doing so,
69
   you need to manually take the lock (read).
70

71
   Parsing zones happens with parseZone(), which fills a BB2DomainInfo object. This can then be stored with safePutBBDomainInfo.
72

73
   Finally, the BB2DomainInfo contains all records as a LookButDontTouch object. This makes sure you only look, but don't touch, since
74
   the records might be in use in other places.
75
*/
76

77
SharedLockGuarded<Bind2Backend::state_t> Bind2Backend::s_state;
78
int Bind2Backend::s_first = 1;
79
bool Bind2Backend::s_ignore_broken_records = false;
80

81
std::mutex Bind2Backend::s_autosecondary_config_lock; // protects writes to config file
82
std::mutex Bind2Backend::s_startup_lock;
83
string Bind2Backend::s_binddirectory;
84

85
BB2DomainInfo::BB2DomainInfo()
86
{
30,084✔
87
  d_loaded = false;
30,084✔
88
  d_lastcheck = 0;
30,084✔
89
  d_checknow = false;
30,084✔
90
  d_status = "Unknown";
30,084✔
91
}
30,084✔
92

93
void BB2DomainInfo::setCheckInterval(time_t seconds)
94
{
5,330✔
95
  d_checkinterval = seconds;
5,330✔
96
}
5,330✔
97

98
bool BB2DomainInfo::current()
99
{
8,212✔
100
  if (d_checknow) {
8,212✔
101
    return false;
12✔
102
  }
12✔
103

104
  if (!d_checkinterval)
8,200!
105
    return true;
8,200✔
106

107
  if (time(nullptr) - d_lastcheck < d_checkinterval)
×
108
    return true;
×
109

110
  if (d_filename.empty())
×
111
    return true;
×
112

113
  return (getCtime() == d_ctime);
×
114
}
×
115

116
time_t BB2DomainInfo::getCtime()
117
{
×
118
  struct stat buf;
×
119

120
  if (d_filename.empty() || stat(d_filename.c_str(), &buf) < 0)
×
121
    return 0;
×
122
  d_lastcheck = time(nullptr);
×
123
  return buf.st_ctime;
×
124
}
×
125

126
void BB2DomainInfo::setCtime()
127
{
5,342✔
128
  struct stat buf;
5,342✔
129
  if (stat(d_filename.c_str(), &buf) < 0)
5,342!
130
    return;
×
131
  d_ctime = buf.st_ctime;
5,342✔
132
}
5,342✔
133

134
// NOLINTNEXTLINE(readability-identifier-length)
135
bool Bind2Backend::safeGetBBDomainInfo(domainid_t id, BB2DomainInfo* bbd)
136
{
22,358✔
137
  auto state = s_state.read_lock();
22,358✔
138
  state_t::const_iterator iter = state->find(id);
22,358✔
139
  if (iter == state->end()) {
22,358!
140
    return false;
×
141
  }
×
142
  *bbd = *iter;
22,358✔
143
  return true;
22,358✔
144
}
22,358✔
145

146
bool Bind2Backend::safeGetBBDomainInfo(const ZoneName& name, BB2DomainInfo* bbd)
147
{
7,732✔
148
  auto state = s_state.read_lock();
7,732✔
149
  const auto& nameindex = boost::multi_index::get<NameTag>(*state);
7,732✔
150
  auto iter = nameindex.find(name);
7,732✔
151
  if (iter == nameindex.end()) {
7,732✔
152
    return false;
5,638✔
153
  }
5,638✔
154
  *bbd = *iter;
2,094✔
155
  return true;
2,094✔
156
}
7,732✔
157

158
bool Bind2Backend::safeRemoveBBDomainInfo(const ZoneName& name)
159
{
×
160
  auto state = s_state.write_lock();
×
161
  using nameindex_t = state_t::index<NameTag>::type;
×
162
  nameindex_t& nameindex = boost::multi_index::get<NameTag>(*state);
×
163

164
  nameindex_t::iterator iter = nameindex.find(name);
×
165
  if (iter == nameindex.end()) {
×
166
    return false;
×
167
  }
×
168
  nameindex.erase(iter);
×
169
  return true;
×
170
}
×
171

172
void Bind2Backend::safePutBBDomainInfo(const BB2DomainInfo& bbd)
173
{
5,630✔
174
  auto state = s_state.write_lock();
5,630✔
175
  replacing_insert(*state, bbd);
5,630✔
176
}
5,630✔
177

178
// NOLINTNEXTLINE(readability-identifier-length)
179
void Bind2Backend::setNotified(domainid_t id, uint32_t serial)
180
{
×
181
  BB2DomainInfo bbd;
×
182
  if (!safeGetBBDomainInfo(id, &bbd))
×
183
    return;
×
184
  bbd.d_lastnotified = serial;
×
185
  safePutBBDomainInfo(bbd);
×
186
}
×
187

188
// NOLINTNEXTLINE(readability-identifier-length)
189
void Bind2Backend::setLastCheck(domainid_t domain_id, time_t lastcheck)
190
{
144✔
191
  BB2DomainInfo bbd;
144✔
192
  if (safeGetBBDomainInfo(domain_id, &bbd)) {
144!
193
    bbd.d_lastcheck = lastcheck;
144✔
194
    safePutBBDomainInfo(bbd);
144✔
195
  }
144✔
196
}
144✔
197

198
void Bind2Backend::setStale(domainid_t domain_id)
199
{
×
200
  Bind2Backend::setLastCheck(domain_id, 0);
×
201
}
×
202

203
void Bind2Backend::setFresh(domainid_t domain_id)
204
{
144✔
205
  Bind2Backend::setLastCheck(domain_id, time(nullptr));
144✔
206
}
144✔
207

208
bool Bind2Backend::startTransaction(const ZoneName& qname, domainid_t domainId)
209
{
442✔
210
  if (domainId == UnknownDomainID) {
442✔
211
    d_transaction_tmpname.clear();
298✔
212
    d_transaction_id = UnknownDomainID;
298✔
213
    // No support for domain contents deletion
214
    return false;
298✔
215
  }
298✔
216
  if (domainId == 0) {
144!
217
    throw DBException("domain_id 0 is invalid for this backend.");
×
218
  }
×
219

220
  d_transaction_id = domainId;
144✔
221
  d_transaction_qname = qname;
144✔
222
  BB2DomainInfo bbd;
144✔
223
  if (safeGetBBDomainInfo(domainId, &bbd)) {
144!
224
    d_transaction_tmpname = bbd.d_filename + "XXXXXX";
144✔
225
    int fd = mkstemp(&d_transaction_tmpname.at(0));
144✔
226
    if (fd == -1) {
144!
227
      throw DBException("Unable to create a unique temporary zonefile '" + d_transaction_tmpname + "': " + stringerror());
×
228
    }
×
229

230
    d_of = std::make_unique<ofstream>(d_transaction_tmpname);
144✔
231
    if (!*d_of) {
144!
232
      unlink(d_transaction_tmpname.c_str());
×
233
      close(fd);
×
234
      fd = -1;
×
235
      d_of.reset();
×
236
      throw DBException("Unable to open temporary zonefile '" + d_transaction_tmpname + "': " + stringerror());
×
237
    }
×
238
    close(fd);
144✔
239
    fd = -1;
144✔
240

241
    *d_of << "; Written by PowerDNS, don't edit!" << endl;
144✔
242
    *d_of << "; Zone '" << bbd.d_name << "' retrieved from primary " << endl
144✔
243
          << "; at " << nowTime() << endl; // insert primary info here again
144✔
244

245
    return true;
144✔
246
  }
144✔
247
  return false;
×
248
}
144✔
249

250
bool Bind2Backend::commitTransaction()
251
{
442✔
252
  if (d_transaction_id == UnknownDomainID) {
442✔
253
    return false;
298✔
254
  }
298✔
255
  d_of.reset();
144✔
256

257
  BB2DomainInfo bbd;
144✔
258
  if (safeGetBBDomainInfo(d_transaction_id, &bbd)) {
144!
259
    if (rename(d_transaction_tmpname.c_str(), bbd.d_filename.c_str()) < 0)
144!
260
      throw DBException("Unable to commit (rename to: '" + bbd.d_filename + "') AXFRed zone: " + stringerror());
×
261
    queueReloadAndStore(bbd.d_id);
144✔
262
  }
144✔
263

264
  d_transaction_id = UnknownDomainID;
144✔
265

266
  return true;
144✔
267
}
144✔
268

269
bool Bind2Backend::abortTransaction()
270
{
×
271
  // -1 = dnssec speciality
272
  // 0  = invalid transact
273
  // >0 = actual transaction
274
  if (d_transaction_id != UnknownDomainID) {
×
275
    unlink(d_transaction_tmpname.c_str());
×
276
    d_of.reset();
×
277
    d_transaction_id = UnknownDomainID;
×
278
  }
×
279

280
  return true;
×
281
}
×
282

283
bool Bind2Backend::feedRecord(const DNSResourceRecord& rr, const DNSName& /* ordername */, bool /* ordernameIsNSEC3 */)
284
{
405,570✔
285
  if (d_transaction_id == UnknownDomainID) {
405,570!
286
    throw DBException("Bind2Backend::feedRecord() called outside of transaction");
×
287
  }
×
288

289
  string qname;
405,570✔
290
  if (d_transaction_qname.empty()) {
405,570!
291
    qname = rr.qname.toString();
×
292
  }
×
293
  else if (rr.qname.isPartOf(d_transaction_qname)) {
405,570!
294
    if (rr.qname == d_transaction_qname.operator const DNSName&()) {
405,570✔
295
      qname = "@";
1,266✔
296
    }
1,266✔
297
    else {
404,304✔
298
      DNSName relName = rr.qname.makeRelative(d_transaction_qname);
404,304✔
299
      qname = relName.toStringNoDot();
404,304✔
300
    }
404,304✔
301
  }
405,570✔
302
  else {
×
303
    throw DBException("out-of-zone data '" + rr.qname.toLogString() + "' during AXFR of zone '" + d_transaction_qname.toLogString() + "'");
×
304
  }
×
305

306
  shared_ptr<DNSRecordContent> drc(DNSRecordContent::make(rr.qtype.getCode(), QClass::IN, rr.content));
405,570✔
307
  string content = drc->getZoneRepresentation();
405,570✔
308

309
  // SOA needs stripping too! XXX FIXME - also, this should not be here I think
310
  switch (rr.qtype.getCode()) {
405,570✔
311
  case QType::MX:
112✔
312
  case QType::SRV:
152✔
313
  case QType::CNAME:
416✔
314
  case QType::DNAME:
424✔
315
  case QType::NS:
864✔
316
    stripDomainSuffix(&content, d_transaction_qname.toString());
864✔
317
    // fallthrough
318
  default:
405,570✔
319
    if (d_of && *d_of) {
405,570!
320
      *d_of << qname << "\t" << rr.ttl << "\t" << rr.qtype.toString() << "\t" << content << endl;
405,570✔
321
    }
405,570✔
322
  }
405,570✔
323
  return true;
405,570✔
324
}
405,570✔
325

326
void Bind2Backend::getUpdatedPrimaries(vector<DomainInfo>& changedDomains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
327
{
×
328
  vector<DomainInfo> consider;
×
329
  {
×
330
    auto state = s_state.read_lock();
×
331

332
    for (const auto& i : *state) {
×
333
      if (i.d_kind != DomainInfo::Primary && this->alsoNotify.empty() && i.d_also_notify.empty())
×
334
        continue;
×
335

336
      DomainInfo di;
×
337
      di.id = i.d_id;
×
338
      di.zone = i.d_name;
×
339
      di.last_check = i.d_lastcheck;
×
340
      di.notified_serial = i.d_lastnotified;
×
341
      di.backend = this;
×
342
      di.kind = DomainInfo::Primary;
×
343
      consider.push_back(std::move(di));
×
344
    }
×
345
  }
×
346

347
  SOAData soadata;
×
348
  for (DomainInfo& di : consider) {
×
349
    soadata.serial = 0;
×
350
    try {
×
NEW
351
      this->getSOA(di.zone, di.id, soadata); // we might not *have* a SOA yet, but this might trigger a load of it
×
352
    }
×
353
    catch (...) {
×
354
      continue;
×
355
    }
×
356
    if (di.notified_serial != soadata.serial) {
×
357
      BB2DomainInfo bbd;
×
358
      if (safeGetBBDomainInfo(di.id, &bbd)) {
×
359
        bbd.d_lastnotified = soadata.serial;
×
360
        safePutBBDomainInfo(bbd);
×
361
      }
×
362
      if (di.notified_serial) { // don't do notification storm on startup
×
363
        di.serial = soadata.serial;
×
364
        changedDomains.push_back(std::move(di));
×
365
      }
×
366
    }
×
367
  }
×
368
}
×
369

370
void Bind2Backend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool /* include_disabled */)
371
{
86✔
372
  SOAData soadata;
86✔
373

374
  // prevent deadlock by using getSOA() later on
375
  {
86✔
376
    auto state = s_state.read_lock();
86✔
377
    domains->reserve(state->size());
86✔
378

379
    for (const auto& i : *state) {
406✔
380
      DomainInfo di;
370✔
381
      di.id = i.d_id;
370✔
382
      di.zone = i.d_name;
370✔
383
      di.last_check = i.d_lastcheck;
370✔
384
      di.kind = i.d_kind;
370✔
385
      di.primaries = i.d_primaries;
370✔
386
      di.backend = this;
370✔
387
      domains->push_back(std::move(di));
370✔
388
    };
370✔
389
  }
86✔
390

391
  if (getSerial) {
86✔
392
    for (DomainInfo& di : *domains) {
676✔
393
      // do not corrupt di if domain supplied by another backend.
394
      if (di.backend != this)
676!
395
        continue;
676✔
396
      try {
×
NEW
397
        this->getSOA(di.zone, di.id, soadata);
×
398
      }
×
399
      catch (...) {
×
400
        continue;
×
401
      }
×
402
      di.serial = soadata.serial;
×
403
    }
×
404
  }
20✔
405
}
86✔
406

407
void Bind2Backend::getUnfreshSecondaryInfos(vector<DomainInfo>* unfreshDomains)
408
{
16✔
409
  vector<DomainInfo> domains;
16✔
410
  {
16✔
411
    auto state = s_state.read_lock();
16✔
412
    domains.reserve(state->size());
16✔
413
    for (const auto& i : *state) {
144✔
414
      if (i.d_kind != DomainInfo::Secondary)
144!
415
        continue;
×
416
      DomainInfo sd;
144✔
417
      sd.id = i.d_id;
144✔
418
      sd.zone = i.d_name;
144✔
419
      sd.primaries = i.d_primaries;
144✔
420
      sd.last_check = i.d_lastcheck;
144✔
421
      sd.backend = this;
144✔
422
      sd.kind = DomainInfo::Secondary;
144✔
423
      domains.push_back(std::move(sd));
144✔
424
    }
144✔
425
  }
16✔
426
  unfreshDomains->reserve(domains.size());
16✔
427

428
  for (DomainInfo& sd : domains) {
144✔
429
    SOAData soadata;
144✔
430
    soadata.refresh = 0;
144✔
431
    soadata.serial = 0;
144✔
432
    try {
144✔
433
      getSOA(sd.zone, sd.id, soadata); // we might not *have* a SOA yet
144✔
434
    }
144✔
435
    catch (...) {
144✔
436
    }
144✔
437
    sd.serial = soadata.serial;
144✔
438
    // coverity[store_truncates_time_t]
439
    if (sd.last_check + soadata.refresh < (unsigned int)time(nullptr))
144!
440
      unfreshDomains->push_back(std::move(sd));
144✔
441
  }
144✔
442
}
16✔
443

444
bool Bind2Backend::getDomainInfo(const ZoneName& domain, DomainInfo& info, bool getSerial)
445
{
1,174✔
446
  BB2DomainInfo bbd;
1,174✔
447
  if (!safeGetBBDomainInfo(domain, &bbd))
1,174✔
448
    return false;
284✔
449

450
  info.id = bbd.d_id;
890✔
451
  info.zone = domain;
890✔
452
  info.primaries = bbd.d_primaries;
890✔
453
  info.last_check = bbd.d_lastcheck;
890✔
454
  info.backend = this;
890✔
455
  info.kind = bbd.d_kind;
890✔
456
  info.serial = 0;
890✔
457
  if (getSerial) {
890✔
458
    try {
270✔
459
      SOAData sd;
270✔
460
      sd.serial = 0;
270✔
461

462
      getSOA(bbd.d_name, bbd.d_id, sd); // we might not *have* a SOA yet
270✔
463
      info.serial = sd.serial;
270✔
464
    }
270✔
465
    catch (...) {
270✔
466
    }
144✔
467
  }
270✔
468

469
  return true;
890✔
470
}
890✔
471

472
void Bind2Backend::alsoNotifies(const ZoneName& domain, set<string>* ips)
473
{
2✔
474
  // combine global list with local list
475
  for (const auto& i : this->alsoNotify) {
2!
476
    (*ips).insert(i);
×
477
  }
×
478
  // check metadata too if available
479
  vector<string> meta;
2✔
480
  if (getDomainMetadata(domain, "ALSO-NOTIFY", meta)) {
2!
481
    for (const auto& str : meta) {
×
482
      (*ips).insert(str);
×
483
    }
×
484
  }
×
485
  auto state = s_state.read_lock();
2✔
486
  for (const auto& i : *state) {
2!
487
    if (i.d_name == domain) {
×
488
      for (const auto& it : i.d_also_notify) {
×
489
        (*ips).insert(it);
×
490
      }
×
491
      return;
×
492
    }
×
493
  }
×
494
}
2✔
495

496
// only parses, does NOT add to s_state!
497
void Bind2Backend::parseZoneFile(BB2DomainInfo* bbd)
498
{
5,474✔
499
  NSEC3PARAMRecordContent ns3pr;
5,474✔
500
  bool nsec3zone = false;
5,474✔
501
  if (d_hybrid) {
5,474!
502
    DNSSECKeeper dk;
×
503
    nsec3zone = dk.getNSEC3PARAM(bbd->d_name, &ns3pr);
×
504
  }
×
505
  else
5,474✔
506
    nsec3zone = getNSEC3PARAMuncached(bbd->d_name, &ns3pr);
5,474✔
507

508
  auto records = std::make_shared<recordstorage_t>();
5,474✔
509
  ZoneParserTNG zpt(bbd->d_filename, bbd->d_name, s_binddirectory, d_upgradeContent);
5,474✔
510
  zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
5,474✔
511
  zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
5,474✔
512
  DNSResourceRecord rr;
5,474✔
513
  string hashed;
5,474✔
514
  while (zpt.get(rr)) {
6,554,246✔
515
    if (rr.qtype.getCode() == QType::NSEC || rr.qtype.getCode() == QType::NSEC3 || rr.qtype.getCode() == QType::NSEC3PARAM)
6,548,772!
516
      continue; // we synthesise NSECs on demand
×
517

518
    insertRecord(records, bbd->d_name, rr.qname, rr.qtype, rr.content, rr.ttl, "");
6,548,772✔
519
  }
6,548,772✔
520
  fixupOrderAndAuth(records, bbd->d_name, nsec3zone, ns3pr);
5,474✔
521
  doEmptyNonTerminals(records, bbd->d_name, nsec3zone, ns3pr);
5,474✔
522
  bbd->setCtime();
5,474✔
523
  bbd->d_loaded = true;
5,474✔
524
  bbd->d_checknow = false;
5,474✔
525
  bbd->d_status = "parsed into memory at " + nowTime();
5,474✔
526
  bbd->d_records = LookButDontTouch<recordstorage_t>(std::move(records));
5,474✔
527
  bbd->d_nsec3zone = nsec3zone;
5,474✔
528
  bbd->d_nsec3param = std::move(ns3pr);
5,474✔
529
}
5,474✔
530

531
/** THIS IS AN INTERNAL FUNCTION! It does moadnsparser prio impedance matching
532
    Much of the complication is due to the efforts to benefit from std::string reference counting copy on write semantics */
533
void Bind2Backend::insertRecord(std::shared_ptr<recordstorage_t>& records, const ZoneName& zoneName, const DNSName& qname, const QType& qtype, const string& content, int ttl, const std::string& hashed, const bool* auth)
534
{
6,558,004✔
535
  Bind2DNSRecord bdr;
6,558,004✔
536
  bdr.qname = qname;
6,558,004✔
537

538
  if (zoneName.empty())
6,558,004!
539
    ;
×
540
  else if (bdr.qname.isPartOf(zoneName))
6,558,004✔
541
    bdr.qname.makeUsRelative(zoneName);
6,557,400✔
542
  else {
604✔
543
    string msg = "Trying to insert non-zone data, name='" + bdr.qname.toLogString() + "', qtype=" + qtype.toString() + ", zone='" + zoneName.toLogString() + "'";
604✔
544
    if (s_ignore_broken_records) {
604!
545
      g_log << Logger::Warning << msg << " ignored" << endl;
604✔
546
      return;
604✔
547
    }
604✔
548
    throw PDNSException(std::move(msg));
×
549
  }
604✔
550

551
  //  bdr.qname.swap(bdr.qname);
552

553
  if (!records->empty() && bdr.qname == boost::prior(records->end())->qname)
6,557,400✔
554
    bdr.qname = boost::prior(records->end())->qname;
17,738✔
555

556
  bdr.qname = bdr.qname;
6,557,400✔
557
  bdr.qtype = qtype.getCode();
6,557,400✔
558
  bdr.content = content;
6,557,400✔
559
  bdr.nsec3hash = hashed;
6,557,400✔
560

561
  if (auth != nullptr) // Set auth on empty non-terminals
6,557,400✔
562
    bdr.auth = *auth;
9,232✔
563
  else
6,548,168✔
564
    bdr.auth = true;
6,548,168✔
565

566
  bdr.ttl = ttl;
6,557,400✔
567
  records->insert(std::move(bdr));
6,557,400✔
568
}
6,557,400✔
569

570
string Bind2Backend::DLReloadNowHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
571
{
×
572
  ostringstream ret;
×
573

574
  for (auto i = parts.begin() + 1; i < parts.end(); ++i) {
×
575
    BB2DomainInfo bbd;
×
576
    ZoneName zone(*i);
×
577
    if (safeGetBBDomainInfo(zone, &bbd)) {
×
578
      Bind2Backend bb2;
×
579
      bb2.queueReloadAndStore(bbd.d_id);
×
580
      if (!safeGetBBDomainInfo(zone, &bbd)) // Read the *new* domain status
×
581
        ret << *i << ": [missing]\n";
×
582
      else
×
583
        ret << *i << ": " << (bbd.d_wasRejectedLastReload ? "[rejected]" : "") << "\t" << bbd.d_status << "\n";
×
584
      purgeAuthCaches(zone.toString() + "$");
×
585
      DNSSECKeeper::clearMetaCache(zone);
×
586
    }
×
587
    else
×
588
      ret << *i << " no such domain\n";
×
589
  }
×
590
  if (ret.str().empty())
×
591
    ret << "no domains reloaded";
×
592
  return ret.str();
×
593
}
×
594

595
string Bind2Backend::DLDomStatusHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
596
{
54✔
597
  ostringstream ret;
54✔
598

599
  if (parts.size() > 1) {
54!
600
    for (auto i = parts.begin() + 1; i < parts.end(); ++i) {
×
601
      BB2DomainInfo bbd;
×
602
      if (safeGetBBDomainInfo(ZoneName(*i), &bbd)) {
×
603
        ret << *i << ": " << (bbd.d_loaded ? "" : "[rejected]") << "\t" << bbd.d_status << "\n";
×
604
      }
×
605
      else {
×
606
        ret << *i << " no such domain\n";
×
607
      }
×
608
    }
×
609
  }
×
610
  else {
54✔
611
    auto state = s_state.read_lock();
54✔
612
    for (const auto& i : *state) {
470✔
613
      ret << i.d_name << ": " << (i.d_loaded ? "" : "[rejected]") << "\t" << i.d_status << "\n";
470✔
614
    }
470✔
615
  }
54✔
616

617
  if (ret.str().empty())
54!
618
    ret << "no domains passed";
×
619

620
  return ret.str();
54✔
621
}
54✔
622

623
static void printDomainExtendedStatus(ostringstream& ret, const BB2DomainInfo& info)
624
{
×
625
  ret << info.d_name << ": " << std::endl;
×
626
  ret << "\t Status: " << info.d_status << std::endl;
×
627
  ret << "\t Internal ID: " << info.d_id << std::endl;
×
628
  ret << "\t On-disk file: " << info.d_filename << " (" << info.d_ctime << ")" << std::endl;
×
629
  ret << "\t Kind: ";
×
630
  switch (info.d_kind) {
×
631
  case DomainInfo::Primary:
×
632
    ret << "Primary";
×
633
    break;
×
634
  case DomainInfo::Secondary:
×
635
    ret << "Secondary";
×
636
    break;
×
637
  default:
×
638
    ret << "Native";
×
639
  }
×
640
  ret << std::endl;
×
641
  ret << "\t Primaries: " << std::endl;
×
642
  for (const auto& primary : info.d_primaries) {
×
643
    ret << "\t\t - " << primary.toStringWithPort() << std::endl;
×
644
  }
×
645
  ret << "\t Also Notify: " << std::endl;
×
646
  for (const auto& also : info.d_also_notify) {
×
647
    ret << "\t\t - " << also << std::endl;
×
648
  }
×
649
  ret << "\t Number of records: " << info.d_records.getEntriesCount() << std::endl;
×
650
  ret << "\t Loaded: " << info.d_loaded << std::endl;
×
651
  ret << "\t Check now: " << info.d_checknow << std::endl;
×
652
  ret << "\t Check interval: " << info.getCheckInterval() << std::endl;
×
653
  ret << "\t Last check: " << info.d_lastcheck << std::endl;
×
654
  ret << "\t Last notified: " << info.d_lastnotified << std::endl;
×
655
}
×
656

657
string Bind2Backend::DLDomExtendedStatusHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
658
{
×
659
  ostringstream ret;
×
660

661
  if (parts.size() > 1) {
×
662
    for (auto i = parts.begin() + 1; i < parts.end(); ++i) {
×
663
      BB2DomainInfo bbd;
×
664
      if (safeGetBBDomainInfo(ZoneName(*i), &bbd)) {
×
665
        printDomainExtendedStatus(ret, bbd);
×
666
      }
×
667
      else {
×
668
        ret << *i << " no such domain" << std::endl;
×
669
      }
×
670
    }
×
671
  }
×
672
  else {
×
673
    auto rstate = s_state.read_lock();
×
674
    for (const auto& state : *rstate) {
×
675
      printDomainExtendedStatus(ret, state);
×
676
    }
×
677
  }
×
678

679
  if (ret.str().empty()) {
×
680
    ret << "no domains passed" << std::endl;
×
681
  }
×
682

683
  return ret.str();
×
684
}
×
685

686
string Bind2Backend::DLListRejectsHandler(const vector<string>& /* parts */, Utility::pid_t /* ppid */)
687
{
×
688
  ostringstream ret;
×
689
  auto rstate = s_state.read_lock();
×
690
  for (const auto& i : *rstate) {
×
691
    if (!i.d_loaded)
×
692
      ret << i.d_name << "\t" << i.d_status << endl;
×
693
  }
×
694
  return ret.str();
×
695
}
×
696

697
string Bind2Backend::DLAddDomainHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
698
{
24✔
699
  if (parts.size() < 3)
24!
700
    return "ERROR: Domain name and zone filename are required";
×
701

702
  ZoneName domainname(parts[1]);
24✔
703
  const string& filename = parts[2];
24✔
704
  BB2DomainInfo bbd;
24✔
705
  if (safeGetBBDomainInfo(domainname, &bbd))
24✔
706
    return "Already loaded";
12✔
707

708
  if (!boost::starts_with(filename, "/") && ::arg()["chroot"].empty())
12!
709
    return "Unable to load zone " + domainname.toLogString() + " from " + filename + " as the filename is not absolute.";
×
710

711
  struct stat buf;
12✔
712
  if (stat(filename.c_str(), &buf) != 0)
12!
713
    return "Unable to load zone " + domainname.toLogString() + " from " + filename + ": " + strerror(errno);
×
714

715
  Bind2Backend bb2; // createdomainentry needs access to our configuration
12✔
716
  bbd = bb2.createDomainEntry(domainname, filename);
12✔
717
  bbd.d_filename = filename;
12✔
718
  bbd.d_checknow = true;
12✔
719
  bbd.d_loaded = true;
12✔
720
  bbd.d_lastcheck = 0;
12✔
721
  bbd.d_status = "parsing into memory";
12✔
722
  bbd.setCtime();
12✔
723

724
  safePutBBDomainInfo(bbd);
12✔
725

726
  g_zoneCache.add(domainname, bbd.d_id); // make new zone visible
12✔
727

728
  g_log << Logger::Warning << "Zone " << domainname << " loaded" << endl;
12✔
729
  return "Loaded zone " + domainname.toLogString() + " from " + filename;
12✔
730
}
12✔
731

732
Bind2Backend::Bind2Backend(const string& suffix, bool loadZones)
733
{
3,588✔
734
  d_getAllDomainMetadataQuery_stmt = nullptr;
3,588✔
735
  d_getDomainMetadataQuery_stmt = nullptr;
3,588✔
736
  d_deleteDomainMetadataQuery_stmt = nullptr;
3,588✔
737
  d_insertDomainMetadataQuery_stmt = nullptr;
3,588✔
738
  d_getDomainKeysQuery_stmt = nullptr;
3,588✔
739
  d_deleteDomainKeyQuery_stmt = nullptr;
3,588✔
740
  d_insertDomainKeyQuery_stmt = nullptr;
3,588✔
741
  d_GetLastInsertedKeyIdQuery_stmt = nullptr;
3,588✔
742
  d_activateDomainKeyQuery_stmt = nullptr;
3,588✔
743
  d_deactivateDomainKeyQuery_stmt = nullptr;
3,588✔
744
  d_getTSIGKeyQuery_stmt = nullptr;
3,588✔
745
  d_setTSIGKeyQuery_stmt = nullptr;
3,588✔
746
  d_deleteTSIGKeyQuery_stmt = nullptr;
3,588✔
747
  d_getTSIGKeysQuery_stmt = nullptr;
3,588✔
748

749
  setArgPrefix("bind" + suffix);
3,588✔
750
  d_logprefix = "[bind" + suffix + "backend]";
3,588✔
751
  d_hybrid = mustDo("hybrid");
3,588✔
752
  if (d_hybrid && g_zoneCache.isEnabled()) {
3,588!
753
    throw PDNSException("bind-hybrid and the zone cache currently interoperate badly. Please disable the zone cache or stop using bind-hybrid");
×
754
  }
×
755

756
  d_transaction_id = UnknownDomainID;
3,588✔
757
  s_ignore_broken_records = mustDo("ignore-broken-records");
3,588✔
758
  d_upgradeContent = ::arg().mustDo("upgrade-unknown-types");
3,588✔
759

760
  if (!loadZones && d_hybrid)
3,588!
761
    return;
×
762

763
  std::lock_guard<std::mutex> l(s_startup_lock);
3,588✔
764

765
  setupDNSSEC();
3,588✔
766
  if (s_first == 0) {
3,588✔
767
    return;
2,640✔
768
  }
2,640✔
769

770
  if (loadZones) {
948✔
771
    loadConfig();
424✔
772
    s_first = 0;
424✔
773
  }
424✔
774

775
  DynListener::registerFunc("BIND-RELOAD-NOW", &DLReloadNowHandler, "bindbackend: reload domains", "<domains>");
948✔
776
  DynListener::registerFunc("BIND-DOMAIN-STATUS", &DLDomStatusHandler, "bindbackend: list status of all domains", "[domains]");
948✔
777
  DynListener::registerFunc("BIND-DOMAIN-EXTENDED-STATUS", &DLDomExtendedStatusHandler, "bindbackend: list the extended status of all domains", "[domains]");
948✔
778
  DynListener::registerFunc("BIND-LIST-REJECTS", &DLListRejectsHandler, "bindbackend: list rejected domains");
948✔
779
  DynListener::registerFunc("BIND-ADD-ZONE", &DLAddDomainHandler, "bindbackend: add zone", "<domain> <filename>");
948✔
780
}
948✔
781

782
Bind2Backend::~Bind2Backend()
783
{
3,364✔
784
  freeStatements();
3,364✔
785
} // deallocate statements
3,364✔
786

787
void Bind2Backend::rediscover(string* status)
788
{
×
789
  loadConfig(status);
×
790
}
×
791

792
void Bind2Backend::reload()
793
{
×
794
  auto state = s_state.write_lock();
×
795
  for (const auto& i : *state) {
×
796
    i.d_checknow = true; // being a bit cheeky here, don't index state_t on this (mutable)
×
797
  }
×
798
}
×
799

800
void Bind2Backend::fixupOrderAndAuth(std::shared_ptr<recordstorage_t>& records, const ZoneName& zoneName, bool nsec3zone, const NSEC3PARAMRecordContent& ns3pr)
801
{
5,330✔
802
  bool skip;
5,330✔
803
  DNSName shorter;
5,330✔
804
  set<DNSName> nssets, dssets;
5,330✔
805

806
  for (const auto& bdr : *records) {
6,548,168✔
807
    if (!bdr.qname.isRoot() && bdr.qtype == QType::NS)
6,548,168✔
808
      nssets.insert(bdr.qname);
6,020✔
809
    else if (bdr.qtype == QType::DS)
6,542,148✔
810
      dssets.insert(bdr.qname);
998✔
811
  }
6,548,168✔
812

813
  for (auto iter = records->begin(); iter != records->end(); iter++) {
6,553,498✔
814
    skip = false;
6,548,168✔
815
    shorter = iter->qname;
6,548,168✔
816

817
    if (!iter->qname.isRoot() && shorter.chopOff() && !iter->qname.isRoot()) {
6,548,168!
818
      do {
6,545,082✔
819
        if (nssets.count(shorter) != 0u) {
6,545,082✔
820
          skip = true;
2,718✔
821
          break;
2,718✔
822
        }
2,718✔
823
      } while (shorter.chopOff() && !iter->qname.isRoot());
6,545,082!
824
    }
6,525,906✔
825

826
    iter->auth = (!skip && (iter->qtype == QType::DS || iter->qtype == QType::RRSIG || (nssets.count(iter->qname) == 0u)));
6,548,168✔
827

828
    if (!skip && nsec3zone && iter->qtype != QType::RRSIG && (iter->auth || (iter->qtype == QType::NS && (ns3pr.d_flags == 0u)) || (dssets.count(iter->qname) != 0u))) {
6,548,168✔
829
      Bind2DNSRecord bdr = *iter;
2,878,890✔
830
      bdr.nsec3hash = toBase32Hex(hashQNameWithSalt(ns3pr, bdr.qname + zoneName.operator const DNSName&()));
2,878,890✔
831
      records->replace(iter, bdr);
2,878,890✔
832
    }
2,878,890✔
833

834
    // cerr<<iter->qname<<"\t"<<QType(iter->qtype).toString()<<"\t"<<iter->nsec3hash<<"\t"<<iter->auth<<endl;
835
  }
6,548,168✔
836
}
5,330✔
837

838
void Bind2Backend::doEmptyNonTerminals(std::shared_ptr<recordstorage_t>& records, const ZoneName& zoneName, bool nsec3zone, const NSEC3PARAMRecordContent& ns3pr)
839
{
5,330✔
840
  bool auth = false;
5,330✔
841
  DNSName shorter;
5,330✔
842
  std::unordered_set<DNSName> qnames;
5,330✔
843
  std::unordered_map<DNSName, bool> nonterm;
5,330✔
844

845
  uint32_t maxent = ::arg().asNum("max-ent-entries");
5,330✔
846

847
  for (const auto& bdr : *records)
5,330✔
848
    qnames.insert(bdr.qname);
6,548,168✔
849

850
  for (const auto& bdr : *records) {
6,548,168✔
851

852
    if (!bdr.auth && bdr.qtype == QType::NS)
6,548,168✔
853
      auth = (!nsec3zone || (ns3pr.d_flags == 0u));
6,020✔
854
    else
6,542,148✔
855
      auth = bdr.auth;
6,542,148✔
856

857
    shorter = bdr.qname;
6,548,168✔
858
    while (shorter.chopOff()) {
13,095,968✔
859
      if (qnames.count(shorter) == 0u) {
6,547,800✔
860
        if (!(maxent)) {
18,844!
861
          g_log << Logger::Error << "Zone '" << zoneName << "' has too many empty non terminals." << endl;
×
862
          return;
×
863
        }
×
864

865
        if (nonterm.count(shorter) == 0u) {
18,844✔
866
          nonterm.emplace(shorter, auth);
9,232✔
867
          --maxent;
9,232✔
868
        }
9,232✔
869
        else if (auth)
9,612✔
870
          nonterm[shorter] = true;
9,420✔
871
      }
18,844✔
872
    }
6,547,800✔
873
  }
6,548,168✔
874

875
  DNSResourceRecord rr;
5,330✔
876
  rr.qtype = "#0";
5,330✔
877
  rr.content = "";
5,330✔
878
  rr.ttl = 0;
5,330✔
879
  for (auto& nt : nonterm) {
9,234✔
880
    string hashed;
9,232✔
881
    rr.qname = nt.first + zoneName.operator const DNSName&();
9,232✔
882
    if (nsec3zone && nt.second)
9,232✔
883
      hashed = toBase32Hex(hashQNameWithSalt(ns3pr, rr.qname));
3,564✔
884
    insertRecord(records, zoneName, rr.qname, rr.qtype, rr.content, rr.ttl, hashed, &nt.second);
9,232✔
885

886
    // cerr<<rr.qname<<"\t"<<rr.qtype.toString()<<"\t"<<hashed<<"\t"<<nt.second<<endl;
887
  }
9,232✔
888
}
5,330✔
889

890
void Bind2Backend::loadConfig(string* status) // NOLINT(readability-function-cognitive-complexity) 13379 https://github.com/PowerDNS/pdns/issues/13379 Habbie: zone2sql.cc, bindbackend2.cc: reduce complexity
891
{
424✔
892
  static int domain_id = 1;
424✔
893

894
  if (!getArg("config").empty()) {
424!
895
    BindParser BP;
424✔
896
    try {
424✔
897
      BP.parse(getArg("config"));
424✔
898
    }
424✔
899
    catch (PDNSException& ae) {
424✔
900
      g_log << Logger::Error << "Error parsing bind configuration: " << ae.reason << endl;
×
901
      throw;
×
902
    }
×
903

904
    vector<BindDomainInfo> domains = BP.getDomains();
424✔
905
    this->alsoNotify = BP.getAlsoNotify();
424✔
906

907
    s_binddirectory = BP.getDirectory();
424✔
908
    //    ZP.setDirectory(d_binddirectory);
909

910
    g_log << Logger::Warning << d_logprefix << " Parsing " << domains.size() << " domain(s), will report when done" << endl;
424✔
911

912
    set<ZoneName> oldnames;
424✔
913
    set<ZoneName> newnames;
424✔
914
    {
424✔
915
      auto state = s_state.read_lock();
424✔
916
      for (const BB2DomainInfo& bbd : *state) {
424!
917
        oldnames.insert(bbd.d_name);
×
918
      }
×
919
    }
424✔
920
    int rejected = 0;
424✔
921
    int newdomains = 0;
424✔
922

923
    struct stat st;
424✔
924

925
    for (auto& domain : domains) {
5,384✔
926
      if (stat(domain.filename.c_str(), &st) == 0) {
5,318✔
927
        domain.d_dev = st.st_dev;
5,174✔
928
        domain.d_ino = st.st_ino;
5,174✔
929
      }
5,174✔
930
    }
5,318✔
931

932
    sort(domains.begin(), domains.end()); // put stuff in inode order
424✔
933
    for (const auto& domain : domains) {
5,384✔
934
      if (!(domain.hadFileDirective)) {
5,318!
935
        g_log << Logger::Warning << d_logprefix << " Zone '" << domain.name << "' has no 'file' directive set in " << getArg("config") << endl;
×
936
        rejected++;
×
937
        continue;
×
938
      }
×
939

940
      if (domain.type.empty()) {
5,318!
941
        g_log << Logger::Notice << d_logprefix << " Zone '" << domain.name << "' has no type specified, assuming 'native'" << endl;
×
942
      }
×
943
      if (domain.type != "primary" && domain.type != "secondary" && domain.type != "native" && !domain.type.empty() && domain.type != "master" && domain.type != "slave") {
5,318!
944
        g_log << Logger::Warning << d_logprefix << " Warning! Skipping zone '" << domain.name << "' because type '" << domain.type << "' is invalid" << endl;
×
945
        rejected++;
×
946
        continue;
×
947
      }
×
948

949
      BB2DomainInfo bbd;
5,318✔
950
      bool isNew = false;
5,318✔
951

952
      if (!safeGetBBDomainInfo(domain.name, &bbd)) {
5,318!
953
        isNew = true;
5,318✔
954
        bbd.d_id = domain_id++;
5,318✔
955
        bbd.setCheckInterval(getArgAsNum("check-interval"));
5,318✔
956
        bbd.d_lastnotified = 0;
5,318✔
957
        bbd.d_loaded = false;
5,318✔
958
      }
5,318✔
959

960
      // overwrite what we knew about the domain
961
      bbd.d_name = domain.name;
5,318✔
962
      bool filenameChanged = (bbd.d_filename != domain.filename);
5,318✔
963
      bool addressesChanged = (bbd.d_primaries != domain.primaries || bbd.d_also_notify != domain.alsoNotify);
5,318!
964
      bbd.d_filename = domain.filename;
5,318✔
965
      bbd.d_primaries = domain.primaries;
5,318✔
966
      bbd.d_also_notify = domain.alsoNotify;
5,318✔
967

968
      DomainInfo::DomainKind kind = DomainInfo::Native;
5,318✔
969
      if (domain.type == "primary" || domain.type == "master") {
5,318!
970
        kind = DomainInfo::Primary;
5,174✔
971
      }
5,174✔
972
      if (domain.type == "secondary" || domain.type == "slave") {
5,318!
973
        kind = DomainInfo::Secondary;
144✔
974
      }
144✔
975

976
      bool kindChanged = (bbd.d_kind != kind);
5,318✔
977
      bbd.d_kind = kind;
5,318✔
978

979
      newnames.insert(bbd.d_name);
5,318✔
980
      if (filenameChanged || !bbd.d_loaded || !bbd.current()) {
5,318!
981
        g_log << Logger::Info << d_logprefix << " parsing '" << domain.name << "' from file '" << domain.filename << "'" << endl;
5,318✔
982

983
        try {
5,318✔
984
          parseZoneFile(&bbd);
5,318✔
985
        }
5,318✔
986
        catch (PDNSException& ae) {
5,318✔
987
          ostringstream msg;
×
988
          msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.reason;
×
989

990
          if (status != nullptr)
×
991
            *status += msg.str();
×
992
          bbd.d_status = msg.str();
×
993

994
          g_log << Logger::Warning << d_logprefix << msg.str() << endl;
×
995
          rejected++;
×
996
        }
×
997
        catch (std::system_error& ae) {
5,318✔
998
          ostringstream msg;
144✔
999
          if (ae.code().value() == ENOENT && isNew && domain.type == "slave")
144!
1000
            msg << " error at " + nowTime() << " no file found for new secondary domain '" << domain.name << "'. Has not been AXFR'd yet";
×
1001
          else
144✔
1002
            msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.what();
144✔
1003

1004
          if (status != nullptr)
144!
1005
            *status += msg.str();
×
1006
          bbd.d_status = msg.str();
144✔
1007
          g_log << Logger::Warning << d_logprefix << msg.str() << endl;
144✔
1008
          rejected++;
144✔
1009
        }
144✔
1010
        catch (std::exception& ae) {
5,318✔
1011
          ostringstream msg;
×
1012
          msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.what();
×
1013

1014
          if (status != nullptr)
×
1015
            *status += msg.str();
×
1016
          bbd.d_status = msg.str();
×
1017

1018
          g_log << Logger::Warning << d_logprefix << msg.str() << endl;
×
1019
          rejected++;
×
1020
        }
×
1021
        safePutBBDomainInfo(bbd);
5,318✔
1022
      }
5,318✔
1023
      else if (addressesChanged || kindChanged) {
×
1024
        safePutBBDomainInfo(bbd);
×
1025
      }
×
1026
    }
5,318✔
1027
    vector<ZoneName> diff;
424✔
1028

1029
    set_difference(oldnames.begin(), oldnames.end(), newnames.begin(), newnames.end(), back_inserter(diff));
424✔
1030
    unsigned int remdomains = diff.size();
424✔
1031

1032
    for (const ZoneName& name : diff) {
424!
1033
      safeRemoveBBDomainInfo(name);
×
1034
    }
×
1035

1036
    // count number of entirely new domains
1037
    diff.clear();
424✔
1038
    set_difference(newnames.begin(), newnames.end(), oldnames.begin(), oldnames.end(), back_inserter(diff));
424✔
1039
    newdomains = diff.size();
424✔
1040

1041
    ostringstream msg;
424✔
1042
    msg << " Done parsing domains, " << rejected << " rejected, " << newdomains << " new, " << remdomains << " removed";
424✔
1043
    if (status != nullptr)
424!
1044
      *status = msg.str();
×
1045

1046
    g_log << Logger::Error << d_logprefix << msg.str() << endl;
424✔
1047
  }
424✔
1048
}
424✔
1049

1050
// NOLINTNEXTLINE(readability-identifier-length)
1051
void Bind2Backend::queueReloadAndStore(domainid_t id)
1052
{
156✔
1053
  BB2DomainInfo bbold;
156✔
1054
  try {
156✔
1055
    if (!safeGetBBDomainInfo(id, &bbold))
156!
1056
      return;
×
1057
    bbold.d_checknow = false;
156✔
1058
    BB2DomainInfo bbnew(bbold);
156✔
1059
    /* make sure that nothing will be able to alter the existing records,
1060
       we will load them from the zone file instead */
1061
    bbnew.d_records = LookButDontTouch<recordstorage_t>();
156✔
1062
    parseZoneFile(&bbnew);
156✔
1063
    bbnew.d_wasRejectedLastReload = false;
156✔
1064
    safePutBBDomainInfo(bbnew);
156✔
1065
    g_log << Logger::Warning << "Zone '" << bbnew.d_name << "' (" << bbnew.d_filename << ") reloaded" << endl;
156✔
1066
  }
156✔
1067
  catch (PDNSException& ae) {
156✔
1068
    ostringstream msg;
×
1069
    msg << " error at " + nowTime() + " parsing '" << bbold.d_name << "' from file '" << bbold.d_filename << "': " << ae.reason;
×
1070
    g_log << Logger::Warning << "Error parsing '" << bbold.d_name << "' from file '" << bbold.d_filename << "': " << ae.reason << endl;
×
1071
    bbold.d_status = msg.str();
×
1072
    bbold.d_lastcheck = time(nullptr);
×
1073
    bbold.d_wasRejectedLastReload = true;
×
1074
    safePutBBDomainInfo(bbold);
×
1075
  }
×
1076
  catch (std::exception& ae) {
156✔
1077
    ostringstream msg;
×
1078
    msg << " error at " + nowTime() + " parsing '" << bbold.d_name << "' from file '" << bbold.d_filename << "': " << ae.what();
×
1079
    g_log << Logger::Warning << "Error parsing '" << bbold.d_name << "' from file '" << bbold.d_filename << "': " << ae.what() << endl;
×
1080
    bbold.d_status = msg.str();
×
1081
    bbold.d_lastcheck = time(nullptr);
×
1082
    bbold.d_wasRejectedLastReload = true;
×
1083
    safePutBBDomainInfo(bbold);
×
1084
  }
×
1085
}
156✔
1086

1087
bool Bind2Backend::findBeforeAndAfterUnhashed(std::shared_ptr<const recordstorage_t>& records, const DNSName& qname, DNSName& /* unhashed */, DNSName& before, DNSName& after)
1088
{
5,428✔
1089
  // for(const auto& record: *records)
1090
  //   cerr<<record.qname<<"\t"<<makeHexDump(record.qname.toDNSString())<<endl;
1091

1092
  recordstorage_t::const_iterator iterBefore, iterAfter;
5,428✔
1093

1094
  iterBefore = iterAfter = records->upper_bound(qname.makeLowerCase());
5,428✔
1095

1096
  if (iterBefore != records->begin())
5,428!
1097
    --iterBefore;
5,428✔
1098
  while ((!iterBefore->auth && iterBefore->qtype != QType::NS) || !iterBefore->qtype)
6,486✔
1099
    --iterBefore;
1,058✔
1100
  before = iterBefore->qname;
5,428✔
1101

1102
  if (iterAfter == records->end()) {
5,428✔
1103
    iterAfter = records->begin();
752✔
1104
  }
752✔
1105
  else {
4,676✔
1106
    while ((!iterAfter->auth && iterAfter->qtype != QType::NS) || !iterAfter->qtype) {
7,004✔
1107
      ++iterAfter;
2,328✔
1108
      if (iterAfter == records->end()) {
2,328!
1109
        iterAfter = records->begin();
×
1110
        break;
×
1111
      }
×
1112
    }
2,328✔
1113
  }
4,676✔
1114
  after = iterAfter->qname;
5,428✔
1115

1116
  return true;
5,428✔
1117
}
5,428✔
1118

1119
// NOLINTNEXTLINE(readability-identifier-length)
1120
bool Bind2Backend::getBeforeAndAfterNamesAbsolute(domainid_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
1121
{
14,076✔
1122
  BB2DomainInfo bbd;
14,076✔
1123
  if (!safeGetBBDomainInfo(id, &bbd))
14,076!
1124
    return false;
×
1125

1126
  shared_ptr<const recordstorage_t> records = bbd.d_records.get();
14,076✔
1127
  if (!bbd.d_nsec3zone) {
14,076✔
1128
    return findBeforeAndAfterUnhashed(records, qname, unhashed, before, after);
5,428✔
1129
  }
5,428✔
1130
  else {
8,648✔
1131
    const auto& hashindex = boost::multi_index::get<NSEC3Tag>(*records);
8,648✔
1132

1133
    // for(auto iter = first; iter != hashindex.end(); iter++)
1134
    //  cerr<<iter->nsec3hash<<endl;
1135

1136
    auto first = hashindex.upper_bound("");
8,648✔
1137
    auto iter = hashindex.upper_bound(qname.toStringNoDot());
8,648✔
1138

1139
    if (iter == hashindex.end()) {
8,648✔
1140
      --iter;
688✔
1141
      before = DNSName(iter->nsec3hash);
688✔
1142
      after = DNSName(first->nsec3hash);
688✔
1143
    }
688✔
1144
    else {
7,960✔
1145
      after = DNSName(iter->nsec3hash);
7,960✔
1146
      if (iter != first)
7,960✔
1147
        --iter;
7,756✔
1148
      else
204✔
1149
        iter = --hashindex.end();
204✔
1150
      before = DNSName(iter->nsec3hash);
7,960✔
1151
    }
7,960✔
1152
    unhashed = iter->qname + bbd.d_name.operator const DNSName&();
8,648✔
1153

1154
    return true;
8,648✔
1155
  }
8,648✔
1156
}
14,076✔
1157

1158
void Bind2Backend::lookup(const QType& qtype, const DNSName& qname, domainid_t zoneId, DNSPacket* /* pkt_p */)
1159
{
8,230✔
1160
  d_handle.reset();
8,230✔
1161

1162
  static bool mustlog = ::arg().mustDo("query-logging");
8,230✔
1163

1164
  bool found = false;
8,230✔
1165
  ZoneName domain;
8,230✔
1166
  BB2DomainInfo bbd;
8,230✔
1167

1168
  if (mustlog)
8,230!
1169
    g_log << Logger::Warning << "Lookup for '" << qtype.toString() << "' of '" << qname << "' within zoneID " << zoneId << endl;
×
1170

1171
  if (zoneId != UnknownDomainID) {
8,230✔
1172
    if ((found = (safeGetBBDomainInfo(zoneId, &bbd) && qname.isPartOf(bbd.d_name)))) {
7,032!
1173
      domain = std::move(bbd.d_name);
7,032✔
1174
    }
7,032✔
1175
  }
7,032✔
1176
  else {
1,198✔
1177
    domain = ZoneName(qname);
1,198✔
1178
    do {
1,204✔
1179
      found = safeGetBBDomainInfo(domain, &bbd);
1,204✔
1180
    } while (!found && qtype != QType::SOA && domain.chopOff());
1,204✔
1181
  }
1,198✔
1182

1183
  if (!found) {
8,230✔
1184
    if (mustlog)
18!
1185
      g_log << Logger::Warning << "Found no authoritative zone for '" << qname << "' and/or id " << zoneId << endl;
×
1186
    d_handle.d_list = false;
18✔
1187
    return;
18✔
1188
  }
18✔
1189

1190
  if (mustlog)
8,212!
1191
    g_log << Logger::Warning << "Found a zone '" << domain << "' (with id " << bbd.d_id << ") that might contain data " << endl;
×
1192

1193
  d_handle.id = bbd.d_id;
8,212✔
1194
  d_handle.qname = qname.makeRelative(domain); // strip domain name
8,212✔
1195
  d_handle.qtype = qtype;
8,212✔
1196
  d_handle.domain = std::move(domain);
8,212✔
1197

1198
  if (!bbd.current()) {
8,212✔
1199
    g_log << Logger::Warning << "Zone '" << d_handle.domain << "' (" << bbd.d_filename << ") needs reloading" << endl;
12✔
1200
    queueReloadAndStore(bbd.d_id);
12✔
1201
    if (!safeGetBBDomainInfo(d_handle.domain, &bbd))
12!
1202
      throw DBException("Zone '" + bbd.d_name.toLogString() + "' (" + bbd.d_filename + ") gone after reload"); // if we don't throw here, we crash for some reason
×
1203
  }
12✔
1204

1205
  if (!bbd.d_loaded) {
8,212✔
1206
    d_handle.reset();
432✔
1207
    throw DBException("Zone for '" + d_handle.domain.toLogString() + "' in '" + bbd.d_filename + "' not loaded (file missing, corrupt or primary dead)"); // fsck
432✔
1208
  }
432✔
1209

1210
  d_handle.d_records = bbd.d_records.get();
7,780✔
1211

1212
  if (d_handle.d_records->empty())
7,780!
1213
    DLOG(g_log << "Query with no results" << endl);
×
1214

1215
  d_handle.mustlog = mustlog;
7,780✔
1216

1217
  const auto& hashedidx = boost::multi_index::get<UnorderedNameTag>(*d_handle.d_records);
7,780✔
1218
  auto range = hashedidx.equal_range(d_handle.qname);
7,780✔
1219

1220
  d_handle.d_list = false;
7,780✔
1221
  d_handle.d_iter = range.first;
7,780✔
1222
  d_handle.d_end_iter = range.second;
7,780✔
1223
}
7,780✔
1224

1225
Bind2Backend::handle::handle()
1226
{
3,588✔
1227
  mustlog = false;
3,588✔
1228
}
3,588✔
1229

1230
bool Bind2Backend::get(DNSResourceRecord& r)
1231
{
395,742✔
1232
  if (!d_handle.d_records) {
395,742✔
1233
    if (d_handle.mustlog)
18!
1234
      g_log << Logger::Warning << "There were no answers" << endl;
×
1235
    return false;
18✔
1236
  }
18✔
1237

1238
  if (!d_handle.get(r)) {
395,724✔
1239
    if (d_handle.mustlog)
8,442!
1240
      g_log << Logger::Warning << "End of answers" << endl;
×
1241

1242
    d_handle.reset();
8,442✔
1243

1244
    return false;
8,442✔
1245
  }
8,442✔
1246
  if (d_handle.mustlog)
387,282!
1247
    g_log << Logger::Warning << "Returning: '" << r.qtype.toString() << "' of '" << r.qname << "', content: '" << r.content << "'" << endl;
×
1248
  return true;
387,282✔
1249
}
395,724✔
1250

1251
bool Bind2Backend::handle::get(DNSResourceRecord& r)
1252
{
395,724✔
1253
  if (d_list)
395,724✔
1254
    return get_list(r);
377,800✔
1255
  else
17,924✔
1256
    return get_normal(r);
17,924✔
1257
}
395,724✔
1258

1259
void Bind2Backend::handle::reset()
1260
{
17,766✔
1261
  d_records.reset();
17,766✔
1262
  qname.clear();
17,766✔
1263
  mustlog = false;
17,766✔
1264
}
17,766✔
1265

1266
//#define DLOG(x) x
1267
bool Bind2Backend::handle::get_normal(DNSResourceRecord& r)
1268
{
17,924✔
1269
  DLOG(g_log << "Bind2Backend get() was called for " << qtype.toString() << " record for '" << qname << "' - " << d_records->size() << " available in total!" << endl);
17,924✔
1270

1271
  if (d_iter == d_end_iter) {
17,924✔
1272
    return false;
7,780✔
1273
  }
7,780✔
1274

1275
  while (d_iter != d_end_iter && !(qtype.getCode() == QType::ANY || (d_iter)->qtype == qtype.getCode())) {
15,610!
1276
    DLOG(g_log << Logger::Warning << "Skipped " << qname << "/" << QType(d_iter->qtype).toString() << ": '" << d_iter->content << "'" << endl);
5,466✔
1277
    d_iter++;
5,466✔
1278
  }
5,466✔
1279
  if (d_iter == d_end_iter) {
10,144!
1280
    return false;
×
1281
  }
×
1282
  DLOG(g_log << "Bind2Backend get() returning a rr with a " << QType(d_iter->qtype).getCode() << endl);
10,144✔
1283

1284
  const DNSName& domainName(domain);
10,144✔
1285
  r.qname = qname.empty() ? domainName : (qname + domainName);
10,144!
1286
  r.domain_id = id;
10,144✔
1287
  r.content = (d_iter)->content;
10,144✔
1288
  //  r.domain_id=(d_iter)->domain_id;
1289
  r.qtype = (d_iter)->qtype;
10,144✔
1290
  r.ttl = (d_iter)->ttl;
10,144✔
1291

1292
  //if(!d_iter->auth && r.qtype.getCode() != QType::A && r.qtype.getCode()!=QType::AAAA && r.qtype.getCode() != QType::NS)
1293
  //  cerr<<"Warning! Unauth response for qtype "<< r.qtype.toString() << " for '"<<r.qname<<"'"<<endl;
1294
  r.auth = d_iter->auth;
10,144✔
1295

1296
  d_iter++;
10,144✔
1297

1298
  return true;
10,144✔
1299
}
10,144✔
1300

1301
bool Bind2Backend::list(const ZoneName& /* target */, domainid_t domainId, bool /* include_disabled */)
1302
{
662✔
1303
  BB2DomainInfo bbd;
662✔
1304

1305
  if (!safeGetBBDomainInfo(domainId, &bbd)) {
662!
1306
    return false;
×
1307
  }
×
1308

1309
  d_handle.reset();
662✔
1310
  DLOG(g_log << "Bind2Backend constructing handle for list of " << domainId << endl);
662✔
1311

1312
  if (!bbd.d_loaded) {
662!
1313
    throw PDNSException("zone was not loaded, perhaps because of: " + bbd.d_status);
×
1314
  }
×
1315

1316
  d_handle.d_records = bbd.d_records.get(); // give it a copy, which will stay around
662✔
1317
  d_handle.d_qname_iter = d_handle.d_records->begin();
662✔
1318
  d_handle.d_qname_end = d_handle.d_records->end(); // iter now points to a vector of pointers to vector<BBResourceRecords>
662✔
1319

1320
  d_handle.id = domainId;
662✔
1321
  d_handle.domain = bbd.d_name;
662✔
1322
  d_handle.d_list = true;
662✔
1323
  return true;
662✔
1324
}
662✔
1325

1326
bool Bind2Backend::handle::get_list(DNSResourceRecord& r)
1327
{
377,800✔
1328
  if (d_qname_iter != d_qname_end) {
377,800✔
1329
    const DNSName& domainName(domain);
377,138✔
1330
    r.qname = d_qname_iter->qname.empty() ? domainName : (d_qname_iter->qname + domainName);
377,138!
1331
    r.domain_id = id;
377,138✔
1332
    r.content = (d_qname_iter)->content;
377,138✔
1333
    r.qtype = (d_qname_iter)->qtype;
377,138✔
1334
    r.ttl = (d_qname_iter)->ttl;
377,138✔
1335
    r.auth = d_qname_iter->auth;
377,138✔
1336
    d_qname_iter++;
377,138✔
1337
    return true;
377,138✔
1338
  }
377,138✔
1339
  return false;
662✔
1340
}
377,800✔
1341

1342
bool Bind2Backend::autoPrimariesList(std::vector<AutoPrimary>& primaries)
1343
{
×
1344
  if (getArg("autoprimary-config").empty())
×
1345
    return false;
×
1346

1347
  ifstream c_if(getArg("autoprimaries"), std::ios::in);
×
1348
  if (!c_if) {
×
1349
    g_log << Logger::Error << "Unable to open autoprimaries file for read: " << stringerror() << endl;
×
1350
    return false;
×
1351
  }
×
1352

1353
  string line, sip, saccount;
×
1354
  while (getline(c_if, line)) {
×
1355
    std::istringstream ii(line);
×
1356
    ii >> sip;
×
1357
    if (!sip.empty()) {
×
1358
      ii >> saccount;
×
1359
      primaries.emplace_back(sip, "", saccount);
×
1360
    }
×
1361
  }
×
1362

1363
  c_if.close();
×
1364
  return true;
×
1365
}
×
1366

1367
bool Bind2Backend::autoPrimaryBackend(const string& ipAddress, const ZoneName& /* domain */, const vector<DNSResourceRecord>& /* nsset */, string* /* nameserver */, string* account, DNSBackend** backend)
1368
{
×
1369
  // Check whether we have a configfile available.
1370
  if (getArg("autoprimary-config").empty())
×
1371
    return false;
×
1372

1373
  ifstream c_if(getArg("autoprimaries").c_str(), std::ios::in); // this was nocreate?
×
1374
  if (!c_if) {
×
1375
    g_log << Logger::Error << "Unable to open autoprimaries file for read: " << stringerror() << endl;
×
1376
    return false;
×
1377
  }
×
1378

1379
  // Format:
1380
  // <ip> <accountname>
1381
  string line, sip, saccount;
×
1382
  while (getline(c_if, line)) {
×
1383
    std::istringstream ii(line);
×
1384
    ii >> sip;
×
1385
    if (sip == ipAddress) {
×
1386
      ii >> saccount;
×
1387
      break;
×
1388
    }
×
1389
  }
×
1390
  c_if.close();
×
1391

1392
  if (sip != ipAddress) { // ip not found in authorization list - reject
×
1393
    return false;
×
1394
  }
×
1395

1396
  // ip authorized as autoprimary - accept
1397
  *backend = this;
×
1398
  if (saccount.length() > 0)
×
1399
    *account = saccount.c_str();
×
1400

1401
  return true;
×
1402
}
×
1403

1404
BB2DomainInfo Bind2Backend::createDomainEntry(const ZoneName& domain, const string& filename)
1405
{
12✔
1406
  domainid_t newid = 1;
12✔
1407
  { // Find a free zone id nr.
12✔
1408
    auto state = s_state.read_lock();
12✔
1409
    if (!state->empty()) {
12!
1410
      // older (1.53) versions of boost have an expression for s_state.rbegin()
1411
      // that is ambiguous in C++17. So construct it explicitly
1412
      newid = boost::make_reverse_iterator(state->end())->d_id + 1;
12✔
1413
    }
12✔
1414
  }
12✔
1415

1416
  BB2DomainInfo bbd;
12✔
1417
  bbd.d_kind = DomainInfo::Native;
12✔
1418
  bbd.d_id = newid;
12✔
1419
  bbd.d_records = std::make_shared<recordstorage_t>();
12✔
1420
  bbd.d_name = domain;
12✔
1421
  bbd.setCheckInterval(getArgAsNum("check-interval"));
12✔
1422
  bbd.d_filename = filename;
12✔
1423

1424
  return bbd;
12✔
1425
}
12✔
1426

1427
bool Bind2Backend::createSecondaryDomain(const string& ipAddress, const ZoneName& domain, const string& /* nameserver */, const string& account)
1428
{
×
1429
  string filename = getArg("autoprimary-destdir") + '/' + domain.toStringNoDot();
×
1430

1431
  g_log << Logger::Warning << d_logprefix
×
1432
        << " Writing bind config zone statement for superslave zone '" << domain
×
1433
        << "' from autoprimary " << ipAddress << endl;
×
1434

1435
  {
×
1436
    std::lock_guard<std::mutex> l2(s_autosecondary_config_lock);
×
1437

1438
    ofstream c_of(getArg("autoprimary-config").c_str(), std::ios::app);
×
1439
    if (!c_of) {
×
1440
      g_log << Logger::Error << "Unable to open autoprimary configfile for append: " << stringerror() << endl;
×
1441
      throw DBException("Unable to open autoprimary configfile for append: " + stringerror());
×
1442
    }
×
1443

1444
    c_of << endl;
×
1445
    c_of << "# AutoSecondary zone '" << domain.toString() << "' (added: " << nowTime() << ") (account: " << account << ')' << endl;
×
1446
    c_of << "zone \"" << domain.toStringNoDot() << "\" {" << endl;
×
1447
    c_of << "\ttype secondary;" << endl;
×
1448
    c_of << "\tfile \"" << filename << "\";" << endl;
×
1449
    c_of << "\tprimaries { " << ipAddress << "; };" << endl;
×
1450
    c_of << "};" << endl;
×
1451
    c_of.close();
×
1452
  }
×
1453

1454
  BB2DomainInfo bbd = createDomainEntry(domain, filename);
×
1455
  bbd.d_kind = DomainInfo::Secondary;
×
1456
  bbd.d_primaries.emplace_back(ComboAddress(ipAddress, 53));
×
1457
  bbd.setCtime();
×
1458
  safePutBBDomainInfo(bbd);
×
1459

1460
  return true;
×
1461
}
×
1462

1463
bool Bind2Backend::searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result)
1464
{
12✔
1465
  SimpleMatch sm(pattern, true);
12✔
1466
  static bool mustlog = ::arg().mustDo("query-logging");
12✔
1467
  if (mustlog)
12!
1468
    g_log << Logger::Warning << "Search for pattern '" << pattern << "'" << endl;
×
1469

1470
  {
12✔
1471
    auto state = s_state.read_lock();
12✔
1472

1473
    for (const auto& i : *state) {
12!
1474
      BB2DomainInfo h;
×
1475
      if (!safeGetBBDomainInfo(i.d_id, &h)) {
×
1476
        continue;
×
1477
      }
×
1478

1479
      if (!h.d_loaded) {
×
1480
        continue;
×
1481
      }
×
1482

1483
      shared_ptr<const recordstorage_t> rhandle = h.d_records.get();
×
1484

1485
      for (recordstorage_t::const_iterator ri = rhandle->begin(); result.size() < maxResults && ri != rhandle->end(); ri++) {
×
1486
        const DNSName& domainName(i.d_name);
×
1487
        DNSName name = ri->qname.empty() ? domainName : (ri->qname + domainName);
×
1488
        if (sm.match(name) || sm.match(ri->content)) {
×
1489
          DNSResourceRecord r;
×
1490
          r.qname = std::move(name);
×
1491
          r.domain_id = i.d_id;
×
1492
          r.content = ri->content;
×
1493
          r.qtype = ri->qtype;
×
1494
          r.ttl = ri->ttl;
×
1495
          r.auth = ri->auth;
×
1496
          result.push_back(std::move(r));
×
1497
        }
×
1498
      }
×
1499
    }
×
1500
  }
12✔
1501

1502
  return true;
12✔
1503
}
12✔
1504

1505
class Bind2Factory : public BackendFactory
1506
{
1507
public:
1508
  Bind2Factory() :
1509
    BackendFactory("bind") {}
4,714✔
1510

1511
  void declareArguments(const string& suffix = "") override
1512
  {
694✔
1513
    declare(suffix, "ignore-broken-records", "Ignore records that are out-of-bound for the zone.", "no");
694✔
1514
    declare(suffix, "config", "Location of named.conf", "");
694✔
1515
    declare(suffix, "check-interval", "Interval for zonefile changes", "0");
694✔
1516
    declare(suffix, "autoprimary-config", "Location of (part of) named.conf where pdns can write zone-statements to", "");
694✔
1517
    declare(suffix, "autoprimaries", "List of IP-addresses of autoprimaries", "");
694✔
1518
    declare(suffix, "autoprimary-destdir", "Destination directory for newly added secondary zones", ::arg()["config-dir"]);
694✔
1519
    declare(suffix, "dnssec-db", "Filename to store & access our DNSSEC metadatabase, empty for none", "");
694✔
1520
    declare(suffix, "dnssec-db-journal-mode", "SQLite3 journal mode", "WAL");
694✔
1521
    declare(suffix, "hybrid", "Store DNSSEC metadata in other backend", "no");
694✔
1522
  }
694✔
1523

1524
  DNSBackend* make(const string& suffix = "") override
1525
  {
2,138✔
1526
    assertEmptySuffix(suffix);
2,138✔
1527
    return new Bind2Backend(suffix);
2,138✔
1528
  }
2,138✔
1529

1530
  DNSBackend* makeMetadataOnly(const string& suffix = "") override
1531
  {
1,438✔
1532
    assertEmptySuffix(suffix);
1,438✔
1533
    return new Bind2Backend(suffix, false);
1,438✔
1534
  }
1,438✔
1535

1536
private:
1537
  void assertEmptySuffix(const string& suffix)
1538
  {
3,576✔
1539
    if (!suffix.empty())
3,576!
1540
      throw PDNSException("launch= suffixes are not supported on the bindbackend");
×
1541
  }
3,576✔
1542
};
1543

1544
//! Magic class that is activated when the dynamic library is loaded
1545
class Bind2Loader
1546
{
1547
public:
1548
  Bind2Loader()
1549
  {
4,714✔
1550
    BackendMakers().report(std::make_unique<Bind2Factory>());
4,714✔
1551
    g_log << Logger::Info << "[bind2backend] This is the bind backend version " << VERSION
4,714✔
1552
#ifndef REPRODUCIBLE
4,714✔
1553
          << " (" __DATE__ " " __TIME__ ")"
4,714✔
1554
#endif
4,714✔
1555
#ifdef HAVE_SQLITE3
4,714✔
1556
          << " (with bind-dnssec-db support)"
4,714✔
1557
#endif
4,714✔
1558
          << " reporting" << endl;
4,714✔
1559
  }
4,714✔
1560
};
1561
static Bind2Loader bind2loader;
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