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

PowerDNS / pdns / 14531710160

18 Apr 2025 07:40AM UTC coverage: 63.509% (+0.02%) from 63.488%
14531710160

Pull #15441

github

web-flow
Merge 985f4f046 into 5ea1a1229
Pull Request #15441: [evil] ZoneName, step 2

41788 of 100536 branches covered (41.57%)

Branch coverage included in aggregate %.

361 of 414 new or added lines in 38 files covered. (87.2%)

52 existing lines in 13 files now uncovered.

129011 of 168399 relevant lines covered (76.61%)

4240587.91 hits per line

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

60.87
/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
{
15,202✔
87
  d_loaded = false;
15,202✔
88
  d_lastcheck = 0;
15,202✔
89
  d_checknow = false;
15,202✔
90
  d_status = "Unknown";
15,202✔
91
}
15,202✔
92

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

98
bool BB2DomainInfo::current()
99
{
4,092✔
100
  if (d_checknow) {
4,092✔
101
    return false;
6✔
102
  }
6✔
103

104
  if (!d_checkinterval)
4,086!
105
    return true;
4,086✔
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
{
2,673✔
128
  struct stat buf;
2,673✔
129
  if (stat(d_filename.c_str(), &buf) < 0)
2,673!
130
    return;
×
131
  d_ctime = buf.st_ctime;
2,673✔
132
}
2,673✔
133

134
bool Bind2Backend::safeGetBBDomainInfo(int id, BB2DomainInfo* bbd)
135
{
10,700✔
136
  auto state = s_state.read_lock();
10,700✔
137
  state_t::const_iterator iter = state->find(id);
10,700✔
138
  if (iter == state->end()) {
10,700!
139
    return false;
×
140
  }
×
141
  *bbd = *iter;
10,700✔
142
  return true;
10,700✔
143
}
10,700✔
144

145
bool Bind2Backend::safeGetBBDomainInfo(const ZoneName& name, BB2DomainInfo* bbd)
146
{
4,505✔
147
  auto state = s_state.read_lock();
4,505✔
148
  const auto& nameindex = boost::multi_index::get<NameTag>(*state);
4,505✔
149
  auto iter = nameindex.find(name);
4,505✔
150
  if (iter == nameindex.end()) {
4,505✔
151
    return false;
3,251✔
152
  }
3,251✔
153
  *bbd = *iter;
1,254✔
154
  return true;
1,254✔
155
}
4,505✔
156

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

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

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

177
void Bind2Backend::setNotified(uint32_t id, uint32_t serial)
178
{
×
179
  BB2DomainInfo bbd;
×
180
  if (!safeGetBBDomainInfo(id, &bbd))
×
181
    return;
×
182
  bbd.d_lastnotified = serial;
×
183
  safePutBBDomainInfo(bbd);
×
184
}
×
185

186
void Bind2Backend::setLastCheck(uint32_t domain_id, time_t lastcheck)
187
{
72✔
188
  BB2DomainInfo bbd;
72✔
189
  if (safeGetBBDomainInfo(domain_id, &bbd)) {
72!
190
    bbd.d_lastcheck = lastcheck;
72✔
191
    safePutBBDomainInfo(bbd);
72✔
192
  }
72✔
193
}
72✔
194

195
void Bind2Backend::setStale(uint32_t domain_id)
196
{
×
197
  setLastCheck(domain_id, 0);
×
198
}
×
199

200
void Bind2Backend::setFresh(uint32_t domain_id)
201
{
72✔
202
  setLastCheck(domain_id, time(nullptr));
72✔
203
}
72✔
204

205
bool Bind2Backend::startTransaction(const ZoneName& qname, int domainId)
206
{
221✔
207
  if (domainId < 0) {
221✔
208
    d_transaction_tmpname.clear();
149✔
209
    d_transaction_id = domainId;
149✔
210
    return false;
149✔
211
  }
149✔
212
  if (domainId == 0) {
72!
213
    throw DBException("domain_id 0 is invalid for this backend.");
×
214
  }
×
215

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

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

237
    *d_of << "; Written by PowerDNS, don't edit!" << endl;
72✔
238
    *d_of << "; Zone '" << bbd.d_name << "' retrieved from primary " << endl
72✔
239
          << "; at " << nowTime() << endl; // insert primary info here again
72✔
240

241
    return true;
72✔
242
  }
72✔
243
  return false;
×
244
}
72✔
245

246
bool Bind2Backend::commitTransaction()
247
{
221✔
248
  if (d_transaction_id < 0)
221✔
249
    return false;
149✔
250
  d_of.reset();
72✔
251

252
  BB2DomainInfo bbd;
72✔
253
  if (safeGetBBDomainInfo(d_transaction_id, &bbd)) {
72!
254
    if (rename(d_transaction_tmpname.c_str(), bbd.d_filename.c_str()) < 0)
72!
255
      throw DBException("Unable to commit (rename to: '" + bbd.d_filename + "') AXFRed zone: " + stringerror());
×
256
    queueReloadAndStore(bbd.d_id);
72✔
257
  }
72✔
258

259
  d_transaction_id = 0;
72✔
260

261
  return true;
72✔
262
}
72✔
263

264
bool Bind2Backend::abortTransaction()
265
{
×
266
  // -1 = dnssec speciality
267
  // 0  = invalid transact
268
  // >0 = actual transaction
269
  if (d_transaction_id > 0) {
×
270
    unlink(d_transaction_tmpname.c_str());
×
271
    d_of.reset();
×
272
    d_transaction_id = 0;
×
273
  }
×
274

275
  return true;
×
276
}
×
277

