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

PowerDNS / pdns / 27793462623

18 Jun 2026 01:44PM UTC coverage: 61.708% (-9.4%) from 71.071%
27793462623

push

github

web-flow
Merge pull request #17576 from karelbilek/kb/readmes

dnsdist: Fix build readme - link, meson, mac brew

80601 of 203712 branches covered (39.57%)

Branch coverage included in aggregate %.

164460 of 193419 relevant lines covered (85.03%)

6800757.76 hits per line

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

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

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

98
bool BB2DomainInfo::current()
99
{
×
100
  if (d_checknow) {
×
101
    return false;
×
102
  }
×
103

104
  if (!d_checkinterval)
×
105
    return true;
×
106

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

110
  d_lastcheck = time(nullptr);
×
111
  // Our data is still current if all the files it has been obtained from have
112
  // their modification times unchanged since the last parse.
113
  return std::all_of(d_fileinfo.cbegin(), d_fileinfo.cend(),
×
114
                     [](const auto& fileinfo) { return getCtime(fileinfo.first) == fileinfo.second; });
×
115
}
116

117
time_t BB2DomainInfo::getCtime(const std::string& filename)
118
{
×
119
  struct stat buf;
120

×
121
  if (filename.empty() || stat(filename.c_str(), &buf) < 0) {
×
122
    return 0;
×
123
  }
×
124
  return buf.st_ctime;
×
125
}
126

127
void BB2DomainInfo::updateCtime()
2✔
128
{
2✔
129
  struct stat buf;
2!
130

131
  for (auto& fileinfo : d_fileinfo) {
2!
132
    if (stat(fileinfo.first.c_str(), &buf) < 0) {
2!
133
      buf.st_ctime = 0;
134
    }
135
    fileinfo.second = buf.st_ctime;
136
  }
×
137
}
×
138

×
139
// NOLINTNEXTLINE(readability-identifier-length)
×
140
bool Bind2Backend::safeGetBBDomainInfo(domainid_t id, BB2DomainInfo* bbd)
141
{
×
142
  auto state = s_state.read_lock();
×
143
  state_t::const_iterator iter = state->find(id);
×
144
  if (iter == state->end()) {
×
145
    return false;
146
  }
603✔
147
  *bbd = *iter;
1,208✔
148
  return true;
1,208✔
149
}
1,208✔
150

1,208!
151
bool Bind2Backend::safeGetBBDomainInfo(const ZoneName& name, BB2DomainInfo* bbd)
1,208!
152
{
1,863✔
153
  auto state = s_state.read_lock();
1,260✔
154
  const auto& nameindex = boost::multi_index::get<NameTag>(*state);
655✔
155
  auto iter = nameindex.find(name);
1,258✔
156
  if (iter == nameindex.end()) {
1,260!
157
    return false;
655✔
158
  }
655✔
159
  *bbd = *iter;
×
160
  return true;
×
161
}
655✔
162

163
bool Bind2Backend::safeRemoveBBDomainInfo(const ZoneName& name)
164
{
×
165
  auto state = s_state.write_lock();
×
166
  using nameindex_t = state_t::index<NameTag>::type;
×
167
  nameindex_t& nameindex = boost::multi_index::get<NameTag>(*state);
×
168

169
  nameindex_t::iterator iter = nameindex.find(name);
×
170
  if (iter == nameindex.end()) {
×
171
    return false;
172
  }
2✔
173
  nameindex.erase(iter);
2✔
174
  return true;
2✔
175
}
2✔
176

177
void Bind2Backend::safePutBBDomainInfo(const BB2DomainInfo& bbd)
178
{
179
  auto state = s_state.write_lock();
180
  replacing_insert(*state, bbd);
×
181
}
×
182

×
183
// NOLINTNEXTLINE(readability-identifier-length)
184
void Bind2Backend::setNotified(domainid_t id, uint32_t serial)
185
{
186
  BB2DomainInfo bbd;
187
  if (!safeGetBBDomainInfo(id, &bbd))
×
188
    return;
189
  bbd.d_lastnotified = serial;
×
190
  safePutBBDomainInfo(bbd);
×
191
}
×
192

×
193
// NOLINTNEXTLINE(readability-identifier-length)
194
void Bind2Backend::setLastCheck(domainid_t domain_id, time_t lastcheck)
195
{
196
  BB2DomainInfo bbd;
×
197
  if (safeGetBBDomainInfo(domain_id, &bbd)) {
×
198
    bbd.d_lastcheck = lastcheck;
199
    safePutBBDomainInfo(bbd);
200
  }
201
}
×
202

203
void Bind2Backend::setStale(domainid_t domain_id)
204
{
205
  Bind2Backend::setLastCheck(domain_id, 0);
206
}
×
207

×
208
void Bind2Backend::setFresh(domainid_t domain_id)
209
{
×
210
  Bind2Backend::setLastCheck(domain_id, time(nullptr));
×
211
}
×
212

×
213
bool Bind2Backend::startTransaction(const ZoneName& qname, domainid_t domainId)
214
{
×
215
  if (domainId == UnknownDomainID) {
×
216
    d_transaction_tmpname.clear();
×
217
    d_transaction_id = UnknownDomainID;
×
218
    // No support for domain contents deletion
219
    return false;
×
220
  }
×
221
  if (domainId == 0) {
×
222
    throw DBException("domain_id 0 is invalid for this backend.");
×
223
  }
×
224

225
  d_transaction_id = domainId;
226
  d_transaction_qname = qname;
×
227
  BB2DomainInfo bbd;
×
228
  if (safeGetBBDomainInfo(domainId, &bbd)) {
×
229
    d_transaction_tmpname = bbd.main_filename() + "XXXXXX";
230
    int fd = mkstemp(&d_transaction_tmpname.at(0));
×
231
    if (fd == -1) {
×
232
      throw DBException("Unable to create a unique temporary zonefile '" + d_transaction_tmpname + "': " + stringerror());
×
233
    }
×
234

235
    d_of = std::make_unique<ofstream>(d_transaction_tmpname);
×
236
    if (!*d_of) {
×
237
      unlink(d_transaction_tmpname.c_str());
×
238
      close(fd);
×
239
      fd = -1;
×
240
      d_of.reset();
241
      throw DBException("Unable to open temporary zonefile '" + d_transaction_tmpname + "': " + stringerror());
×
242
    }
×
243
    close(fd);
×
244
    fd = -1;
245

246
    *d_of << "; Written by PowerDNS, don't edit!" << endl;
247
    *d_of << "; Zone '" << bbd.d_name << "' retrieved from primary " << endl
×
248
          << "; at " << nowTime() << endl; // insert primary info here again
×
249

250
    return true;
251
  }
252
  return false;
253
}
×
254

×
255
bool Bind2Backend::commitTransaction()
256
{
×
257
  // d_transaction_id is only set to a valid domain id if we are actually
258
  // setting up a replacement zone file with the updated data.
259
  if (d_transaction_id == UnknownDomainID) {
×
260
    return false;
×
261
  }
×
262
  d_of.reset();
×
263

264
  BB2DomainInfo bbd;
265
  if (safeGetBBDomainInfo(d_transaction_id, &bbd)) {
×
266
    if (rename(d_transaction_tmpname.c_str(), bbd.main_filename().c_str()) < 0) {
×
267
      throw DBException("Unable to commit (rename to: '" + bbd.main_filename() + "') AXFRed zone: " + stringerror());
268
    }
269
    queueReloadAndStore(bbd.d_id);
×
270
  }
271

272
  d_transaction_id = UnknownDomainID;
×
273

274
  return true;
275
}
×
276

277
bool Bind2Backend::abortTransaction()
278
{
279
  // d_transaction_id is only set to a valid domain id if we are actually
280
  // setting up a replacement zone file with the updated data.
281
  if (d_transaction_id != UnknownDomainID) {
×
282
    unlink(d_transaction_tmpname.c_str());
283
    d_of.reset();
284
    d_transaction_id = UnknownDomainID;
285
  }
286

287
  return true;
×
288
}
×
289

290
/** does domain end on suffix? Is smart about "wwwds9a.nl" "ds9a.nl" not matching */
×
291
static bool endsOn(const string& domain, const string& suffix)
×
292
{
×
293
  if (domain.size() <= suffix.size()) {
×
294
    return false;
×
295
  }
×
296

297
  string::size_type dpos = domain.size() - suffix.size() - 1;
298
  if (domain[dpos++] != '.') {
×
299
    return false;
×
300
  }
×
301
  // That dot might have been escaped. So we now need to count how many '\'
302
  // characters we can find in a row before it; if their number is odd, the
×
303
  // dot is escaped and we are not a proper suffix.
304
  size_t slashes{0};
×
305
  while (dpos >= 2 + slashes && domain.at(dpos - 2 - slashes) == '\\') {
×
306
    ++slashes;
307
  }
×
308
  if ((slashes % 2) != 0) {
×
309
    return false;
×
310
  }
×
311

312
  string::size_type spos = 0;
313
  for (; dpos < domain.size(); ++dpos, ++spos) {
×
314
    if (!pdns_iequals_ch(domain[dpos], suffix[spos])) {
×
315
      return false;
316
    }
317
  }
×
318

×
319
  return true;
×
320
}
321

×
322
/** strips a domain suffix from a domain */
323
static void stripDomainSuffix(string* qname, const ZoneName& zonename)
324
{
×
325
  std::string domain = zonename.operator const DNSName&().toString();
×
326

327
  if (domain.empty()) {
×
328
    return;
×
329
  }
330
  if (pdns_iequals(*qname, domain)) {
×
331
    *qname = "@";
×
332
    return;
333
  }
334
  if (endsOn(*qname, domain)) {
×
335
    auto prefix = qname->size() - domain.size();
×
336
    qname->resize(prefix - 1); // also strip dot
×
337
  }
×
338
}
×
339