278
bool Bind2Backend::feedRecord(const DNSResourceRecord& rr, const DNSName& /* ordername */, bool /* ordernameIsNSEC3 */)
279
{
202,785✔
280
  if (d_transaction_id < 1) {
202,785!
281
    throw DBException("Bind2Backend::feedRecord() called outside of transaction");
×
282
  }
×
283

284
  string qname;
202,785✔
285
  if (d_transaction_qname.empty()) {
202,785!
286
    qname = rr.qname.toString();
×
287
  }
×
288
  else if (rr.qname.isPartOf(d_transaction_qname)) {
202,785!
289
    if (rr.qname == d_transaction_qname.operator const DNSName&()) {
202,785✔
290
      qname = "@";
633✔
291
    }
633✔
292
    else {
202,152✔
293
      DNSName relName = rr.qname.makeRelative(d_transaction_qname);
202,152✔
294
      qname = relName.toStringNoDot();
202,152✔
295
    }
202,152✔
296
  }
202,785✔
297
  else {
×
298
    throw DBException("out-of-zone data '" + rr.qname.toLogString() + "' during AXFR of zone '" + d_transaction_qname.toLogString() + "'");
×
299
  }
×
300

301
  shared_ptr<DNSRecordContent> drc(DNSRecordContent::make(rr.qtype.getCode(), QClass::IN, rr.content));
202,785✔
302
  string content = drc->getZoneRepresentation();
202,785✔
303

304
  // SOA needs stripping too! XXX FIXME - also, this should not be here I think
305
  switch (rr.qtype.getCode()) {
202,785✔
306
  case QType::MX:
56✔
307
  case QType::SRV:
76✔
308
  case QType::CNAME:
208✔
309
  case QType::DNAME:
212✔
310
  case QType::NS:
432✔
311
    stripDomainSuffix(&content, d_transaction_qname.toString());
432✔
312
    // fallthrough
313
  default:
202,785✔
314
    if (d_of && *d_of) {
202,785!
315
      *d_of << qname << "\t" << rr.ttl << "\t" << rr.qtype.toString() << "\t" << content << endl;
202,785✔
316
    }
202,785✔
317
  }
202,785✔
318
  return true;
202,785✔
319
}
202,785✔
320

321
void Bind2Backend::getUpdatedPrimaries(vector<DomainInfo>& changedDomains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
322
{
×
323
  vector<DomainInfo> consider;
×
324
  {
×
325
    auto state = s_state.read_lock();
×
326

327
    for (const auto& i : *state) {
×
328
      if (i.d_kind != DomainInfo::Primary && this->alsoNotify.empty() && i.d_also_notify.empty())
×
329
        continue;
×
330

331
      DomainInfo di;
×
332
      di.id = i.d_id;
×
333
      di.zone = i.d_name;
×
334
      di.last_check = i.d_lastcheck;
×
335
      di.notified_serial = i.d_lastnotified;
×
336
      di.backend = this;
×
337
      di.kind = DomainInfo::Primary;
×
338
      consider.push_back(std::move(di));
×
339
    }
×
340
  }
×
341

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

365
void Bind2Backend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool /* include_disabled */)
366
{
90✔
367
  SOAData soadata;
90✔
368

369
  // prevent deadlock by using getSOA() later on
370
  {
90✔
371
    auto state = s_state.read_lock();
90✔
372
    domains->reserve(state->size());
90✔
373

374
    for (const auto& i : *state) {
250✔
375
      DomainInfo di;
185✔
376
      di.id = i.d_id;
185✔
377
      di.zone = i.d_name;
185✔
378
      di.last_check = i.d_lastcheck;
185✔
379
      di.kind = i.d_kind;
185✔
380
      di.primaries = i.d_primaries;
185✔
381
      di.backend = this;
185✔
382
      domains->push_back(std::move(di));
185✔
383
    };
185✔
384
  }
90✔
385

386
  if (getSerial) {
90✔
387
    for (DomainInfo& di : *domains) {
1,313✔
388
      // do not corrupt di if domain supplied by another backend.
389
      if (di.backend != this)
1,313!
390
        continue;
1,313✔
391
      try {
×
392
        this->getSOA(di.zone, soadata);
×
393
      }
×
394
      catch (...) {
×
395
        continue;
×
396
      }
×
397
      di.serial = soadata.serial;
×
398
    }
×
399
  }
40✔
400
}
90✔
401

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

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

439
bool Bind2Backend::getDomainInfo(const ZoneName& domain, DomainInfo& info, bool getSerial)
440
{
1,002✔
441
  BB2DomainInfo bbd;
1,002✔
442
  if (!safeGetBBDomainInfo(domain, &bbd))
1,002✔
443
    return false;
557✔
444

445
  info.id = bbd.d_id;
445✔
446
  info.zone = domain;
445✔
447
  info.primaries = bbd.d_primaries;
445✔
448
  info.last_check = bbd.d_lastcheck;
445✔
449
  info.backend = this;
445✔
450
  info.kind = bbd.d_kind;
445✔
451
  info.serial = 0;
445✔
452
  if (getSerial) {
445✔
453
    try {
135✔
454
      SOAData sd;
135✔
455
      sd.serial = 0;
135✔
456

457
      getSOA(bbd.d_name, sd); // we might not *have* a SOA yet
135✔
458
      info.serial = sd.serial;
135✔
459
    }
135✔
460
    catch (...) {
135✔
461
    }
72✔
462
  }
135✔
463

464
  return true;
445✔
465
}
445✔
466

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

491
// only parses, does NOT add to s_state!
492
void Bind2Backend::parseZoneFile(BB2DomainInfo* bbd)
493
{
2,739✔
494
  NSEC3PARAMRecordContent ns3pr;
2,739✔
495
  bool nsec3zone = false;
2,739✔
496
  if (d_hybrid) {
2,739!
497
    DNSSECKeeper dk;
×
498
    nsec3zone = dk.getNSEC3PARAM(bbd->d_name, &ns3pr);
×
499
  }
×
500
  else
2,739✔
501
    nsec3zone = getNSEC3PARAMuncached(bbd->d_name, &ns3pr);
2,739✔
502

503
  auto records = std::make_shared<recordstorage_t>();
2,739✔
504
  ZoneParserTNG zpt(bbd->d_filename, bbd->d_name, s_binddirectory, d_upgradeContent);
2,739✔
505
  zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
2,739✔
506
  zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
2,739✔
507
  DNSResourceRecord rr;
2,739✔
508
  string hashed;
2,739✔
509
  while (zpt.get(rr)) {
3,277,135✔
510
    if (rr.qtype.getCode() == QType::NSEC || rr.qtype.getCode() == QType::NSEC3 || rr.qtype.getCode() == QType::NSEC3PARAM)
3,274,396!
511
      continue; // we synthesise NSECs on demand
×
512

513
    insertRecord(records, bbd->d_name, rr.qname, rr.qtype, rr.content, rr.ttl, "");
3,274,396✔
514
  }
3,274,396✔
515
  fixupOrderAndAuth(records, bbd->d_name, nsec3zone, ns3pr);
2,739✔
516
  doEmptyNonTerminals(records, bbd->d_name, nsec3zone, ns3pr);
2,739✔
517
  bbd->setCtime();
2,739✔
518
  bbd->d_loaded = true;
2,739✔
519
  bbd->d_checknow = false;
2,739✔
520
  bbd->d_status = "parsed into memory at " + nowTime();
2,739✔
521
  bbd->d_records = LookButDontTouch<recordstorage_t>(std::move(records));
2,739✔
522
  bbd->d_nsec3zone = nsec3zone;
2,739✔
523
  bbd->d_nsec3param = std::move(ns3pr);
2,739✔
524
}
2,739✔
525

526
/** THIS IS AN INTERNAL FUNCTION! It does moadnsparser prio impedance matching
527
    Much of the complication is due to the efforts to benefit from std::string reference counting copy on write semantics */
528
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)
529
{
3,279,012✔
530
  Bind2DNSRecord bdr;
3,279,012✔
531
  bdr.qname = qname;
3,279,012✔
532

533
  if (zoneName.empty())
3,279,012!
534
    ;
×
535
  else if (bdr.qname.isPartOf(zoneName))
3,279,012✔
536
    bdr.qname.makeUsRelative(zoneName);
3,278,710✔
537
  else {
302✔
538
    string msg = "Trying to insert non-zone data, name='" + bdr.qname.toLogString() + "', qtype=" + qtype.toString() + ", zone='" + zoneName.toLogString() + "'";
302✔
539
    if (s_ignore_broken_records) {
302!
540
      g_log << Logger::Warning << msg << " ignored" << endl;
302✔
541
      return;
302✔
542
    }
302✔
543
    else
×
544
      throw PDNSException(msg);
×
545
  }
302✔
546

547
  //  bdr.qname.swap(bdr.qname);
548

549
  if (!records->empty() && bdr.qname == boost::prior(records->end())->qname)
3,278,710✔
550
    bdr.qname = boost::prior(records->end())->qname;
8,875✔
551

552
  bdr.qname = bdr.qname;
3,278,710✔
553
  bdr.qtype = qtype.getCode();
3,278,710✔
554
  bdr.content = content;
3,278,710✔
555
  bdr.nsec3hash = hashed;
3,278,710✔
556

557
  if (auth != nullptr) // Set auth on empty non-terminals
3,278,710✔
558
    bdr.auth = *auth;
4,616✔
559
  else
3,274,094✔
560
    bdr.auth = true;
3,274,094✔
561

562
  bdr.ttl = ttl;
3,278,710✔
563
  records->insert(std::move(bdr));
3,278,710✔
564
}
3,278,710✔
565

566
string Bind2Backend::DLReloadNowHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
567
{
×
568
  ostringstream ret;
×
569

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

591
string Bind2Backend::DLDomStatusHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
592
{
27✔
593
  ostringstream ret;
27✔
594

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

613
  if (ret.str().empty())
27!
614
    ret << "no domains passed";
×
615

616
  return ret.str();
27✔
617
}
27✔
618

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

653
string Bind2Backend::DLDomExtendedStatusHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
654
{
×
655
  ostringstream ret;
×
656

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

675
  if (ret.str().empty()) {
×
676
    ret << "no domains passed" << std::endl;
×
677
  }
×
678

679
  return ret.str();
×
680
}
×
681

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

693
string Bind2Backend::DLAddDomainHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
694
{
12✔
695
  if (parts.size() < 3)
12!
696
    return "ERROR: Domain name and zone filename are required";
×
697

698
  ZoneName domainname(parts[1]);
12✔
699
  const string& filename = parts[2];
12✔
700
  BB2DomainInfo bbd;
12✔
701
  if (safeGetBBDomainInfo(domainname, &bbd))
12✔
702
    return "Already loaded";
6✔
703

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

707
  struct stat buf;
6✔
708
  if (stat(filename.c_str(), &buf) != 0)
6!
709
    return "Unable to load zone " + domainname.toLogString() + " from " + filename + ": " + strerror(errno);
×
710

711
  Bind2Backend bb2; // createdomainentry needs access to our configuration
6✔
712
  bbd = bb2.createDomainEntry(domainname, filename);
6✔
713
  bbd.d_filename = filename;
6✔
714
  bbd.d_checknow = true;
6✔
715
  bbd.d_loaded = true;
6✔
716
  bbd.d_lastcheck = 0;
6✔
717
  bbd.d_status = "parsing into memory";
6✔
718
  bbd.setCtime();
6✔
719

720
  safePutBBDomainInfo(bbd);
6✔
721

722
  g_zoneCache.add(domainname, bbd.d_id); // make new zone visible
6✔
723

724
  g_log << Logger::Warning << "Zone " << domainname << " loaded" << endl;
6✔
725
  return "Loaded zone " + domainname.toLogString() + " from " + filename;
6✔
726
}
6✔
727

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

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

752
  d_transaction_id = 0;
3,117✔
753
  s_ignore_broken_records = mustDo("ignore-broken-records");
3,117✔
754
  d_upgradeContent = ::arg().mustDo("upgrade-unknown-types");
3,117✔
755

756
  if (!loadZones && d_hybrid)
3,117!
757
    return;
×
758

759
  std::lock_guard<std::mutex> l(s_startup_lock);
3,117✔
760

761
  setupDNSSEC();
3,117✔
762
  if (s_first == 0) {
3,117✔
763
    return;
2,359✔
764
  }
2,359✔
765

766
  if (loadZones) {
758✔
767
    loadConfig();
315✔
768
    s_first = 0;
315✔
769
  }
315✔
770

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

778
Bind2Backend::~Bind2Backend()
779
{
2,993✔
780
  freeStatements();
2,993✔
781
} // deallocate statements
2,993✔
782

783
void Bind2Backend::rediscover(string* status)
784
{
×
785
  loadConfig(status);
×
786
}
×
787

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

796
void Bind2Backend::fixupOrderAndAuth(std::shared_ptr<recordstorage_t>& records, const ZoneName& zoneName, bool nsec3zone, const NSEC3PARAMRecordContent& ns3pr)
797
{
2,667✔
798
  bool skip;
2,667✔
799
  DNSName shorter;
2,667✔
800
  set<DNSName> nssets, dssets;
2,667✔
801

802
  for (const auto& bdr : *records) {
3,274,094✔
803
    if (!bdr.qname.isRoot() && bdr.qtype == QType::NS)
3,274,094✔
804
      nssets.insert(bdr.qname);
3,010✔
805
    else if (bdr.qtype == QType::DS)
3,271,084✔
806
      dssets.insert(bdr.qname);
499✔
807
  }
3,274,094✔
808

809
  for (auto iter = records->begin(); iter != records->end(); iter++) {
3,276,761✔
810
    skip = false;
3,274,094✔
811
    shorter = iter->qname;
3,274,094✔
812

813
    if (!iter->qname.isRoot() && shorter.chopOff() && !iter->qname.isRoot()) {
3,274,094!
814
      do {
3,272,545✔
815
        if (nssets.count(shorter) != 0u) {
3,272,545✔
816
          skip = true;
1,359✔
817
          break;
1,359✔
818
        }
1,359✔
819
      } while (shorter.chopOff() && !iter->qname.isRoot());
3,272,545!
820
    }
3,262,957✔
821

822
    iter->auth = (!skip && (iter->qtype == QType::DS || iter->qtype == QType::RRSIG || (nssets.count(iter->qname) == 0u)));
3,274,094✔
823

824
    if (!skip && nsec3zone && iter->qtype != QType::RRSIG && (iter->auth || (iter->qtype == QType::NS && (ns3pr.d_flags == 0u)) || (dssets.count(iter->qname) != 0u))) {
3,274,094✔
825
      Bind2DNSRecord bdr = *iter;
1,439,445✔
826
      bdr.nsec3hash = toBase32Hex(hashQNameWithSalt(ns3pr, bdr.qname + zoneName.operator const DNSName&()));
1,439,445✔
827
      records->replace(iter, bdr);
1,439,445✔
828
    }
1,439,445✔
829

830
    // cerr<<iter->qname<<"\t"<<QType(iter->qtype).toString()<<"\t"<<iter->nsec3hash<<"\t"<<iter->auth<<endl;
831
  }
3,274,094✔
832
}
2,667✔
833

834
void Bind2Backend::doEmptyNonTerminals(std::shared_ptr<recordstorage_t>& records, const ZoneName& zoneName, bool nsec3zone, const NSEC3PARAMRecordContent& ns3pr)
835
{
2,667✔
836
  bool auth = false;
2,667✔
837
  DNSName shorter;
2,667✔
838
  std::unordered_set<DNSName> qnames;
2,667✔
839
  std::unordered_map<DNSName, bool> nonterm;
2,667✔
840

841
  uint32_t maxent = ::arg().asNum("max-ent-entries");
2,667✔
842

843
  for (const auto& bdr : *records)
2,667✔
844
    qnames.insert(bdr.qname);
3,274,094✔
845

846
  for (const auto& bdr : *records) {
3,274,094✔
847

848
    if (!bdr.auth && bdr.qtype == QType::NS)
3,274,094✔
849
      auth = (!nsec3zone || (ns3pr.d_flags == 0u));
3,010✔
850
    else
3,271,084✔
851
      auth = bdr.auth;
3,271,084✔
852

853
    shorter = bdr.qname;
3,274,094✔
854
    while (shorter.chopOff()) {
6,547,998✔
855
      if (qnames.count(shorter) == 0u) {
3,273,904✔
856
        if (!(maxent)) {
9,422!
857
          g_log << Logger::Error << "Zone '" << zoneName << "' has too many empty non terminals." << endl;
×
858
          return;
×
859
        }
×
860

861
        if (nonterm.count(shorter) == 0u) {
9,422✔
862
          nonterm.emplace(shorter, auth);
4,616✔
863
          --maxent;
4,616✔
864
        }
4,616✔
865
        else if (auth)
4,806✔
866
          nonterm[shorter] = true;
4,710✔
867
      }
9,422✔
868
    }
3,273,904✔
869
  }
3,274,094✔
870

871
  DNSResourceRecord rr;
2,667✔
872
  rr.qtype = "#0";
2,667✔
873
  rr.content = "";
2,667✔
874
  rr.ttl = 0;
2,667✔
875
  for (auto& nt : nonterm) {
4,619✔
876
    string hashed;
4,616✔
877
    rr.qname = nt.first + zoneName.operator const DNSName&();
4,616✔
878
    if (nsec3zone && nt.second)
4,616✔
879
      hashed = toBase32Hex(hashQNameWithSalt(ns3pr, rr.qname));
1,782✔
880
    insertRecord(records, zoneName, rr.qname, rr.qtype, rr.content, rr.ttl, hashed, &nt.second);
4,616✔
881

882
    // cerr<<rr.qname<<"\t"<<rr.qtype.toString()<<"\t"<<hashed<<"\t"<<nt.second<<endl;
883
  }
4,616✔
884
}
2,667✔
885

886
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
887
{
315✔
888
  static int domain_id = 1;
315✔
889

890
  if (!getArg("config").empty()) {
315!
891
    BindParser BP;
315✔
892
    try {
315✔
893
      BP.parse(getArg("config"));
315✔
894
    }
315✔
895
    catch (PDNSException& ae) {
315✔
896
      g_log << Logger::Error << "Error parsing bind configuration: " << ae.reason << endl;
×
897
      throw;
×
898
    }
×
899

900
    vector<BindDomainInfo> domains = BP.getDomains();
315✔
901
    this->alsoNotify = BP.getAlsoNotify();
315✔
902

903
    s_binddirectory = BP.getDirectory();
315✔
904
    //    ZP.setDirectory(d_binddirectory);
905

906
    g_log << Logger::Warning << d_logprefix << " Parsing " << domains.size() << " domain(s), will report when done" << endl;
315✔
907

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

919
    struct stat st;
315✔
920

921
    for (auto& domain : domains) {
2,795✔
922
      if (stat(domain.filename.c_str(), &st) == 0) {
2,661✔
923
        domain.d_dev = st.st_dev;
2,589✔
924
        domain.d_ino = st.st_ino;
2,589✔
925
      }
2,589✔
926
    }
2,661✔
927

928
    sort(domains.begin(), domains.end()); // put stuff in inode order
315✔
929
    for (const auto& domain : domains) {
2,795✔
930
      if (!(domain.hadFileDirective)) {
2,661!
931
        g_log << Logger::Warning << d_logprefix << " Zone '" << domain.name << "' has no 'file' directive set in " << getArg("config") << endl;
×
932
        rejected++;
×
933
        continue;
×
934
      }
×
935

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

945
      BB2DomainInfo bbd;
2,661✔
946
      bool isNew = false;
2,661✔
947

948
      if (!safeGetBBDomainInfo(domain.name, &bbd)) {
2,661!
949
        isNew = true;
2,661✔
950
        bbd.d_id = domain_id++;
2,661✔
951
        bbd.setCheckInterval(getArgAsNum("check-interval"));
2,661✔
952
        bbd.d_lastnotified = 0;
2,661✔
953
        bbd.d_loaded = false;
2,661✔
954
      }
2,661✔
955

956
      // overwrite what we knew about the domain
957
      bbd.d_name = ZoneName(domain.name);
2,661✔
958
      bool filenameChanged = (bbd.d_filename != domain.filename);
2,661✔
959
      bool addressesChanged = (bbd.d_primaries != domain.primaries || bbd.d_also_notify != domain.alsoNotify);
2,661!
960
      bbd.d_filename = domain.filename;
2,661✔
961
      bbd.d_primaries = domain.primaries;
2,661✔
962
      bbd.d_also_notify = domain.alsoNotify;
2,661✔
963

964
      DomainInfo::DomainKind kind = DomainInfo::Native;
2,661✔
965
      if (domain.type == "primary" || domain.type == "master") {
2,661!
966
        kind = DomainInfo::Primary;
2,589✔
967
      }
2,589✔
968
      if (domain.type == "secondary" || domain.type == "slave") {
2,661!
969
        kind = DomainInfo::Secondary;
72✔
970
      }
72✔
971

972
      bool kindChanged = (bbd.d_kind != kind);
2,661✔
973
      bbd.d_kind = kind;
2,661✔
974

975
      newnames.insert(bbd.d_name);
2,661✔
976
      if (filenameChanged || !bbd.d_loaded || !bbd.current()) {
2,661!
977
        g_log << Logger::Info << d_logprefix << " parsing '" << domain.name << "' from file '" << domain.filename << "'" << endl;
2,661✔
978

979
        try {
2,661✔
980
          parseZoneFile(&bbd);
2,661✔
981
        }
2,661✔
982
        catch (PDNSException& ae) {
2,661✔
983
          ostringstream msg;
×
984
          msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.reason;
×
985

986
          if (status != nullptr)
×
987
            *status += msg.str();
×
988
          bbd.d_status = msg.str();
×
989

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

1000
          if (status != nullptr)
72!
1001
            *status += msg.str();
×
1002
          bbd.d_status = msg.str();
72✔
1003
          g_log << Logger::Warning << d_logprefix << msg.str() << endl;
72✔
1004
          rejected++;
72✔
1005
        }
72✔
1006
        catch (std::exception& ae) {
2,661✔
1007
          ostringstream msg;
×
1008
          msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.what();
×
1009

1010
          if (status != nullptr)
×
1011
            *status += msg.str();
×
1012
          bbd.d_status = msg.str();
×
1013

1014
          g_log << Logger::Warning << d_logprefix << msg.str() << endl;
×
1015
          rejected++;
×
1016
        }
×
1017
        safePutBBDomainInfo(bbd);
2,661✔
1018
      }
2,661✔
1019
      else if (addressesChanged || kindChanged) {
×
1020
        safePutBBDomainInfo(bbd);
×
1021
      }
×
1022
    }
2,661✔
1023
    vector<ZoneName> diff;
315✔
1024

1025
    set_difference(oldnames.begin(), oldnames.end(), newnames.begin(), newnames.end(), back_inserter(diff));
315✔
1026
    unsigned int remdomains = diff.size();
315✔
1027

1028
    for (const ZoneName& name : diff) {
315!
1029
      safeRemoveBBDomainInfo(name);
×
1030
    }
×
1031

1032
    // count number of entirely new domains
1033
    diff.clear();
315✔
1034
    set_difference(newnames.begin(), newnames.end(), oldnames.begin(), oldnames.end(), back_inserter(diff));
315✔
1035
    newdomains = diff.size();
315✔
1036

1037
    ostringstream msg;
315✔
1038
    msg << " Done parsing domains, " << rejected << " rejected, " << newdomains << " new, " << remdomains << " removed";
315✔
1039
    if (status != nullptr)
315!
1040
      *status = msg.str();
×
1041

1042
    g_log << Logger::Error << d_logprefix << msg.str() << endl;
315✔
1043
  }
315✔
1044
}
315✔
1045

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

1082
bool Bind2Backend::findBeforeAndAfterUnhashed(std::shared_ptr<const recordstorage_t>& records, const DNSName& qname, DNSName& /* unhashed */, DNSName& before, DNSName& after)
1083
{
2,602✔
1084
  // for(const auto& record: *records)
1085
  //   cerr<<record.qname<<"\t"<<makeHexDump(record.qname.toDNSString())<<endl;
1086

1087
  recordstorage_t::const_iterator iterBefore, iterAfter;
2,602✔
1088

1089
  iterBefore = iterAfter = records->upper_bound(qname.makeLowerCase());
2,602✔
1090

1091
  if (iterBefore != records->begin())
2,602!
1092
    --iterBefore;
2,602✔
1093
  while ((!iterBefore->auth && iterBefore->qtype != QType::NS) || !iterBefore->qtype)
3,131✔
1094
    --iterBefore;
529✔
1095
  before = iterBefore->qname;
2,602✔
1096

1097
  if (iterAfter == records->end()) {
2,602✔
1098
    iterAfter = records->begin();
376✔
1099
  }
376✔
1100
  else {
2,226✔
1101
    while ((!iterAfter->auth && iterAfter->qtype != QType::NS) || !iterAfter->qtype) {
3,319✔
1102
      ++iterAfter;
1,093✔
1103
      if (iterAfter == records->end()) {
1,093!
1104
        iterAfter = records->begin();
×
1105
        break;
×
1106
      }
×
1107
    }
1,093✔
1108
  }
2,226✔
1109
  after = iterAfter->qname;
2,602✔
1110

1111
  return true;
2,602✔
1112
}
2,602✔
1113

1114
bool Bind2Backend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
1115
{
6,780✔
1116
  BB2DomainInfo bbd;
6,780✔
1117
  if (!safeGetBBDomainInfo(id, &bbd))
6,780!
1118
    return false;
×
1119

1120
  shared_ptr<const recordstorage_t> records = bbd.d_records.get();
6,780✔
1121
  if (!bbd.d_nsec3zone) {
6,780✔
1122
    return findBeforeAndAfterUnhashed(records, qname, unhashed, before, after);
2,602✔
1123
  }
2,602✔
1124
  else {
4,178✔
1125
    const auto& hashindex = boost::multi_index::get<NSEC3Tag>(*records);
4,178✔
1126

1127
    // for(auto iter = first; iter != hashindex.end(); iter++)
1128
    //  cerr<<iter->nsec3hash<<endl;
1129

1130
    auto first = hashindex.upper_bound("");
4,178✔
1131
    auto iter = hashindex.upper_bound(qname.toStringNoDot());
4,178✔
1132

1133
    if (iter == hashindex.end()) {
4,178✔
1134
      --iter;
346✔
1135
      before = DNSName(iter->nsec3hash);
346✔
1136
      after = DNSName(first->nsec3hash);
346✔
1137
    }
346✔
1138
    else {
3,832✔
1139
      after = DNSName(iter->nsec3hash);
3,832✔
1140
      if (iter != first)
3,832✔
1141
        --iter;
3,742✔
1142
      else
90✔
1143
        iter = --hashindex.end();
90✔
1144
      before = DNSName(iter->nsec3hash);
3,832✔
1145
    }
3,832✔
1146
    unhashed = iter->qname + bbd.d_name.operator const DNSName&();
4,178✔
1147

1148
    return true;
4,178✔
1149
  }
4,178✔
1150
}
6,780✔
1151

1152
void Bind2Backend::lookup(const QType& qtype, const DNSName& qname, int zoneId, DNSPacket* /* pkt_p */)
1153
{
4,116✔
1154
  d_handle.reset();
4,116✔
1155

1156
  static bool mustlog = ::arg().mustDo("query-logging");
4,116✔
1157

1158
  bool found = false;
4,116✔
1159
  ZoneName domain;
4,116✔
1160
  BB2DomainInfo bbd;
4,116✔
1161

1162
  if (mustlog)
4,116!
1163
    g_log << Logger::Warning << "Lookup for '" << qtype.toString() << "' of '" << qname << "' within zoneID " << zoneId << endl;
×
1164

1165
  if (zoneId >= 0) {
4,116✔
1166
    if ((found = (safeGetBBDomainInfo(zoneId, &bbd) && qname.isPartOf(bbd.d_name)))) {
3,295!
1167
      domain = std::move(bbd.d_name);
3,295✔
1168
    }
3,295✔
1169
  }
3,295✔
1170
  else {
821✔
1171
    domain = ZoneName(qname);
821✔
1172
    do {
824✔
1173
      found = safeGetBBDomainInfo(domain, &bbd);
824✔
1174
    } while (!found && qtype != QType::SOA && domain.chopOff());
824✔
1175
  }
821✔
1176

1177
  if (!found) {
4,116✔
1178
    if (mustlog)
24!
1179
      g_log << Logger::Warning << "Found no authoritative zone for '" << qname << "' and/or id " << zoneId << endl;
×
1180
    d_handle.d_list = false;
24✔
1181
    return;
24✔
1182
  }
24✔
1183

1184
  if (mustlog)
4,092!
1185
    g_log << Logger::Warning << "Found a zone '" << domain << "' (with id " << bbd.d_id << ") that might contain data " << endl;
×
1186

1187
  d_handle.id = bbd.d_id;
4,092✔
1188
  d_handle.qname = qname.makeRelative(domain); // strip domain name
4,092✔
1189
  d_handle.qtype = qtype;
4,092✔
1190
  d_handle.domain = std::move(domain);
4,092✔
1191

1192
  if (!bbd.current()) {
4,092✔
1193
    g_log << Logger::Warning << "Zone '" << d_handle.domain << "' (" << bbd.d_filename << ") needs reloading" << endl;
6✔
1194
    queueReloadAndStore(bbd.d_id);
6✔
1195
    if (!safeGetBBDomainInfo(d_handle.domain, &bbd))
6!
1196
      throw DBException("Zone '" + bbd.d_name.toLogString() + "' (" + bbd.d_filename + ") gone after reload"); // if we don't throw here, we crash for some reason
×
1197
  }
6✔
1198

1199
  if (!bbd.d_loaded) {
4,092✔
1200
    d_handle.reset();
216✔
1201
    throw DBException("Zone for '" + d_handle.domain.toLogString() + "' in '" + bbd.d_filename + "' not loaded (file missing, corrupt or primary dead)"); // fsck
216✔
1202
  }
216✔
1203

1204
  d_handle.d_records = bbd.d_records.get();
3,876✔
1205

1206
  if (d_handle.d_records->empty())
3,876!
1207
    DLOG(g_log << "Query with no results" << endl);
×
1208

1209
  d_handle.mustlog = mustlog;
3,876✔
1210

1211
  const auto& hashedidx = boost::multi_index::get<UnorderedNameTag>(*d_handle.d_records);
3,876✔
1212
  auto range = hashedidx.equal_range(d_handle.qname);
3,876✔
1213

1214
  d_handle.d_list = false;
3,876✔
1215
  d_handle.d_iter = range.first;
3,876✔
1216
  d_handle.d_end_iter = range.second;
3,876✔
1217
}
3,876✔
1218

1219
Bind2Backend::handle::handle()
1220
{
3,117✔
1221
  mustlog = false;
3,117✔
1222
}
3,117✔
1223

1224
bool Bind2Backend::get(DNSResourceRecord& r)
1225
{
197,873✔
1226
  if (!d_handle.d_records) {
197,873✔
1227
    if (d_handle.mustlog)
24!
1228
      g_log << Logger::Warning << "There were no answers" << endl;
×
1229
    return false;
24✔
1230
  }
24✔
1231

1232
  if (!d_handle.get(r)) {
197,849✔
1233
    if (d_handle.mustlog)
4,207!
1234
      g_log << Logger::Warning << "End of answers" << endl;
×
1235

1236
    d_handle.reset();
4,207✔
1237

1238
    return false;
4,207✔
1239
  }
4,207✔
1240
  if (d_handle.mustlog)
193,642!
1241
    g_log << Logger::Warning << "Returning: '" << r.qtype.toString() << "' of '" << r.qname << "', content: '" << r.content << "'" << endl;
×
1242
  return true;
193,642✔
1243
}
197,849✔
1244

1245
bool Bind2Backend::handle::get(DNSResourceRecord& r)
1246
{
197,849✔
1247
  if (d_list)
197,849✔
1248
    return get_list(r);
188,900✔
1249
  else
8,949✔
1250
    return get_normal(r);
8,949✔
1251
}
197,849✔
1252

1253
void Bind2Backend::handle::reset()
1254
{
8,870✔
1255
  d_records.reset();
8,870✔
1256
  qname.clear();
8,870✔
1257
  mustlog = false;
8,870✔
1258
}
8,870✔
1259

1260
//#define DLOG(x) x
1261
bool Bind2Backend::handle::get_normal(DNSResourceRecord& r)
1262
{
8,949✔
1263
  DLOG(g_log << "Bind2Backend get() was called for " << qtype.toString() << " record for '" << qname << "' - " << d_records->size() << " available in total!" << endl);
8,949✔
1264

1265
  if (d_iter == d_end_iter) {
8,949✔
1266
    return false;
3,876✔
1267
  }
3,876✔
1268

1269
  while (d_iter != d_end_iter && !(qtype.getCode() == QType::ANY || (d_iter)->qtype == qtype.getCode())) {
7,806!
1270
    DLOG(g_log << Logger::Warning << "Skipped " << qname << "/" << QType(d_iter->qtype).toString() << ": '" << d_iter->content << "'" << endl);
2,733✔
1271
    d_iter++;
2,733✔
1272
  }
2,733✔
1273
  if (d_iter == d_end_iter) {
5,073!
1274
    return false;
×
1275
  }
×
1276
  DLOG(g_log << "Bind2Backend get() returning a rr with a " << QType(d_iter->qtype).getCode() << endl);
5,073✔
1277

1278
  const DNSName& domainName(domain);
5,073✔
1279
  r.qname = qname.empty() ? domainName : (qname + domainName);
5,073!
1280
  r.domain_id = id;
5,073✔
1281
  r.content = (d_iter)->content;
5,073✔
1282
  //  r.domain_id=(d_iter)->domain_id;
1283
  r.qtype = (d_iter)->qtype;
5,073✔
1284
  r.ttl = (d_iter)->ttl;
5,073✔
1285

1286
  //if(!d_iter->auth && r.qtype.getCode() != QType::A && r.qtype.getCode()!=QType::AAAA && r.qtype.getCode() != QType::NS)
1287
  //  cerr<<"Warning! Unauth response for qtype "<< r.qtype.toString() << " for '"<<r.qname<<"'"<<endl;
1288
  r.auth = d_iter->auth;
5,073✔
1289

1290
  d_iter++;
5,073✔
1291

1292
  return true;
5,073✔
1293
}
5,073✔
1294

1295
bool Bind2Backend::list(const ZoneName& /* target */, int domainId, bool /* include_disabled */)
1296
{
331✔
1297
  BB2DomainInfo bbd;
331✔
1298

1299
  if (!safeGetBBDomainInfo(domainId, &bbd)) {
331!
1300
    return false;
×
1301
  }
×
1302

1303
  d_handle.reset();
331✔
1304
  DLOG(g_log << "Bind2Backend constructing handle for list of " << domainId << endl);
331✔
1305

1306
  if (!bbd.d_loaded) {
331!
1307
    throw PDNSException("zone was not loaded, perhaps because of: " + bbd.d_status);
×
1308
  }
×
1309

1310
  d_handle.d_records = bbd.d_records.get(); // give it a copy, which will stay around
331✔
1311
  d_handle.d_qname_iter = d_handle.d_records->begin();
331✔
1312
  d_handle.d_qname_end = d_handle.d_records->end(); // iter now points to a vector of pointers to vector<BBResourceRecords>
331✔
1313

1314
  d_handle.id = domainId;
331✔
1315
  d_handle.domain = bbd.d_name;
331✔
1316
  d_handle.d_list = true;
331✔
1317
  return true;
331✔
1318
}
331✔
1319

1320
bool Bind2Backend::handle::get_list(DNSResourceRecord& r)
1321
{
188,900✔
1322
  if (d_qname_iter != d_qname_end) {
188,900✔
1323
    const DNSName& domainName(domain);
188,569✔
1324
    r.qname = d_qname_iter->qname.empty() ? domainName : (d_qname_iter->qname + domainName);
188,569!
1325
    r.domain_id = id;
188,569✔
1326
    r.content = (d_qname_iter)->content;
188,569✔
1327
    r.qtype = (d_qname_iter)->qtype;
188,569✔
1328
    r.ttl = (d_qname_iter)->ttl;
188,569✔
1329
    r.auth = d_qname_iter->auth;
188,569✔
1330
    d_qname_iter++;
188,569✔
1331
    return true;
188,569✔
1332
  }
188,569✔
1333
  return false;
331✔
1334
}
188,900✔
1335

1336
bool Bind2Backend::autoPrimariesList(std::vector<AutoPrimary>& primaries)
1337
{
×
1338
  if (getArg("autoprimary-config").empty())
×
1339
    return false;
×
1340

1341
  ifstream c_if(getArg("autoprimaries"), std::ios::in);
×
1342
  if (!c_if) {
×
1343
    g_log << Logger::Error << "Unable to open autoprimaries file for read: " << stringerror() << endl;
×
1344
    return false;
×
1345
  }
×
1346

1347
  string line, sip, saccount;
×
1348
  while (getline(c_if, line)) {
×
1349
    std::istringstream ii(line);
×
1350
    ii >> sip;
×
1351
    if (!sip.empty()) {
×
1352
      ii >> saccount;
×
1353
      primaries.emplace_back(sip, "", saccount);
×
1354
    }
×
1355
  }
×
1356

1357
  c_if.close();
×
1358
  return true;
×
1359
}
×
1360

1361
bool Bind2Backend::autoPrimaryBackend(const string& ipAddress, const ZoneName& /* domain */, const vector<DNSResourceRecord>& /* nsset */, string* /* nameserver */, string* account, DNSBackend** backend)
1362
{
×
1363
  // Check whether we have a configfile available.
1364
  if (getArg("autoprimary-config").empty())
×
1365
    return false;
×
1366

1367
  ifstream c_if(getArg("autoprimaries").c_str(), std::ios::in); // this was nocreate?
×
1368
  if (!c_if) {
×
1369
    g_log << Logger::Error << "Unable to open autoprimaries file for read: " << stringerror() << endl;
×
1370
    return false;
×
1371
  }
×
1372

1373
  // Format:
1374
  // <ip> <accountname>
1375
  string line, sip, saccount;
×
1376
  while (getline(c_if, line)) {
×
1377
    std::istringstream ii(line);
×
1378
    ii >> sip;
×
1379
    if (sip == ipAddress) {
×
1380
      ii >> saccount;
×
1381
      break;
×
1382
    }
×
1383
  }
×
1384
  c_if.close();
×
1385

1386
  if (sip != ipAddress) { // ip not found in authorization list - reject
×
1387
    return false;
×
1388
  }
×
1389

1390
  // ip authorized as autoprimary - accept
1391
  *backend = this;
×
1392
  if (saccount.length() > 0)
×
1393
    *account = saccount.c_str();
×
1394

1395
  return true;
×
1396
}
×
1397

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

1410
  BB2DomainInfo bbd;
6✔
1411
  bbd.d_kind = DomainInfo::Native;
6✔
1412
  bbd.d_id = newid;
6✔
1413
  bbd.d_records = std::make_shared<recordstorage_t>();
6✔
1414
  bbd.d_name = domain;
6✔
1415
  bbd.setCheckInterval(getArgAsNum("check-interval"));
6✔
1416
  bbd.d_filename = filename;
6✔
1417

1418
  return bbd;
6✔
1419
}
6✔
1420