×
340
// Perform adequate escaping of characters which have special meaning in
341
// Bind zone files.
342
// Note that the input is supposed to be a DNSName::toString() - or any of
×
343
// its variants - so we assume \ and . have been correctly escaped by
×
344
// DNSName::appendEscapedLabel already.
×
345
static const std::string bindEscape(const std::string& name)
346
{
×
347
  std::string ret;
×
348
  std::array<char, 5> ebuf{};
×
349

×
350
  for (char letter : name) {
×
351
    switch (letter) {
×
352
    case '$':
×
353
    case '@':
×
354
    case '"':
×
355
    case ';':
×
356
    case '(':
×
357
    case ')':
×
358
      snprintf(ebuf.data(), ebuf.size(), "\\%03u", static_cast<unsigned char>(letter));
×
359
      ret += ebuf.data();
360
      break;
×
361
    default:
×
362
      ret += letter;
363
      break;
364
    }
×
365
  }
×
366
  return ret;
×
367
}
×
368

369
bool Bind2Backend::feedRecord(const DNSResourceRecord& rr, const DNSName& /* ordername */, bool /* ordernameIsNSEC3 */)
370
{
×
371
  if (d_transaction_id == UnknownDomainID) {
×
372
    throw DBException("Bind2Backend::feedRecord() called outside of transaction");
×
373
  }
×
374

×
375
  string qname;
×
376
  if (d_transaction_qname.empty()) {
×
377
    qname = bindEscape(rr.qname.toString());
×
378
  }
×
379
  else if (rr.qname.isPartOf(d_transaction_qname)) {
×
380
    if (rr.qname == d_transaction_qname.operator const DNSName&()) {
×
381
      qname = "@";
×
382
    }
×
383
    else {
×
384
      DNSName relName = rr.qname.makeRelative(d_transaction_qname);
×
385
      qname = bindEscape(relName.toStringNoDot());
386
    }
×
387
  }
×
388
  else {
389
    throw DBException("out-of-zone data '" + rr.qname.toLogString() + "' during AXFR of zone '" + d_transaction_qname.toLogString() + "'");
390
  }
×
391

×
392
  shared_ptr<DNSRecordContent> drc(DNSRecordContent::make(rr.qtype.getCode(), QClass::IN, rr.content));
×
393
  string content = drc->getZoneRepresentation();
×
394

×
395
  // SOA needs stripping too! XXX FIXME - also, this should not be here I think
66!
396
  switch (rr.qtype.getCode()) {
66✔
397
  case QType::MX:
×
398
  case QType::SRV:
×
399
  case QType::CNAME:
66!
400
  case QType::DNAME:
66!
401
  case QType::NS:
66!
402
    stripDomainSuffix(&content, d_transaction_qname);
403
    [[fallthrough]];
66!
404
  default:
×
405
    if (d_of && *d_of) {
×
406
      *d_of << qname << "\t" << rr.ttl << "\t" << rr.qtype.toString() << "\t" << content << endl;
407
    }
×
408
  }
×
409
  return true;
×
410
}
×
411

412
void Bind2Backend::getUpdatedPrimaries(vector<DomainInfo>& changedDomains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
×
413
{
66!
414
  vector<DomainInfo> consider;
415
  {
66✔
416
    auto state = s_state.read_lock();
1,340✔
417

418
    for (const auto& i : *state) {
1,340!
419
      if (i.d_kind != DomainInfo::Primary && this->alsoNotify.empty() && i.d_also_notify.empty())
1,340!
420
        continue;
×
421

422
      DomainInfo di;
×
423
      di.id = i.d_id;
×
424
      di.zone = i.d_name;
×
425
      di.last_check = i.d_lastcheck;
×
426
      di.notified_serial = i.d_lastnotified;
427
      di.backend = this;
×
428
      di.kind = DomainInfo::Primary;
41!
429
      consider.push_back(std::move(di));
66✔
430
    }
431
  }
432

433
  SOAData soadata;
×
434
  for (DomainInfo& di : consider) {
×
435
    soadata.serial = 0;
×
436
    try {
×
437
      this->getSOA(di.zone, di.id, soadata); // we might not *have* a SOA yet, but this might trigger a load of it
×
438
    }
×
439
    catch (...) {
×
440
      continue;
×
441
    }
×
442
    if (di.notified_serial != soadata.serial) {
×
443
      BB2DomainInfo bbd;
×
444
      if (safeGetBBDomainInfo(di.id, &bbd)) {
×
445
        bbd.d_lastnotified = soadata.serial;
×
446
        safePutBBDomainInfo(bbd);
×
447
      }
×
448
      if (di.notified_serial) { // don't do notification storm on startup
×
449
        di.serial = soadata.serial;
450
        changedDomains.push_back(std::move(di));
451
      }
73✔
452
    }
73!
453
  }
454
}
455

73✔
456
void Bind2Backend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool /* include_disabled */)
73✔
457
{
167✔
458
  SOAData soadata;
94✔
459

73!
460
  // prevent deadlock by using getSOA() later on
461
  {
94✔
462
    auto state = s_state.read_lock();
94✔
463
    domains->reserve(state->size());
94!
464

465
    for (const auto& i : *state) {
94!
466
      DomainInfo di;
×
467
      di.id = i.d_id;
468
      di.zone = i.d_name;
469
      di.last_check = i.d_lastcheck;
654✔
470
      di.kind = i.d_kind;
581✔
471
      di.primaries = i.d_primaries;
654✔
472
      di.backend = this;
1,943✔
473
      domains->push_back(std::move(di));
474
    };
1,362!
475
  }
1,456✔
476

477
  if (getSerial) {
94✔
478
    for (DomainInfo& di : *domains) {
1,509✔
479
      // do not corrupt di if domain supplied by another backend.
480
      if (di.backend != this)
1,509!
481
        continue;
1,509!
482
      try {
×
483
        this->getSOA(di.zone, di.id, soadata);
×
484
      }
42✔
485
      catch (...) {
73✔
486
        continue;
487
      }
488
      di.serial = soadata.serial;
×
489
    }
×
490
  }
42✔
491
}
94✔
492

493
void Bind2Backend::getUnfreshSecondaryInfos(vector<DomainInfo>* unfreshDomains)
×
494
{
×
495
  vector<DomainInfo> domains;
496
  {
497
    auto state = s_state.read_lock();
4✔
498
    domains.reserve(state->size());
499
    for (const auto& i : *state) {
4!
500
      if (i.d_kind != DomainInfo::Secondary)
×
501
        continue;
×
502
      DomainInfo sd;
503
      sd.id = i.d_id;
4✔
504
      sd.zone = i.d_name;
4!
505
      sd.primaries = i.d_primaries;
×
506
      sd.last_check = i.d_lastcheck;
×
507
      sd.backend = this;
508
      sd.kind = DomainInfo::Secondary;
×
509
      domains.push_back(std::move(sd));
4✔
510
    }
4!
511
  }
×
512
  unfreshDomains->reserve(domains.size());
×
513

514
  for (DomainInfo& sd : domains) {
×
515
    SOAData soadata;
×
516
    soadata.refresh = 0;
×
517
    soadata.serial = 0;
×
518
    try {
4✔
519
      getSOA(sd.zone, sd.id, soadata); // we might not *have* a SOA yet
×
520
    }
521
    catch (...) {
522
    }
2✔
523
    sd.serial = soadata.serial;
2✔
524
    if (sd.last_check + soadata.refresh < time(nullptr))
2!
525
      unfreshDomains->push_back(std::move(sd));
587!
526
  }
585✔
527
}
585!
528

585✔
529
bool Bind2Backend::getDomainInfo(const ZoneName& domain, DomainInfo& info, bool getSerial)
2✔
530
{
637✔
531
  BB2DomainInfo bbd;
635✔
532
  if (!safeGetBBDomainInfo(domain, &bbd))
637!
533
    return false;
637✔
534

2✔
535
  info.id = bbd.d_id;
2✔
536
  info.zone = domain;
2✔
537
  info.primaries = bbd.d_primaries;
2!
538
  info.last_check = bbd.d_lastcheck;
12✔
539
  info.backend = this;
10!
540
  info.kind = bbd.d_kind;
×
541
  info.serial = 0;
542
  if (getSerial) {
10!
543
    try {
10✔
544
      SOAData sd;
2✔
545
      sd.serial = 0;
2✔
546

2✔
547
      getSOA(bbd.d_name, bbd.d_id, sd); // we might not *have* a SOA yet
2✔
548
      info.serial = sd.serial;
2✔
549
    }
2✔
550
    catch (...) {
2✔
551
    }
2✔
552
  }
2✔
553

6✔
554
  return true;
555
}
4!
556

557
void Bind2Backend::alsoNotifies(const ZoneName& domain, set<string>* ips)
558
{
14✔
559
  // combine global list with local list
14✔
560
  for (const auto& i : this->alsoNotify) {
18!
561
    (*ips).insert(i);
×
562
  }
10!
563
  // check metadata too if available
564
  vector<string> meta;
14!
565
  if (getDomainMetadata(domain, "ALSO-NOTIFY", meta)) {
18!
566
    for (const auto& str : meta) {
4!
567
      (*ips).insert(str);
×
568
    }
×
569
  }
×
570
  auto state = s_state.read_lock();
4✔
571
  for (const auto& i : *state) {
4!
572
    if (i.d_name == domain) {
×
573
      for (const auto& it : i.d_also_notify) {
×
574
        (*ips).insert(it);
4✔
575
      }
576
      return;
577
    }
578
  }
10✔
579
}
8✔
580