1421
bool Bind2Backend::createSecondaryDomain(const string& ipAddress, const ZoneName& domain, const string& /* nameserver */, const string& account)
1422
{
×
1423
  string filename = getArg("autoprimary-destdir") + '/' + domain.toStringNoDot();
×
1424

1425
  g_log << Logger::Warning << d_logprefix
×
1426
        << " Writing bind config zone statement for superslave zone '" << domain
×
1427
        << "' from autoprimary " << ipAddress << endl;
×
1428

1429
  {
×
1430
    std::lock_guard<std::mutex> l2(s_autosecondary_config_lock);
×
1431

1432
    ofstream c_of(getArg("autoprimary-config").c_str(), std::ios::app);
×
1433
    if (!c_of) {
×
1434
      g_log << Logger::Error << "Unable to open autoprimary configfile for append: " << stringerror() << endl;
×
1435
      throw DBException("Unable to open autoprimary configfile for append: " + stringerror());
×
1436
    }
×
1437

1438
    c_of << endl;
×
1439
    c_of << "# AutoSecondary zone '" << domain.toString() << "' (added: " << nowTime() << ") (account: " << account << ')' << endl;
×
1440
    c_of << "zone \"" << domain.toStringNoDot() << "\" {" << endl;
×
1441
    c_of << "\ttype secondary;" << endl;
×
1442
    c_of << "\tfile \"" << filename << "\";" << endl;
×
1443
    c_of << "\tprimaries { " << ipAddress << "; };" << endl;
×
1444
    c_of << "};" << endl;
×
1445
    c_of.close();
×
1446
  }
×
1447

1448
  BB2DomainInfo bbd = createDomainEntry(domain, filename);
×
1449
  bbd.d_kind = DomainInfo::Secondary;
×
1450
  bbd.d_primaries.emplace_back(ComboAddress(ipAddress, 53));
×
1451
  bbd.setCtime();
×
1452
  safePutBBDomainInfo(bbd);
×
1453

1454
  return true;
×
1455
}
×
1456