581
// only parses, does NOT add to s_state!
10!
582
void Bind2Backend::parseZoneFile(BB2DomainInfo* bbd)
10✔
583
{
10✔
584
  NSEC3PARAMRecordContent ns3pr;
10✔
585
  bool nsec3zone = false;
586
  if (d_hybrid) {
10!
587
    DNSSECKeeper dk(d_slog);
588
    nsec3zone = dk.getNSEC3PARAM(bbd->d_name, &ns3pr);
10✔
589
  }
10✔
590
  else
591
    nsec3zone = getNSEC3PARAMuncached(bbd->d_name, &ns3pr);
10✔
592

10✔
593
  auto records = std::make_shared<recordstorage_t>();
10✔
594
  ZoneParserTNG zpt(bbd->main_filename(), bbd->d_name, s_binddirectory, d_upgradeContent);
×
595
  zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
×
596
  zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
×
597
  DNSResourceRecord rr;
598
  string hashed;
599
  while (zpt.get(rr)) {
×
600
    if (rr.qtype.getCode() == QType::NSEC || rr.qtype.getCode() == QType::NSEC3 || rr.qtype.getCode() == QType::NSEC3PARAM)
×
601
      continue; // we synthesise NSECs on demand
×
602

×
603
    insertRecord(records, bbd->d_name, rr.qname, rr.qtype, rr.content, rr.ttl, "");
×
604
  }
×
605
  fixupOrderAndAuth(records, bbd->d_name, nsec3zone, ns3pr);
×
606
  doEmptyNonTerminals(records, bbd->d_name, nsec3zone, ns3pr);
×
607
  bbd->d_fileinfo = zpt.getFileset();
×
608
  bbd->d_loaded = true;
×
609
  bbd->d_checknow = false;
×
610
  bbd->d_status = "parsed into memory at " + nowTime();
611
  bbd->d_records = LookButDontTouch<recordstorage_t>(std::move(records));
612
  bbd->d_nsec3zone = nsec3zone;
613
  bbd->d_nsec3param = std::move(ns3pr);
614
}
×
615

×
616
/** THIS IS AN INTERNAL FUNCTION! It does moadnsparser prio impedance matching
617
    Much of the complication is due to the efforts to benefit from std::string reference counting copy on write semantics */
618
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)
×
619
{
620
  Bind2DNSRecord bdr;
×
621
  bdr.qname = qname;
×
622

623
  if (zoneName.empty())
×
624
    ;
×
625
  else if (bdr.qname.isPartOf(zoneName))
×
626
    bdr.qname.makeUsRelative(zoneName);
×
627
  else {
×
628
    string msg = "Trying to insert non-zone data, name='" + bdr.qname.toLogString() + "', qtype=" + qtype.toString() + ", zone='" + zoneName.toLogString() + "'";
×
629
    if (s_ignore_broken_records) {
×
630
      SLOG(g_log << Logger::Warning << msg << " ignored" << endl,
×
631
           d_slog->info(Logr::Warning, "Non-zone data record ignored", "zone", Logging::Loggable(zoneName), "name", Logging::Loggable(bdr.qname), "qtype", Logging::Loggable(qtype)));
632
      return;
633
    }
×
634
    throw PDNSException(std::move(msg));
×
635
  }
636

637
  //  bdr.qname.swap(bdr.qname);
×
638

×
639
  if (!records->empty() && bdr.qname == boost::prior(records->end())->qname)
×
640
    bdr.qname = boost::prior(records->end())->qname;
641

×
642
  bdr.qname = bdr.qname;
×
643
  bdr.qtype = qtype.getCode();
×
644
  bdr.content = content;
645
  bdr.nsec3hash = hashed;
646

647
  if (auth != nullptr) // Set auth on empty non-terminals
×
648
    bdr.auth = *auth;
649
  else
650
    bdr.auth = true;
651

652
  bdr.ttl = ttl;
×
653
  records->insert(std::move(bdr));
654
}
×
655

656
string Bind2Backend::DLReloadNowHandler(const vector<string>& parts, Utility::pid_t /* ppid */, Logr::log_t /* slog */)
×
657
{
×
658
  ostringstream ret;
×
659

×
660
  for (auto i = parts.begin() + 1; i < parts.end(); ++i) {
×
661
    BB2DomainInfo bbd;
×
662
    ZoneName zone(*i);
×
663
    if (safeGetBBDomainInfo(zone, &bbd)) {
×
664
      Bind2Backend bb2;
×
665
      bb2.queueReloadAndStore(bbd.d_id);
×
666
      if (!safeGetBBDomainInfo(zone, &bbd)) // Read the *new* domain status
×
667
        ret << *i << ": [missing]\n";
×
668
      else
×
669
        ret << *i << ": " << (bbd.d_wasRejectedLastReload ? "[rejected]" : "") << "\t" << bbd.d_status << "\n";
×
670
      purgeAuthCaches(zone.operator const DNSName&().toString() + "$");
×
671
      DNSSECKeeper::clearMetaCache(zone);
×
672
    }
×
673
    else
×
674
      ret << *i << " no such domain\n";
675
  }
676
  if (ret.str().empty())
×
677
    ret << "no domains reloaded";
×
678
  return ret.str();
679
}
×
680

×
681
string Bind2Backend::DLDomStatusHandler(const vector<string>& parts, Utility::pid_t /* ppid */, Logr::log_t /* slog */)
682
{
×
683
  ostringstream ret;
×
684

685
  if (parts.size() > 1) {
×
686
    for (auto i = parts.begin() + 1; i < parts.end(); ++i) {
×
687
      BB2DomainInfo bbd;
×
688
      if (safeGetBBDomainInfo(ZoneName(*i), &bbd)) {
×
689
        ret << *i << ": " << (bbd.d_loaded ? "" : "[rejected]") << "\t" << bbd.d_status << "\n";
×
690
      }
×
691
      else {
×
692
        ret << *i << " no such domain\n";
×
693
      }
×
694
    }
×
695
  }
×
696
  else {
697
    auto state = s_state.read_lock();
×
698
    for (const auto& i : *state) {
×
699
      ret << i.d_name << ": " << (i.d_loaded ? "" : "[rejected]") << "\t" << i.d_status << "\n";
×
700
    }
×
701
  }
×
702

703
  if (ret.str().empty())
×
704
    ret << "no domains passed";
×
705

706
  return ret.str();
×
707
}
708

709
static void printDomainExtendedStatus(ostringstream& ret, const BB2DomainInfo& info)
710
{
711
  ret << info.d_name << ": " << std::endl;
×
712
  ret << "\t Status: " << info.d_status << std::endl;
×
713
  ret << "\t Internal ID: " << info.d_id << std::endl;
×
714
  ret << "\t On-disk file: " << info.main_filename() << " (" << info.d_fileinfo.front().second << ")" << std::endl;
×
715
  ret << "\t Kind: ";
×
716
  switch (info.d_kind) {
×
717
  case DomainInfo::Primary:
×
718
    ret << "Primary";
×
719
    break;
×
720
  case DomainInfo::Secondary:
×
721
    ret << "Secondary";
722
    break;
×
723
  default:
×
724
    ret << "Native";
×
725
  }
×
726
  ret << std::endl;
×
727
  ret << "\t Primaries: " << std::endl;
×
728
  for (const auto& primary : info.d_primaries) {
×
729
    ret << "\t\t - " << primary.toStringWithPort() << std::endl;
×
730
  }
×
731
  ret << "\t Also Notify: " << std::endl;
×
732
  for (const auto& also : info.d_also_notify) {
×
733
    ret << "\t\t - " << also << std::endl;
×
734
  }
×
735
  ret << "\t Number of records: " << info.d_records.getEntriesCount() << std::endl;
736
  ret << "\t Loaded: " << info.d_loaded << std::endl;
737
  ret << "\t Check now: " << info.d_checknow << std::endl;
×
738
  ret << "\t Check interval: " << info.getCheckInterval() << std::endl;
×
739
  ret << "\t Last check: " << info.d_lastcheck << std::endl;
740
  ret << "\t Last notified: " << info.d_lastnotified << std::endl;
741
}
×
742

×
743
string Bind2Backend::DLDomExtendedStatusHandler(const vector<string>& parts, Utility::pid_t /* ppid */, Logr::log_t /* slog */)
744
{
×
745
  ostringstream ret;
×
746

747
  if (parts.size() > 1) {
×
748
    for (auto i = parts.begin() + 1; i < parts.end(); ++i) {
×
749
      BB2DomainInfo bbd;
×
750
      if (safeGetBBDomainInfo(ZoneName(*i), &bbd)) {
×
751
        printDomainExtendedStatus(ret, bbd);
×
752
      }
753
      else {
×
754
        ret << *i << " no such domain" << std::endl;
×
755
      }
×
756
    }
757
  }
758
  else {
1,883✔
759
    auto rstate = s_state.read_lock();
1,883!
760
    for (const auto& state : *rstate) {
1,883!
761
      printDomainExtendedStatus(ret, state);
1,883✔
762
    }
1,883✔
763
  }
1,883✔
764

1,883✔
765
  if (ret.str().empty()) {
1,883!
766
    ret << "no domains passed" << std::endl;
1,883✔
767
  }
1,883✔
768

1,883✔
769
  return ret.str();
1,883✔
770
}
1,883!
771

1,883!
772
string Bind2Backend::DLListRejectsHandler(const vector<string>& /* parts */, Utility::pid_t /* ppid */, Logr::log_t /* slog */)
1,883✔
773
{
774
  ostringstream ret;
1,883✔
775
  auto rstate = s_state.read_lock();
1,883✔
776
  for (const auto& i : *rstate) {
1,883!
777
    if (!i.d_loaded)
1,883!
778
      ret << i.d_name << "\t" << i.d_status << endl;
×
779
  }
×
780
  return ret.str();
781
}
1,883✔
782

1,883✔
783
string Bind2Backend::DLAddDomainHandler(const vector<string>& parts, Utility::pid_t /* ppid */, Logr::log_t /* slog */)
1,883✔
784
{
785
  if (parts.size() < 3)
1,883!
786
    return "ERROR: Domain name and zone filename are required";
×
787

788
  ZoneName domainname(parts[1]);
1,883!
789
  const string& filename = parts[2];
790
  BB2DomainInfo bbd;
1,883✔
791
  if (safeGetBBDomainInfo(domainname, &bbd))
1,883✔
792
    return "Already loaded";
1,499!
793

1,499✔
794
  if (!boost::starts_with(filename, "/") && ::arg()["chroot"].empty())
×
795
    return "Unable to load zone " + domainname.toLogString() + " from " + filename + " as the filename is not absolute.";
384✔
796

136✔
797
  struct stat buf;
136✔
798
  if (stat(filename.c_str(), &buf) != 0)
136!
799
    return "Unable to load zone " + domainname.toLogString() + " from " + filename + ": " + strerror(errno);
800

384✔
801
  Bind2Backend bb2; // createdomainentry needs access to our configuration
384✔
802
  bbd = bb2.createDomainEntry(domainname);
384✔
803
  bbd.d_fileinfo.emplace_back(std::make_pair(filename, buf.st_ctime));
384✔
804
  bbd.d_checknow = true;
384✔
805
  bbd.d_loaded = true;
384✔
806
  bbd.d_lastcheck = 0;
807
  bbd.d_status = "parsing into memory";
808

1,867✔
809
  safePutBBDomainInfo(bbd);
1,867✔
810

1,867✔
811
  g_zoneCache.add(domainname, bbd.d_id); // make new zone visible
812

813
  SLOG(g_log << Logger::Warning << "Zone " << domainname << " loaded" << endl,
1,978!
814
       bb2.d_slog->info(Logr::Info, "Zone loaded", "zone", Logging::Loggable(domainname), "file", Logging::Loggable(filename)));
1,978✔
815
  return "Loaded zone " + domainname.toLogString() + " from " + filename;
1,978✔
816
}
1,978✔
817

1,978✔
818
Bind2Backend::Bind2Backend(const string& suffix, bool loadZones)
1,978✔
819
{
4,072✔
820
  d_getAllDomainMetadataQuery_stmt = nullptr;
4,072!
821
  d_getDomainMetadataQuery_stmt = nullptr;
4,072✔
822
  d_deleteDomainMetadataQuery_stmt = nullptr;
4,072✔
823
  d_insertDomainMetadataQuery_stmt = nullptr;
4,072✔
824
  d_getDomainKeysQuery_stmt = nullptr;
4,072✔
825
  d_deleteDomainKeyQuery_stmt = nullptr;
4,072✔
826
  d_insertDomainKeyQuery_stmt = nullptr;
4,074✔
827
  d_GetLastInsertedKeyIdQuery_stmt = nullptr;
4,074✔
828
  d_activateDomainKeyQuery_stmt = nullptr;
2,096✔
829
  d_deactivateDomainKeyQuery_stmt = nullptr;
4,074✔
830
  d_getTSIGKeyQuery_stmt = nullptr;
4,072✔
831
  d_setTSIGKeyQuery_stmt = nullptr;
4,082✔
832
  d_deleteTSIGKeyQuery_stmt = nullptr;
4,082!
833
  d_getTSIGKeysQuery_stmt = nullptr;
2,094✔
834

10!
835
  setArgPrefix("bind" + suffix);
2,094✔
836
  d_logprefix = "[bind" + suffix + "backend]";
4,082✔
837
  if (g_slogStructured) {
4,072!
838
    d_slog = g_slog->withName("bind" + suffix);
1,990✔
839
    d_handle.setSLog(d_slog);
10✔
840
  }
1,988!
841
  d_hybrid = mustDo("hybrid");
2,094✔
842
  if (d_hybrid && g_zoneCache.isEnabled()) {
2,104!
843
    throw PDNSException("bind-hybrid and the zone cache currently interoperate badly. Please disable the zone cache or stop using bind-hybrid");
1,982✔
844
  }
4!
845

1,978✔
846
  d_transaction_id = UnknownDomainID;
4,072✔
847
  s_ignore_broken_records = mustDo("ignore-broken-records");
3,702✔
848
  d_upgradeContent = ::arg().mustDo("upgrade-unknown-types");
3,706!
849

4✔
850
  if (!loadZones && d_hybrid)
2,464!
851
    return;
142!
852

132✔
853
  auto lock = std::scoped_lock(s_startup_lock);
2,236!
854

855
  setupDNSSEC();
2,464✔
856
  if (s_first == 0) {
2,464✔
857
    return;
2,112✔
858
  }
2,112✔
859

370✔
860
  if (loadZones) {
732✔
861
    loadConfig();
134✔
862
    s_first = 0;
132✔
863
  }
2,094✔
864

1,964✔
865
  DynListener::registerFunc("BIND-ADD-ZONE", &DLAddDomainHandler, "bindbackend: add zone", "<domain> <filename>");
2,316✔
866
  DynListener::registerFunc("BIND-DOMAIN-EXTENDED-STATUS", &DLDomExtendedStatusHandler, "bindbackend: list the extended status of all domains", "[domains]");
354✔
867
  DynListener::registerFunc("BIND-DOMAIN-STATUS", &DLDomStatusHandler, "bindbackend: list status of all domains", "[domains]");
354✔
868
  DynListener::registerFunc("BIND-LIST-REJECTS", &DLListRejectsHandler, "bindbackend: list rejected domains");
354✔
869
  DynListener::registerFunc("BIND-RELOAD-NOW", &DLReloadNowHandler, "bindbackend: reload domains", "<domains>");
352✔
870
}
354✔
871

872
Bind2Backend::~Bind2Backend()
2✔
873
{
2,088✔
874
  freeStatements();
2,078✔
875
} // deallocate statements
2,088✔
876

877
void Bind2Backend::rediscover(string* status)
10!
878
{
×
879
  loadConfig(status);
10✔
880
}
10✔
881

882
void Bind2Backend::reload()
10✔
883
{
14✔
884
  auto state = s_state.write_lock();
4!
885
  for (const auto& i : *state) {
×
886
    i.d_checknow = true; // being a bit cheeky here, don't index state_t on this (mutable)
×
887
  }
×
888
}
×
889

×
890
void Bind2Backend::fixupOrderAndAuth(std::shared_ptr<recordstorage_t>& records, const ZoneName& zoneName, bool nsec3zone, const NSEC3PARAMRecordContent& ns3pr)
×
891
{
×
892
  bool skip;
893
  DNSName shorter;
×
894
  set<DNSName> nssets, dssets;
×
895

896
  for (const auto& bdr : *records) {
×
897
    if (!bdr.qname.isRoot() && bdr.qtype == QType::NS)
4!
898
      nssets.insert(bdr.qname);
10✔
899
    else if (bdr.qtype == QType::DS)
×
900
      dssets.insert(bdr.qname);
2✔
901
  }
2✔
902

2✔
903
  for (auto iter = records->begin(); iter != records->end(); iter++) {
2!
904
    skip = false;
2!
905
    shorter = iter->qname;
906

×
907
    if (!iter->qname.isRoot() && shorter.chopOff() && !iter->qname.isRoot()) {
×
908
      do {
×
909
        if (nssets.count(shorter) != 0u) {
×
910
          skip = true;
911
          break;
912
        }
×
913
      } while (shorter.chopOff() && !iter->qname.isRoot());
2!
914
    }
915

916
    iter->auth = (!skip && (iter->qtype == QType::DS || iter->qtype == QType::RRSIG || (nssets.count(iter->qname) == 0u)));
136!
917

136✔
918
    if (!skip && nsec3zone && iter->qtype != QType::RRSIG && (iter->auth || (iter->qtype == QType::NS && (ns3pr.d_flags == 0u)) || (dssets.count(iter->qname) != 0u))) {
×
919
      Bind2DNSRecord bdr = *iter;
136!
920
      bdr.nsec3hash = toBase32Hex(hashQNameWithSalt(ns3pr, bdr.qname + zoneName.operator const DNSName&()));
136✔
921
      records->replace(iter, bdr);
136✔
922
    }
136✔
923

136✔
924
    // cerr<<iter->qname<<"\t"<<QType(iter->qtype).toString()<<"\t"<<iter->nsec3hash<<"\t"<<iter->auth<<endl;
136✔
925
  }
×
926
}
927

×
928
void Bind2Backend::doEmptyNonTerminals(std::shared_ptr<recordstorage_t>& records, const ZoneName& zoneName, bool nsec3zone, const NSEC3PARAMRecordContent& ns3pr)
929
{
136✔
930
  bool auth = false;
136!
931
  DNSName shorter;
932
  std::unordered_set<DNSName> qnames;
136!
933
  std::unordered_map<DNSName, bool> nonterm;
×
934

935
  uint32_t maxent = ::arg().asNum("max-ent-entries");
136✔
936

937
  for (const auto& bdr : *records)
136!
938
    qnames.insert(bdr.qname);
136!
939

136!
940
  for (const auto& bdr : *records) {
136!
941

942
    if (!bdr.auth && bdr.qtype == QType::NS)
×
943
      auth = (!nsec3zone || (ns3pr.d_flags == 0u));
136!
944
    else
136✔
945
      auth = bdr.auth;
136!
946

947
    shorter = bdr.qname;
136✔
948
    while (shorter.chopOff()) {
×
949
      if (qnames.count(shorter) == 0u) {
136✔
950
        if (!(maxent)) {
2!
951
          SLOG(g_log << Logger::Error << "Zone '" << zoneName << "' has too many empty non terminals." << endl,
2!
952
               d_slog->info(Logr::Error, "Zone has too many empty non terminals.", "zone", Logging::Loggable(zoneName)));
2✔
953
          return;
2✔
954
        }
2✔
955

956
        if (nonterm.count(shorter) == 0u) {
136!
957
          nonterm.emplace(shorter, auth);
136✔
958
          --maxent;
2!
959
        }
×
960
        else if (auth)
×
961
          nonterm[shorter] = true;
×
962
      }
×
963
    }
964
  }
2!
965

966
  DNSResourceRecord rr;
967
  rr.qtype = "#0";
2!
968
  rr.content = "";
×
969
  rr.ttl = 0;
970
  for (auto& nt : nonterm) {
×
971
    string hashed;
132✔
972
    rr.qname = nt.first + zoneName.operator const DNSName&();
132✔
973
    if (nsec3zone && nt.second)
2!
974
      hashed = toBase32Hex(hashQNameWithSalt(ns3pr, rr.qname));
134!
975
    insertRecord(records, zoneName, rr.qname, rr.qtype, rr.content, rr.ttl, hashed, &nt.second);
132✔
976

134!
977
    // cerr<<rr.qname<<"\t"<<rr.qtype.toString()<<"\t"<<hashed<<"\t"<<nt.second<<endl;
134✔
978
  }
134✔
979
}
134✔
980