1457
bool Bind2Backend::searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result)
1458
{
18✔
1459
  SimpleMatch sm(pattern, true);
18✔
1460
  static bool mustlog = ::arg().mustDo("query-logging");
18✔
1461
  if (mustlog)
18!
1462
    g_log << Logger::Warning << "Search for pattern '" << pattern << "'" << endl;
×
1463

1464
  {
18✔
1465
    auto state = s_state.read_lock();
18✔
1466

1467
    for (const auto& i : *state) {
18!
1468
      BB2DomainInfo h;
×
1469
      if (!safeGetBBDomainInfo(i.d_id, &h)) {
×
1470
        continue;
×
1471
      }
×
1472

1473
      if (!h.d_loaded) {
×
1474
        continue;
×
1475
      }
×
1476

1477
      shared_ptr<const recordstorage_t> rhandle = h.d_records.get();
×
1478

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

1496
  return true;
18✔
1497
}
18✔
1498

1499
class Bind2Factory : public BackendFactory
1500
{
1501
public:
1502
  Bind2Factory() :
1503
    BackendFactory("bind") {}
3,928✔
1504

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

1518
  DNSBackend* make(const string& suffix = "") override
1519
  {
2,211✔
1520
    assertEmptySuffix(suffix);
2,211✔
1521
    return new Bind2Backend(suffix);
2,211✔
1522
  }
2,211✔
1523

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

1530
private:
1531
  void assertEmptySuffix(const string& suffix)
1532
  {
3,111✔
1533
    if (!suffix.empty())
3,111!
1534
      throw PDNSException("launch= suffixes are not supported on the bindbackend");
×
1535
  }
3,111✔
1536
};
1537

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