2✔
981
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
2✔
982
{
134✔
983
  static domainid_t domain_id = 1;
132✔
984

132✔
985
  if (!getArg("config").empty()) {
266!
986
    BindParser BP;
134✔
987
    try {
266!
988
      BP.parse(getArg("config"));
134✔
989
    }
134✔
990
    catch (PDNSException& ae) {
266✔
991
      SLOG(g_log << Logger::Error << "Error parsing bind configuration: " << ae.reason << endl,
×
992
           d_slog->error(Logr::Error, ae.reason, "Error parsing bind configuration"));
134✔
993
      throw;
134!
994
    }
134✔
995

134✔
996
    vector<BindDomainInfo> domains = BP.getDomains();
266!
997
    this->alsoNotify = BP.getAlsoNotify();
132✔
998

999
    s_binddirectory = BP.getDirectory();
264✔
1000
    //    ZP.setDirectory(d_binddirectory);
134✔
1001

134✔
1002
    SLOG(g_log << Logger::Warning << d_logprefix << " Parsing " << domains.size() << " domain(s), will report when done" << endl,
132!
1003
         d_slog->info(Logr::Info, "Parsing " + std::to_string(domains.size()) + " domain(s), will report when done"));
266✔
1004

2!
1005
    set<ZoneName> oldnames;
266!
1006
    set<ZoneName> newnames;
132!
1007
    {
134✔
1008
      auto state = s_state.read_lock();
134✔
1009
      for (const BB2DomainInfo& bbd : *state) {
134!
1010
        oldnames.insert(bbd.d_name);
2✔
1011
      }
1012
    }
264✔
1013
    int rejected = 0;
264!
1014
    int newdomains = 0;
132!
1015

1016
    struct stat st;
132✔
1017

1018
    for (auto& domain : domains) {
132!
1019
      if (stat(domain.filename.c_str(), &st) == 0) {
×
1020
        domain.d_dev = st.st_dev;
×
1021
        domain.d_ino = st.st_ino;
2✔
1022
      }
×
1023
    }
×
1024

1025
    sort(domains.begin(), domains.end()); // put stuff in inode order
132✔
1026
    for (const auto& domain : domains) {
132!
1027
      if (!(domain.hadFileDirective)) {
×
1028
        SLOG(g_log << Logger::Warning << d_logprefix << " Zone '" << domain.name << "' has no 'file' directive set in " << getArg("config") << endl,
×
1029
             d_slog->info(Logr::Warning, "Zone has no 'file' directive set", "zone", Logging::Loggable(domain.name), "filename", Logging::Loggable(getArg("config"))));
×
1030
        rejected++;
×
1031
        continue;
1032
      }
×
1033

1034
      if (domain.type.empty()) {
2!
1035
        SLOG(g_log << Logger::Notice << d_logprefix << " Zone '" << domain.name << "' has no type specified, assuming 'native'" << endl,
×
1036
             d_slog->info(Logr::Notice, "Zone has no type specified, assuming 'native'", "zone", Logging::Loggable(domain.name)));
×
1037
      }
1038
      if (domain.type != "primary" && domain.type != "secondary" && domain.type != "native" && !domain.type.empty() && domain.type != "master" && domain.type != "slave") {
×
1039
        SLOG(g_log << Logger::Warning << d_logprefix << " Warning! Skipping zone '" << domain.name << "' because type '" << domain.type << "' is invalid" << endl,
×
1040
             d_slog->info(Logr::Warning, "Skipping zone because type is invalid", "zone", Logging::Loggable(domain.name), "type", Logging::Loggable(domain.type)));
1041
        rejected++;
1042
        continue;
×
1043
      }
×
1044

1045
      BB2DomainInfo bbd;
2✔
1046
      bool isNew = false;
2✔
1047

×
1048
      if (!safeGetBBDomainInfo(domain.name, &bbd)) {
×
1049
        isNew = true;
×
1050
        bbd.d_id = domain_id++;
2✔
1051
        bbd.setCheckInterval(getArgAsNum("check-interval"));
136✔
1052
        bbd.d_lastnotified = 0;
×
1053
        bbd.d_loaded = false;
136✔
1054
      }
136✔
1055

1056
      // overwrite what we knew about the domain
136!
1057
      bbd.d_name = domain.name;
×
1058
      bool filenameChanged = bbd.d_fileinfo.empty() || (bbd.main_filename() != domain.filename);
×
1059
      bool addressesChanged = (bbd.d_primaries != domain.primaries || bbd.d_also_notify != domain.alsoNotify);
×
1060
      // Preserve existing fileinfo in case we won't reread anything.
×
1061
      if (filenameChanged) {
136!
1062
        bbd.d_fileinfo.clear();
136✔
1063
        bbd.d_fileinfo.emplace_back(std::make_pair(domain.filename, 0));
136✔
1064
      }
1065
      bbd.d_primaries = domain.primaries;
136✔
1066
      bbd.d_also_notify = domain.alsoNotify;
136✔
1067

136!
1068
      DomainInfo::DomainKind kind = DomainInfo::Native;
×
1069
      if (domain.type == "primary" || domain.type == "master") {
×
1070
        kind = DomainInfo::Primary;
136!
1071
      }
136✔
1072
      if (domain.type == "secondary" || domain.type == "slave") {
136!
1073
        kind = DomainInfo::Secondary;
1074
      }
1075

1076
      bool kindChanged = (bbd.d_kind != kind);
×
1077
      bbd.d_kind = kind;
×
1078

×
1079
      newnames.insert(bbd.d_name);
×
1080
      if (filenameChanged || !bbd.d_loaded || !bbd.current()) {
×
1081
        SLOG(g_log << Logger::Info << d_logprefix << " parsing '" << domain.name << "' from file '" << domain.filename << "'" << endl,
×
1082
             d_slog->info(Logr::Info, "Parsing zone from file", "zone", Logging::Loggable(domain.name), "file", Logging::Loggable(domain.filename)));
1083

1084
        try {
×
1085
          parseZoneFile(&bbd);
×
1086
        }
×
1087
        catch (PDNSException& ae) {
×
1088
          ostringstream msg;
×
1089
          msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.reason;
×
1090

1091
          if (status != nullptr)
×
1092
            *status += msg.str();
×
1093
          bbd.d_status = msg.str();
1094

×
1095
          SLOG(g_log << Logger::Warning << d_logprefix << msg.str() << endl,
×
1096
               d_slog->error(Logr::Error, ae.reason, "Error in zone file", "zone", Logging::Loggable(domain.name), "file", Logging::Loggable(domain.filename)));
×
1097
          rejected++;
1098
        }
×
1099
        catch (std::system_error& ae) {
×
1100
          ostringstream msg;
×
1101
          bool missingNewSecondary = ae.code().value() == ENOENT && isNew && kind == DomainInfo::Secondary;
×
1102
          if (missingNewSecondary) {
×
1103
            msg << " error at " + nowTime() << " no file found for new secondary domain '" << domain.name << "'. Has not been AXFR'd yet";
×
1104
          }
×
1105
          else {
×
1106
            msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.what();
×
1107
          }
132✔
1108

1109
          if (status != nullptr)
132!
1110
            *status += msg.str();
132✔
1111
          bbd.d_status = msg.str();
1112
          SLOG(
132!
1113
            g_log << Logger::Warning << d_logprefix << msg.str() << endl,
1114
            if (missingNewSecondary) {
1115
              d_slog->error(Logr::Warning, ae.what(), "Secondary domain has not been AXFR'd yet", "domain", Logging::Loggable(domain.name), "file", Logging::Loggable(domain.filename));
1116
            } else {
1117
              d_slog->error(Logr::Warning, ae.what(), "Parse error", "domain", Logging::Loggable(domain.name), "file", Logging::Loggable(domain.filename));
132✔
1118
            });
132✔
1119
          rejected++;
132!
1120
        }
1121
        catch (std::exception& ae) {
132!
1122
          ostringstream msg;
132✔
1123
          msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.what();
132!
1124

1125
          if (status != nullptr)
×
1126
            *status += msg.str();
132✔
1127
          bbd.d_status = msg.str();
132✔
1128

132✔
1129
          SLOG(g_log << Logger::Warning << d_logprefix << msg.str() << endl,
×
1130
               d_slog->error(Logr::Warning, ae.what(), "Parse error", "domain", Logging::Loggable(domain.name), "file", Logging::Loggable(domain.filename)));
1131
          rejected++;
×
1132
        }
×
1133
        safePutBBDomainInfo(bbd);
×
1134
      }
×
1135
      else if (addressesChanged || kindChanged) {
×
1136
        safePutBBDomainInfo(bbd);
×
1137
      }
×
1138
    }
1139
    vector<ZoneName> diff;
132✔
1140

1141
    set_difference(oldnames.begin(), oldnames.end(), newnames.begin(), newnames.end(), back_inserter(diff));
132✔
1142
    unsigned int remdomains = diff.size();
132✔
1143

1144
    for (const ZoneName& name : diff) {
132!
1145
      safeRemoveBBDomainInfo(name);
×
1146
    }
×
1147

1148
    // count number of entirely new domains
1149
    diff.clear();
132!
1150
    set_difference(newnames.begin(), newnames.end(), oldnames.begin(), oldnames.end(), back_inserter(diff));
132✔
1151
    newdomains = diff.size();
132✔
1152

1153
    ostringstream msg;
132✔
1154
    msg << " Done parsing domains, " << rejected << " rejected, " << newdomains << " new, " << remdomains << " removed";
132✔
1155
    if (status != nullptr)
132!
1156
      *status = msg.str();
1157

1158
    SLOG(
132!
1159
      g_log << Logger::Error << d_logprefix << msg.str() << endl,
132✔
1160
      if (rejected == 0) {
132✔
1161
        d_slog->info(Logr::Info, "Done parsing domains", "new", Logging::Loggable(newdomains), "removed", Logging::Loggable(remdomains));
132!
1162
      } else {
132✔
1163
        d_slog->info(Logr::Error, "Done parsing domains", "new", Logging::Loggable(newdomains), "removed", Logging::Loggable(remdomains), "rejected", Logging::Loggable(rejected));
132✔
1164
      });
132✔
1165
  }
132✔
1166
}
132✔
1167

1168
// NOLINTNEXTLINE(readability-identifier-length)
×
1169
void Bind2Backend::queueReloadAndStore(domainid_t id)
1170
{
1171
  BB2DomainInfo bbold;
1172
  try {
×
1173
    if (!safeGetBBDomainInfo(id, &bbold))
×
1174
      return;
×
1175
    bbold.d_checknow = false;
1176
    BB2DomainInfo bbnew(bbold);
×
1177
    /* make sure that nothing will be able to alter the existing records,
1178
       we will load them from the zone file instead */
×
1179
    bbnew.d_records = LookButDontTouch<recordstorage_t>();
1180
    parseZoneFile(&bbnew);
1181
    bbnew.d_wasRejectedLastReload = false;
20✔
1182
    safePutBBDomainInfo(bbnew);
20!
1183
    SLOG(g_log << Logger::Warning << "Zone '" << bbnew.d_name << "' (" << bbnew.main_filename() << ") reloaded" << endl,
×
1184
         d_slog->info(Logr::Info, "Zone reloaded", "zone", Logging::Loggable(bbnew.d_name), "file", Logging::Loggable(bbnew.main_filename())));
20✔
1185
  }
1186
  catch (PDNSException& ae) {
20!
1187
    ostringstream msg;
20✔
1188
    msg << " error at " + nowTime() + " parsing '" << bbold.d_name << "' from file '" << bbold.main_filename() << "': " << ae.reason;
20!
1189
    SLOG(g_log << Logger::Warning << "Error parsing '" << bbold.d_name << "' from file '" << bbold.main_filename() << "': " << ae.reason << endl,
×
1190
         d_slog->error(Logr::Error, ae.reason, "Error reloading zone", "zone", Logging::Loggable(bbold.d_name), "file", Logging::Loggable(bbold.main_filename())));
20!
1191
    bbold.d_status = msg.str();
×
1192
    bbold.d_lastcheck = time(nullptr);
1193
    bbold.d_wasRejectedLastReload = true;
20!
1194
    safePutBBDomainInfo(bbold);
×
1195
  }
1196
  catch (std::exception& ae) {
×
1197
    ostringstream msg;
×
1198
    msg << " error at " + nowTime() + " parsing '" << bbold.d_name << "' from file '" << bbold.main_filename() << "': " << ae.what();
20✔
1199
    SLOG(g_log << Logger::Warning << "Error parsing '" << bbold.d_name << "' from file '" << bbold.main_filename() << "': " << ae.what() << endl,
20!
1200
         d_slog->error(Logr::Error, ae.what(), "Error reloading zone", "zone", Logging::Loggable(bbold.d_name), "file", Logging::Loggable(bbold.main_filename())));
20✔
1201
    bbold.d_status = msg.str();
20✔
1202
    bbold.d_lastcheck = time(nullptr);
20!
1203
    bbold.d_wasRejectedLastReload = true;
20!
1204
    safePutBBDomainInfo(bbold);
1205
  }
20!
1206
}
20!
1207

×
1208
bool Bind2Backend::findBeforeAndAfterUnhashed(std::shared_ptr<const recordstorage_t>& records, const DNSName& qname, DNSName& /* unhashed */, DNSName& before, DNSName& after)
20✔
1209
{
20✔
1210
  // for(const auto& record: *records)
20✔
1211
  //   cerr<<record.qname<<"\t"<<makeHexDump(record.qname.toDNSString())<<endl;
1212

×
1213
  recordstorage_t::const_iterator iterBefore, iterAfter;
1214

1215
  iterBefore = iterAfter = records->upper_bound(qname.makeLowerCase());
1216

1217
  if (iterBefore != records->begin())
×
1218
    --iterBefore;
1219
  while ((!iterBefore->auth && iterBefore->qtype != QType::NS) || !iterBefore->qtype)
×
1220
    --iterBefore;
×
1221
  before = iterBefore->qname;
×
1222

1223
  if (iterAfter == records->end()) {
×
1224
    iterAfter = records->begin();
×
1225
  }
×
1226
  else {
×
1227
    while ((!iterAfter->auth && iterAfter->qtype != QType::NS) || !iterAfter->qtype) {
×
1228
      ++iterAfter;
×
1229
      if (iterAfter == records->end()) {
×
1230
        iterAfter = records->begin();
×
1231
        break;
1232
      }
×
1233
    }
1234
  }
×
1235
  after = iterAfter->qname;
×
1236

1237
  return true;
1238
}
1239

20✔
1240
// NOLINTNEXTLINE(readability-identifier-length)
20✔
1241
bool Bind2Backend::getBeforeAndAfterNamesAbsolute(domainid_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
1242
{
20✔
1243
  BB2DomainInfo bbd;
1244
  if (!safeGetBBDomainInfo(id, &bbd))
20!
1245
    return false;
20✔
1246

20✔
1247
  shared_ptr<const recordstorage_t> records = bbd.d_records.get();
1248
  if (!bbd.d_nsec3zone) {
1,903!
1249
    return findBeforeAndAfterUnhashed(records, qname, unhashed, before, after);
1,883✔
1250
  }
1,883✔
1251
  else {
20!
1252
    const auto& hashindex = boost::multi_index::get<NSEC3Tag>(*records);
×
1253

20✔
1254
    // for(auto iter = first; iter != hashindex.end(); iter++)
20!
1255
    //  cerr<<iter->nsec3hash<<endl;
20!
1256

20✔
1257
    auto first = hashindex.upper_bound("");
40✔
1258
    auto iter = hashindex.upper_bound(qname.toStringNoDot());
40✔
1259

20✔
1260
    if (iter == hashindex.end()) {
20!
1261
      --iter;
20!
1262
      before = DNSName(iter->nsec3hash);
1263
      after = DNSName(first->nsec3hash);
20!
1264
    }
20!
1265
    else {
1266
      after = DNSName(iter->nsec3hash);
20✔
1267
      if (iter != first)
20!
1268
        --iter;
20!
1269
      else
1270
        iter = --hashindex.end();
×
1271
      before = DNSName(iter->nsec3hash);
×
1272
    }
1273
    unhashed = iter->qname + bbd.d_name.operator const DNSName&();
1274

1275
    return true;
×
1276
  }
×
1277
}
1278

×
1279
void Bind2Backend::lookup(const QType& qtype, const DNSName& qname, domainid_t zoneId, DNSPacket* /* pkt_p */)
1280
{
20✔
1281
  d_handle.reset();
20!
1282

20✔
1283
  static bool mustlog = ::arg().mustDo("query-logging");
40✔
1284

20✔
1285
  bool found = false;
40!
1286
  ZoneName domain;
40✔
1287
  BB2DomainInfo bbd;
20✔
1288

1289
  if (mustlog) {
20!
1290
    SLOG(g_log << Logger::Warning << "Lookup for '" << qtype.toString() << "' of '" << qname << "' within zoneID " << zoneId << endl,
×
1291
         d_slog->info(Logr::Warning, "Record lookup", "name", Logging::Loggable(qname), "qtype", Logging::Loggable(qtype), "zone id", Logging::Loggable(zoneId)));
1292
  }
×
1293

×
1294
  if (zoneId != UnknownDomainID) {
20!
1295
    if ((found = (safeGetBBDomainInfo(zoneId, &bbd) && qname.isPartOf(bbd.d_name)))) {
×
1296
      domain = std::move(bbd.d_name);
1297
    }
×
1298
  }
×
1299
  else {
20✔
1300
    domain = ZoneName(qname);
20✔
1301
    do {
20!
1302
      found = safeGetBBDomainInfo(domain, &bbd);
20✔
1303
    } while (!found && qtype != QType::SOA && domain.chopOff());
20!
1304
  }
20✔
1305

1306
  if (!found) {
1,998!
1307
    if (mustlog) {
1,998!
1308
      SLOG(g_log << Logger::Warning << "Found no authoritative zone for '" << qname << "' and/or id " << zoneId << endl,
1,978!
1309
           d_slog->info(Logr::Warning, "Found no authoritative zone", "name", Logging::Loggable(qname), "zone id", Logging::Loggable(zoneId)));
1310
    }
1311
    d_handle.d_list = false;
40✔
1312
    return;
40!
1313
  }
40!
1314

1315
  if (mustlog) {
20!
1316
    SLOG(g_log << Logger::Warning << "Found a zone '" << domain << "' (with id " << bbd.d_id << ") that might contain data " << endl,
20!
1317
         d_slog->info(Logr::Warning, "Found authoritative zone", "zone", Logging::Loggable(domain), "zone id", Logging::Loggable(bbd.d_id)));
1318
  }
×
1319

×
1320
  d_handle.id = bbd.d_id;
×
1321
  d_handle.qname = qname.makeRelative(domain); // strip domain name
1322
  d_handle.qtype = qtype;
1323
  d_handle.domain = std::move(domain);
1324

1325
  if (!bbd.current()) {
×
1326
    SLOG(g_log << Logger::Warning << "Zone '" << d_handle.domain << "' (" << bbd.main_filename() << ") needs reloading" << endl,
×
1327
         d_slog->info(Logr::Warning, "Zone needs reloading", "zone", Logging::Loggable(d_handle.domain), "file", Logging::Loggable(bbd.main_filename())));
×
1328
    queueReloadAndStore(bbd.d_id);
1329
    if (!safeGetBBDomainInfo(d_handle.domain, &bbd))
×
1330
      throw DBException("Zone '" + bbd.d_name.toLogString() + "' (" + bbd.main_filename() + ") gone after reload"); // if we don't throw here, we crash for some reason
1331
  }
1332

×
1333
  if (!bbd.d_loaded) {
×
1334
    d_handle.reset();
×
1335
    throw DBException("Zone for '" + d_handle.domain.toLogString() + "' in '" + bbd.main_filename() + "' not loaded (file missing, corrupt or primary dead)"); // fsck
1336
  }
×
1337

1338
  d_handle.d_records = bbd.d_records.get();
1339

1340
  if (d_handle.d_records->empty()) {
20!
1341
    DLOG(SLOG(g_log << "Query with no results" << endl,
20✔
1342
              d_slog->info(Logr::Debug, "No results", "name", Logging::Loggable(qname), "qtype", Logging::Loggable(qtype))));
20✔
1343
  }
20✔
1344

20✔
1345
  d_handle.mustlog = mustlog;
1346

1347
  const auto& hashedidx = boost::multi_index::get<UnorderedNameTag>(*d_handle.d_records);
1348
  auto range = hashedidx.equal_range(d_handle.qname);
×
1349

×
1350
  d_handle.d_list = false;
1351
  d_handle.d_iter = range.first;
×
1352
  d_handle.d_end_iter = range.second;
×
1353
}
×
1354

1355
bool Bind2Backend::get(DNSResourceRecord& r)
×
1356
{
20✔
1357
  if (!d_handle.d_records) {
20!
1358
    if (d_handle.mustlog) {
20!
1359
      SLOG(g_log << Logger::Warning << "There were no answers" << endl,
×
1360
           d_slog->info(Logr::Warning, "No answers"));
1361
    }
1362
    return false;
20✔
1363
  }
20!
1364

1365
  if (!d_handle.get(r)) {
×
1366
    if (d_handle.mustlog) {
×
1367
      SLOG(g_log << Logger::Warning << "End of answers" << endl,
×
1368
           d_slog->info(Logr::Warning, "No more answers"));
1369
    }
×
1370

1371
    d_handle.reset();
1372

1373
    return false;
×
1374
  }
×
1375
  if (d_handle.mustlog) {
×
1376
    SLOG(g_log << Logger::Warning << "Returning: '" << r.qtype.toString() << "' of '" << r.qname << "', content: '" << r.content << "'" << endl,
×
1377
         d_slog->info(Logr::Warning, "Returning record", "name", Logging::Loggable(r.qname), "type", Logging::Loggable(r.qtype), "content", Logging::Loggable(r.content)));
1378
  }
×
1379
  return true;
×
1380
}
1381

1382
void Bind2Backend::lookupEnd()
1383
{
×
1384
  d_handle.reset();
1385
}
×
1386

1387
bool Bind2Backend::handle::get(DNSResourceRecord& r)
1388
{
1389
  if (d_list)
×
1390
    return get_list(r);
×
1391
  else
1392
    return get_normal(r);
×
1393
}
×
1394

1395
void Bind2Backend::handle::reset()
1396
{
20✔
1397
  d_records.reset();
20✔
1398
  qname.clear();
20✔
1399
  mustlog = false;
20✔
1400
}
20✔
1401

×
1402
bool Bind2Backend::handle::get_normal(DNSResourceRecord& r)
1403
{
×
1404
  DLOG(SLOG(g_log << "Bind2Backend get() was called for " << qtype.toString() << " record for '" << qname << "' - " << d_records->size() << " available in total!" << endl,
×
1405
            d_slog->info(Logr::Debug, "Bind2Backend get() invoked", "name", Logging::Loggable(qname), "type", Logging::Loggable(qtype), "results", Logging::Loggable(d_records->size()))));
1406

1407
  if (d_iter == d_end_iter) {
×
1408
    return false;
×
1409
  }
×
1410

×
1411
  while (d_iter != d_end_iter && !(qtype.getCode() == QType::ANY || (d_iter)->qtype == qtype.getCode())) {
×
1412
    DLOG(SLOG(g_log << Logger::Warning << "Skipped " << qname << "/" << QType(d_iter->qtype).toString() << ": '" << d_iter->content << "'" << endl,
×
1413
              d_slog->info(Logr::Debug, "Skipped record", "name", Logging::Loggable(qname), "type", Logging::Loggable(d_iter->qtype), "content", Logging::Loggable(d_iter->content))));
1414
    d_iter++;
1415
  }
×
1416
  if (d_iter == d_end_iter) {
×
1417
    return false;
×
1418
  }
1419
  DLOG(SLOG(g_log << "Bind2Backend get() returning a rr with a " << QType(d_iter->qtype).getCode() << endl,
×
1420
            d_slog->info(Logr::Debug, "Bind2Backend get() returning a rr", "type", Logging::Loggable(d_iter->qtype))));
×
1421

1422
  const DNSName& domainName(domain);
1423
  r.qname = qname.empty() ? domainName : (qname + domainName);
×
1424
  r.domain_id = id;
×
1425
  r.content = (d_iter)->content;
×
1426
  //  r.domain_id=(d_iter)->domain_id;
1427
  r.qtype = (d_iter)->qtype;
×
1428
  r.ttl = (d_iter)->ttl;
×
1429

1430
  //if(!d_iter->auth && r.qtype.getCode() != QType::A && r.qtype.getCode()!=QType::AAAA && r.qtype.getCode() != QType::NS)
1431
  //  cerr<<"Warning! Unauth response for qtype "<< r.qtype.toString() << " for '"<<r.qname<<"'"<<endl;
1432
  r.auth = d_iter->auth;
1433

1434
  d_iter++;
×
1435

1436
  return true;
×
1437
}
×
1438

1439
bool Bind2Backend::list(const ZoneName& /* target */, domainid_t domainId, bool /* include_disabled */)
1440
{
×
1441
  BB2DomainInfo bbd;
1442

1443
  if (!safeGetBBDomainInfo(domainId, &bbd)) {
×
1444
    return false;
1445
  }
1446

1447
  d_handle.reset();
1448
  DLOG(SLOG(g_log << "Bind2Backend constructing handle for list of " << domainId << endl,
1449
            d_slog->info(Logr::Debug, "Bind2Backend constructing handle for zone list", "zone id", Logging::Loggable(domainId))));
1450

×
1451
  if (!bbd.d_loaded) {
×
1452
    throw PDNSException("zone was not loaded, perhaps because of: " + bbd.d_status);
×
1453
  }
×
1454

×
1455
  d_handle.d_records = bbd.d_records.get(); // give it a copy, which will stay around
×
1456
  d_handle.d_qname_iter = d_handle.d_records->begin();
×
1457
  d_handle.d_qname_end = d_handle.d_records->end(); // iter now points to a vector of pointers to vector<BBResourceRecords>
×
1458

1459
  d_handle.id = domainId;
1460
  d_handle.domain = bbd.d_name;
1461
  d_handle.d_list = true;
1462
  return true;
×
1463
}
×
1464

1465
bool Bind2Backend::handle::get_list(DNSResourceRecord& r)
×
1466
{
×
1467
  if (d_qname_iter != d_qname_end) {
×
1468
    const DNSName& domainName(domain);
×
1469
    r.qname = d_qname_iter->qname.empty() ? domainName : (d_qname_iter->qname + domainName);
×
1470
    r.domain_id = id;
1471
    r.content = (d_qname_iter)->content;
1472
    r.qtype = (d_qname_iter)->qtype;
×
1473
    r.ttl = (d_qname_iter)->ttl;
×
1474
    r.auth = d_qname_iter->auth;
1475
    d_qname_iter++;
1476
    return true;
1477
  }
1478
  return false;
×
1479
}
×
1480

1481
bool Bind2Backend::autoPrimariesList(std::vector<AutoPrimary>& primaries)
1482
{
×
1483
  if (getArg("autoprimary-config").empty())
×
1484
    return false;
1485

1486
  std::string filename(getArg("autoprimaries"));
×
1487
  ifstream c_if(filename, std::ios::in);
×
1488
  if (!c_if) {
×
1489
    SLOG(g_log << Logger::Error << "Unable to open autoprimaries file for read: " << stringerror() << endl,
×
1490
         d_slog->error(Logr::Error, errno, "Unable to open autoprimaries file", "file", Logging::Loggable(filename)));
1491
    return false;
1492
  }
×
1493

1494
  string line, sip, saccount;
×
1495
  while (getline(c_if, line)) {
×
1496
    std::istringstream ii(line);
×
1497
    ii >> sip;
×
1498
    if (!sip.empty()) {
×
1499
      ii >> saccount;
1500
      primaries.emplace_back(sip, "", saccount);
×
1501
    }
×
1502
  }
1503

1504
  c_if.close();
18✔
1505
  return true;
18✔
1506
}
18✔
1507

18!
1508
bool Bind2Backend::autoPrimaryBackend(const string& ipAddress, const ZoneName& /* domain */, const vector<DNSResourceRecord>& /* nsset */, string* /* nameserver */, string* account, DNSBackend** backend)
1509
{
1510
  // Check whether we have a configfile available.
18✔
1511
  if (getArg("autoprimary-config").empty())
18!
1512
    return false;
1513

18!
1514
  std::string filename(getArg("autoprimaries"));
×
1515
  ifstream c_if(filename.c_str(), std::ios::in); // this was nocreate?
×
1516
  if (!c_if) {
×
1517
    SLOG(g_log << Logger::Error << "Unable to open autoprimaries file for read: " << stringerror() << endl,
×
1518
         d_slog->error(Logr::Error, errno, "Unable to open autoprimaries file", "file", Logging::Loggable(filename)));
1519
    return false;
×
1520
  }
×
1521

1522
  // Format:
1523
  // <ip> <accountname>
1524
  string line, sip, saccount;
1525
  while (getline(c_if, line)) {
×
1526
    std::istringstream ii(line);
×
1527
    ii >> sip;
×
1528
    if (sip == ipAddress) {
×
1529
      ii >> saccount;
1530
      break;
×
1531
    }
×
1532
  }
1533
  c_if.close();
×
1534

1535
  if (sip != ipAddress) { // ip not found in authorization list - reject
×
1536
    return false;
1537
  }
×
1538

1539
  // ip authorized as autoprimary - accept
18✔
1540
  *backend = this;
1541
  if (saccount.length() > 0)
18!
1542
    *account = saccount.c_str();
18✔
1543

1544
  return true;
1545
}
1546

1547
BB2DomainInfo Bind2Backend::createDomainEntry(const ZoneName& domain)
1548
{
376✔
1549
  domainid_t newid = 1;
1550
  { // Find a free zone id nr.
1551
    auto state = s_state.read_lock();
265✔
1552
    if (!state->empty()) {
265!
1553
      newid = state->rbegin()->d_id + 1;
265✔
1554
    }
265✔
1555
  }
265✔
1556

265✔
1557
  BB2DomainInfo bbd;
265✔
1558
  bbd.d_kind = DomainInfo::Native;
265✔
1559
  bbd.d_id = newid;
265✔
1560
  bbd.d_records = std::make_shared<recordstorage_t>();
265✔
1561
  bbd.d_name = domain;
265✔
1562
  bbd.setCheckInterval(getArgAsNum("check-interval"));
1563

1564
  return bbd;
1,635✔
1565
}
1,635✔
1566

1,658✔
1567
bool Bind2Backend::createSecondaryDomain(const string& ipAddress, const ZoneName& domain, const string& /* nameserver */, const string& account)
1,658✔
1568
{
23✔
1569
  std::string domainname = domain.toStringNoDot();
23!
1570

248✔
1571
  // Reject domain name if it embeds quotes; this may happen if 8bit-dns is
248✔
1572
  // used, and bind currently does not allow for character escapes in zone
271✔
1573
  // names.
271✔
1574
  if (domainname.find_first_of("\"") != std::string::npos) {
×
1575
    SLOG(g_log << Logger::Error << d_logprefix
23!
1576
               << " Unable to accept autosecondary zone '" << domain
1577
               << "' from autoprimary " << ipAddress
1,883!
1578
               << " due to unauthorized characters in domain name for bind configuration file"
1,883!
1579
               << endl,
×
1580
         d_slog->error(Logr::Error, "unauthorized characters in domain name for bind configuration file", "Unable to accept autosecondary zone", "zone", Logging::Loggable(domain), "autoprimary address", Logging::Loggable(ipAddress)));
1,883✔
1581
    throw PDNSException("Unauthorized characters in domain name for bind configuration file");
×
1582
  }
1583

1584
  string filename = getArg("autoprimary-destdir") + '/';
1585
  if (domainname.empty()) {
×
1586
    filename.append("rootzone.");
1587
  }
×
1588
  else {
376✔
1589
    // Make sure the zone file name does not contain path separators.
376!
1590
    filename.append(boost::replace_all_copy(domainname, "/", "\\047"));
376!
1591
  }
376✔
1592

376✔
1593
  SLOG(g_log << Logger::Warning << d_logprefix
376!
1594
             << " Writing bind config zone statement for autosecondary zone '" << domain
376✔
1595
             << "' from autoprimary " << ipAddress << endl,
376✔
1596
       d_slog->info(Logr::Warning, "Writing bind config zone statement for autosecondary zone", "zone", Logging::Loggable(domain), "autoprimary address", Logging::Loggable(ipAddress)));
376✔
1597

376✔
1598
  {
376✔
1599
    auto lock = std::scoped_lock(s_autosecondary_config_lock);
1600

1601
    std::string configfile(getArg("autoprimary-config"));
1602
    ofstream c_of(configfile.c_str(), std::ios::app);
23✔
1603
    if (!c_of) {
×
1604
      int err = errno;
23✔
1605
      auto errorMessage = stringerror();
23✔
1606
      SLOG(g_log << Logger::Error << "Unable to open autoprimary configfile for append: " << errorMessage << endl,
×
1607
           d_slog->error(Logr::Error, err, "Unable to open autoprimary configuration file for append", "file", Logging::Loggable(configfile)));
1608
      throw DBException("Unable to open autoprimary configfile for append: " + errorMessage);
1609
    }
1610

1611
    c_of << endl;
613✔
1612
    c_of << "# AutoSecondary zone '" << domainname << "' (added: " << nowTime() << ") (account: " << account << ')' << endl;
1613
    c_of << "zone \"" << domainname << "\" {" << endl;
1614
    c_of << "\ttype secondary;" << endl;
273✔
1615
    c_of << "\tfile \"" << filename << "\";" << endl;
273✔
1616
    c_of << "\tprimaries { " << ipAddress << "; };" << endl;
273✔
1617
    c_of << "};" << endl;
273✔
1618
    c_of.close();
273✔
1619
  }
273✔
1620

273✔
1621
  BB2DomainInfo bbd = createDomainEntry(domain);
273✔
1622
  bbd.d_kind = DomainInfo::Secondary;
273✔
1623
  bbd.d_primaries.emplace_back(ComboAddress(ipAddress, 53));
273✔
1624
  bbd.d_fileinfo.emplace_back(std::make_pair(filename, 0));
273✔
1625
  bbd.updateCtime();
1626
  safePutBBDomainInfo(bbd);
1627

1,740✔
1628
  return true;
1,740✔
1629
}
1,740✔
1630

1,740✔
1631
bool Bind2Backend::searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result)
1632
{
24✔
1633
  SimpleMatch sm(pattern, true);
262✔
1634
  static bool mustlog = ::arg().mustDo("query-logging");
262✔
1635
  if (mustlog) {
262!
1636
    SLOG(g_log << Logger::Warning << "Search for pattern '" << pattern << "'" << endl,
238!
1637
         d_slog->info(Logr::Debug, "Search for pattern", "pattern", Logging::Loggable(pattern)));
1638
  }
1639

1640
  {
2,002✔
1641
    auto state = s_state.read_lock();
2,002!
1642

1643
    for (const auto& i : *state) {
2,002!
1644
      BB2DomainInfo h;
1645
      if (!safeGetBBDomainInfo(i.d_id, &h)) {
×
1646
        continue;
1647
      }
1648

1649
      if (!h.d_loaded) {
×
1650
        continue;
1651
      }
613✔
1652

613✔
1653
      shared_ptr<const recordstorage_t> rhandle = h.d_records.get();
613✔
1654

613✔
1655
      for (recordstorage_t::const_iterator ri = rhandle->begin(); result.size() < maxResults && ri != rhandle->end(); ri++) {
613!
1656
        const DNSName& domainName(i.d_name);
613✔
1657
        DNSName name = ri->qname.empty() ? domainName : (ri->qname + domainName);
613!
1658
        if (sm.match(name) || sm.match(ri->content)) {
613!
1659
          DNSResourceRecord r;
613✔
1660
          r.qname = std::move(name);
613✔
1661
          r.domain_id = i.d_id;
613✔
1662
          r.content = ri->content;
1663
          r.qtype = ri->qtype;
1664
          r.ttl = ri->ttl;
1665
          r.auth = ri->auth;
1666
          result.push_back(std::move(r));
1667
        }
1668
      }
1669
    }
1670
  }
24✔
1671

1672
  return true;
24✔
1673
}
24✔
1674

1675
class Bind2Factory : public BackendFactory
1676
{
1677
public:
1678
  Bind2Factory() :
1679
    BackendFactory("bind") {}
287✔
1680

1681
  void declareArguments(const string& suffix = "") override
1682
  {
236✔
1683
    declare(suffix, "ignore-broken-records", "Ignore records that are out-of-bound for the zone.", "no");
236✔
1684
    declare(suffix, "config", "Location of named.conf", "");
236✔
1685
    declare(suffix, "check-interval", "Interval for zonefile changes", "0");
236✔
1686
    declare(suffix, "autoprimary-config", "Location of (part of) named.conf where pdns can write zone-statements to", "");
236✔
1687
    declare(suffix, "autoprimaries", "List of IP-addresses of autoprimaries", "");
236✔
1688
    declare(suffix, "autoprimary-destdir", "Destination directory for newly added secondary zones", ::arg()["config-dir"]);
236✔
1689
    declare(suffix, "dnssec-db", "Filename to store & access our DNSSEC metadatabase, empty for none", "");
236✔
1690
    declare(suffix, "dnssec-db-journal-mode", "SQLite3 journal mode", "WAL");
236✔
1691
    declare(suffix, "hybrid", "Store DNSSEC metadata in other backend", "no");
236✔
1692
  }
236✔
1693

1694
  DNSBackend* make(const string& suffix = "") override
1695
  {
1,874✔
1696
    assertEmptySuffix(suffix);
1,874✔
1697
    return new Bind2Backend(suffix);
1,874✔
1698
  }
1,874✔
1699

1700
  DNSBackend* makeMetadataOnly(const string& suffix = "") override
1701
  {
220✔
1702
    assertEmptySuffix(suffix);
220✔
1703
    return new Bind2Backend(suffix, false);
220✔
1704
  }
220✔
1705

1706
private:
1707
  void assertEmptySuffix(const string& suffix)
1708
  {
2,094✔
1709
    if (!suffix.empty())
2,094!
1710
      throw PDNSException("launch= suffixes are not supported on the bindbackend");
1711
  }
2,094✔
1712
};
1713

1714
//! Magic class that is activated when the dynamic library is loaded
1715
class Bind2Loader
1716
{
1717
public:
1718
  Bind2Loader()
1719
  {
287✔
1720
    BackendMakers().report(std::make_unique<Bind2Factory>());
287✔
1721
    // If this module is not loaded dynamically at runtime, this code runs
1722
    // as part of a global constructor, before the structured logger has a
1723
    // chance to be set up, so fallback to simple logging.
1724
    g_log << Logger::Info << "[bind2backend] This is the bind backend version " << VERSION
287✔
1725
#ifndef REPRODUCIBLE
287✔
1726
          << " (" __DATE__ " " __TIME__ ")"
287✔
1727
#endif
287✔
1728
#ifdef HAVE_SQLITE3
287✔
1729
          << " (with bind-dnssec-db support)"
287✔
1730
#endif
287✔
1731
          << " reporting" << endl;
287✔
1732
  }
287✔
1733
};
1734
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