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

PowerDNS / pdns / 25358531950

05 May 2026 04:53AM UTC coverage: 61.082% (-1.9%) from 63.025%
25358531950

push

github

web-flow
Merge pull request #17275 from miodvallat/backport-17152-to-auth-5.0.x

auth 5.0: backport "fixes to AXFR in Bind backend"

12817 of 27922 branches covered (45.9%)

Branch coverage included in aggregate %.

12 of 17 new or added lines in 1 file covered. (70.59%)

1220 existing lines in 38 files now uncovered.

41237 of 60572 relevant lines covered (68.08%)

10232741.78 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

250
bool Bind2Backend::commitTransaction()
251
{
221✔
252
  // d_transaction_id is only set to a valid domain id if we are actually
253
  // setting up a replacement zone file with the updated data.
254
  if (d_transaction_id == UnknownDomainID) {
221✔
255
    return false;
149✔
256
  }
149✔
257
  d_of.reset();
72✔
258

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

266
  d_transaction_id = UnknownDomainID;
72✔
267

268
  return true;
72✔
269
}
72✔
270

271
bool Bind2Backend::abortTransaction()
272
{
×
273
  // d_transaction_id is only set to a valid domain id if we are actually
274
  // setting up a replacement zone file with the updated data.
275
  if (d_transaction_id != UnknownDomainID) {
×
276
    unlink(d_transaction_tmpname.c_str());
×
277
    d_of.reset();
×
278
    d_transaction_id = UnknownDomainID;
×
279
  }
×
280

281
  return true;
×
282
}
×
283

284
/** does domain end on suffix? Is smart about "wwwds9a.nl" "ds9a.nl" not matching */
285
static bool endsOn(const string& domain, const string& suffix)
286
{
416✔
287
  if (domain.size() <= suffix.size()) {
416✔
288
    return false;
52✔
289
  }
52✔
290

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

306
  string::size_type spos = 0;
276✔
307
  for (; dpos < domain.size(); ++dpos, ++spos) {
3,928✔
308
    if (!pdns_iequals_ch(domain[dpos], suffix[spos])) {
3,664✔
309
      return false;
12✔
310
    }
12✔
311
  }
3,664✔
312

313
  return true;
264✔
314
}
276✔
315

316
/** strips a domain suffix from a domain */
317
static void stripDomainSuffix(string* qname, const ZoneName& zonename)
318
{
432✔
319
  std::string domain = zonename.operator const DNSName&().toString();
432✔
320

321
  if (domain.empty()) {
432!
NEW
322
    return;
×
UNCOV
323
  }
×
324
  if (pdns_iequals(*qname, domain)) {
432✔
325
    *qname = "@";
16✔
326
    return;
16✔
327
  }
16✔
328
  if (endsOn(*qname, domain)) {
416✔
329
    auto prefix = qname->size() - domain.size();
264✔
330
    qname->resize(prefix - 1); // also strip dot
264✔
331
  }
264✔
332
}
416✔
333

334
bool Bind2Backend::feedRecord(const DNSResourceRecord& rr, const DNSName& /* ordername */, bool /* ordernameIsNSEC3 */)
335
{
202,785✔
336
  if (d_transaction_id == UnknownDomainID) {
202,785!
337
    throw DBException("Bind2Backend::feedRecord() called outside of transaction");
×
338
  }
×
339

340
  string qname;
202,785✔
341
  if (d_transaction_qname.empty()) {
202,785!
342
    qname = rr.qname.toString();
×
343
  }
×
344
  else if (rr.qname.isPartOf(d_transaction_qname)) {
202,785!
345
    if (rr.qname == d_transaction_qname.operator const DNSName&()) {
202,785✔
346
      qname = "@";
633✔
347
    }
633✔
348
    else {
202,152✔
349
      DNSName relName = rr.qname.makeRelative(d_transaction_qname);
202,152✔
350
      qname = relName.toStringNoDot();
202,152✔
351
    }
202,152✔
352
  }
202,785✔
353
  else {
×
354
    throw DBException("out-of-zone data '" + rr.qname.toLogString() + "' during AXFR of zone '" + d_transaction_qname.toLogString() + "'");
×
355
  }
×
356

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

360
  // SOA needs stripping too! XXX FIXME - also, this should not be here I think
361
  switch (rr.qtype.getCode()) {
202,785✔
362
  case QType::MX:
56✔
363
  case QType::SRV:
76✔
364
  case QType::CNAME:
208✔
365
  case QType::DNAME:
212✔
366
  case QType::NS:
432✔
367
    stripDomainSuffix(&content, d_transaction_qname);
432✔
368
    [[fallthrough]];
432✔
369
  default:
202,785✔
370
    if (d_of && *d_of) {
202,785!
371
      *d_of << qname << "\t" << rr.ttl << "\t" << rr.qtype.toString() << "\t" << content << endl;
202,785✔
372
    }
202,785✔
373
  }
202,785✔
374
  return true;
202,785✔
375
}
202,785✔
376

377
void Bind2Backend::getUpdatedPrimaries(vector<DomainInfo>& changedDomains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
378
{
×
379
  vector<DomainInfo> consider;
×
380
  {
×
381
    auto state = s_state.read_lock();
×
382

383
    for (const auto& i : *state) {
×
384
      if (i.d_kind != DomainInfo::Primary && this->alsoNotify.empty() && i.d_also_notify.empty())
×
385
        continue;
×
386

387
      DomainInfo di;
×
388
      di.id = i.d_id;
×
389
      di.zone = i.d_name;
×
390
      di.last_check = i.d_lastcheck;
×
391
      di.notified_serial = i.d_lastnotified;
×
392
      di.backend = this;
×
393
      di.kind = DomainInfo::Primary;
×
394
      consider.push_back(std::move(di));
×
395
    }
×
396
  }
×
397

398
  SOAData soadata;
×
399
  for (DomainInfo& di : consider) {
×
400
    soadata.serial = 0;
×
401
    try {
×
402
      this->getSOA(di.zone, di.id, soadata); // we might not *have* a SOA yet, but this might trigger a load of it
×
403
    }
×
404
    catch (...) {
×
405
      continue;
×
406
    }
×
407
    if (di.notified_serial != soadata.serial) {
×
408
      BB2DomainInfo bbd;
×
409
      if (safeGetBBDomainInfo(di.id, &bbd)) {
×
410
        bbd.d_lastnotified = soadata.serial;
×
411
        safePutBBDomainInfo(bbd);
×
412
      }
×
413
      if (di.notified_serial) { // don't do notification storm on startup
×
414
        di.serial = soadata.serial;
×
415
        changedDomains.push_back(std::move(di));
×
416
      }
×
417
    }
×
418
  }
×
419
}
×
420

421
void Bind2Backend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool /* include_disabled */)
422
{
93✔
423
  SOAData soadata;
93✔
424

425
  // prevent deadlock by using getSOA() later on
426
  {
93✔
427
    auto state = s_state.read_lock();
93✔
428
    domains->reserve(state->size());
93✔
429

430
    for (const auto& i : *state) {
253✔
431
      DomainInfo di;
180✔
432
      di.id = i.d_id;
180✔
433
      di.zone = i.d_name;
180✔
434
      di.last_check = i.d_lastcheck;
180✔
435
      di.kind = i.d_kind;
180✔
436
      di.primaries = i.d_primaries;
180✔
437
      di.backend = this;
180✔
438
      domains->push_back(std::move(di));
180✔
439
    };
180✔
440
  }
93✔
441

442
  if (getSerial) {
93✔
443
    for (DomainInfo& di : *domains) {
1,362✔
444
      // do not corrupt di if domain supplied by another backend.
445
      if (di.backend != this)
1,362!
446
        continue;
1,362✔
447
      try {
×
448
        this->getSOA(di.zone, di.id, soadata);
×
449
      }
×
450
      catch (...) {
×
451
        continue;
×
452
      }
×
453
      di.serial = soadata.serial;
×
454
    }
×
455
  }
42✔
456
}
93✔
457

458
void Bind2Backend::getUnfreshSecondaryInfos(vector<DomainInfo>* unfreshDomains)
459
{
8✔
460
  vector<DomainInfo> domains;
8✔
461
  {
8✔
462
    auto state = s_state.read_lock();
8✔
463
    domains.reserve(state->size());
8✔
464
    for (const auto& i : *state) {
72✔
465
      if (i.d_kind != DomainInfo::Secondary)
72!
466
        continue;
×
467
      DomainInfo sd;
72✔
468
      sd.id = i.d_id;
72✔
469
      sd.zone = i.d_name;
72✔
470
      sd.primaries = i.d_primaries;
72✔
471
      sd.last_check = i.d_lastcheck;
72✔
472
      sd.backend = this;
72✔
473
      sd.kind = DomainInfo::Secondary;
72✔
474
      domains.push_back(std::move(sd));
72✔
475
    }
72✔
476
  }
8✔
477
  unfreshDomains->reserve(domains.size());
8✔
478

479
  for (DomainInfo& sd : domains) {
72✔
480
    SOAData soadata;
72✔
481
    soadata.refresh = 0;
72✔
482
    soadata.serial = 0;
72✔
483
    try {
72✔
484
      getSOA(sd.zone, sd.id, soadata); // we might not *have* a SOA yet
72✔
485
    }
72✔
486
    catch (...) {
72✔
487
    }
72✔
488
    sd.serial = soadata.serial;
72✔
489
    // coverity[store_truncates_time_t]
490
    if (sd.last_check + soadata.refresh < (unsigned int)time(nullptr))
72!
491
      unfreshDomains->push_back(std::move(sd));
72✔
492
  }
72✔
493
}
8✔
494

495
bool Bind2Backend::getDomainInfo(const ZoneName& domain, DomainInfo& info, bool getSerial)
496
{
1,025✔
497
  BB2DomainInfo bbd;
1,025✔
498
  if (!safeGetBBDomainInfo(domain, &bbd))
1,025✔
499
    return false;
585✔
500

501
  info.id = bbd.d_id;
440✔
502
  info.zone = domain;
440✔
503
  info.primaries = bbd.d_primaries;
440✔
504
  info.last_check = bbd.d_lastcheck;
440✔
505
  info.backend = this;
440✔
506
  info.kind = bbd.d_kind;
440✔
507
  info.serial = 0;
440✔
508
  if (getSerial) {
440✔
509
    try {
134✔
510
      SOAData sd;
134✔
511
      sd.serial = 0;
134✔
512

513
      getSOA(bbd.d_name, bbd.d_id, sd); // we might not *have* a SOA yet
134✔
514
      info.serial = sd.serial;
134✔
515
    }
134✔
516
    catch (...) {
134✔
517
    }
72✔
518
  }
134✔
519

520
  return true;
440✔
521
}
440✔
522

523
void Bind2Backend::alsoNotifies(const ZoneName& domain, set<string>* ips)
524
{
4✔
525
  // combine global list with local list
526
  for (const auto& i : this->alsoNotify) {
4!
527
    (*ips).insert(i);
×
528
  }
×
529
  // check metadata too if available
530
  vector<string> meta;
4✔
531
  if (getDomainMetadata(domain, "ALSO-NOTIFY", meta)) {
4!
532
    for (const auto& str : meta) {
×
533
      (*ips).insert(str);
×
534
    }
×
535
  }
×
536
  auto state = s_state.read_lock();
4✔
537
  for (const auto& i : *state) {
4!
538
    if (i.d_name == domain) {
×
539
      for (const auto& it : i.d_also_notify) {
×
540
        (*ips).insert(it);
×
541
      }
×
542
      return;
×
543
    }
×
544
  }
×
545
}
4✔
546

547
// only parses, does NOT add to s_state!
548
void Bind2Backend::parseZoneFile(BB2DomainInfo* bbd)
549
{
2,730✔
550
  NSEC3PARAMRecordContent ns3pr;
2,730✔
551
  bool nsec3zone = false;
2,730✔
552
  if (d_hybrid) {
2,730!
553
    DNSSECKeeper dk;
×
554
    nsec3zone = dk.getNSEC3PARAM(bbd->d_name, &ns3pr);
×
555
  }
×
556
  else
2,730✔
557
    nsec3zone = getNSEC3PARAMuncached(bbd->d_name, &ns3pr);
2,730✔
558

559
  auto records = std::make_shared<recordstorage_t>();
2,730✔
560
  ZoneParserTNG zpt(bbd->d_filename, bbd->d_name, s_binddirectory, d_upgradeContent);
2,730✔
561
  zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
2,730✔
562
  zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
2,730✔
563
  DNSResourceRecord rr;
2,730✔
564
  string hashed;
2,730✔
565
  while (zpt.get(rr)) {
3,277,054✔
566
    if (rr.qtype.getCode() == QType::NSEC || rr.qtype.getCode() == QType::NSEC3 || rr.qtype.getCode() == QType::NSEC3PARAM)
3,274,324!
567
      continue; // we synthesise NSECs on demand
×
568

569
    insertRecord(records, bbd->d_name, rr.qname, rr.qtype, rr.content, rr.ttl, "");
3,274,324✔
570
  }
3,274,324✔
571
  fixupOrderAndAuth(records, bbd->d_name, nsec3zone, ns3pr);
2,730✔
572
  doEmptyNonTerminals(records, bbd->d_name, nsec3zone, ns3pr);
2,730✔
573
  bbd->setCtime();
2,730✔
574
  bbd->d_loaded = true;
2,730✔
575
  bbd->d_checknow = false;
2,730✔
576
  bbd->d_status = "parsed into memory at " + nowTime();
2,730✔
577
  bbd->d_records = LookButDontTouch<recordstorage_t>(std::move(records));
2,730✔
578
  bbd->d_nsec3zone = nsec3zone;
2,730✔
579
  bbd->d_nsec3param = std::move(ns3pr);
2,730✔
580
}
2,730✔
581

582
/** THIS IS AN INTERNAL FUNCTION! It does moadnsparser prio impedance matching
583
    Much of the complication is due to the efforts to benefit from std::string reference counting copy on write semantics */
584
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)
585
{
3,278,934✔
586
  Bind2DNSRecord bdr;
3,278,934✔
587
  bdr.qname = qname;
3,278,934✔
588

589
  if (zoneName.empty())
3,278,934!
590
    ;
×
591
  else if (bdr.qname.isPartOf(zoneName))
3,278,934✔
592
    bdr.qname.makeUsRelative(zoneName);
3,278,632✔
593
  else {
302✔
594
    string msg = "Trying to insert non-zone data, name='" + bdr.qname.toLogString() + "', qtype=" + qtype.toString() + ", zone='" + zoneName.toLogString() + "'";
302✔
595
    if (s_ignore_broken_records) {
302!
596
      g_log << Logger::Warning << msg << " ignored" << endl;
302✔
597
      return;
302✔
598
    }
302✔
599
    throw PDNSException(std::move(msg));
×
600
  }
302✔
601

602
  //  bdr.qname.swap(bdr.qname);
603

604
  if (!records->empty() && bdr.qname == boost::prior(records->end())->qname)
3,278,632✔
605
    bdr.qname = boost::prior(records->end())->qname;
8,868✔
606

607
  bdr.qname = bdr.qname;
3,278,632✔
608
  bdr.qtype = qtype.getCode();
3,278,632✔
609
  bdr.content = content;
3,278,632✔
610
  bdr.nsec3hash = hashed;
3,278,632✔
611

612
  if (auth != nullptr) // Set auth on empty non-terminals
3,278,632✔
613
    bdr.auth = *auth;
4,610✔
614
  else
3,274,022✔
615
    bdr.auth = true;
3,274,022✔
616

617
  bdr.ttl = ttl;
3,278,632✔
618
  records->insert(std::move(bdr));
3,278,632✔
619
}
3,278,632✔
620

621
string Bind2Backend::DLReloadNowHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
622
{
×
623
  ostringstream ret;
×
624

625
  for (auto i = parts.begin() + 1; i < parts.end(); ++i) {
×
626
    BB2DomainInfo bbd;
×
627
    ZoneName zone(*i);
×
628
    if (safeGetBBDomainInfo(zone, &bbd)) {
×
629
      Bind2Backend bb2;
×
630
      bb2.queueReloadAndStore(bbd.d_id);
×
631
      if (!safeGetBBDomainInfo(zone, &bbd)) // Read the *new* domain status
×
632
        ret << *i << ": [missing]\n";
×
633
      else
×
634
        ret << *i << ": " << (bbd.d_wasRejectedLastReload ? "[rejected]" : "") << "\t" << bbd.d_status << "\n";
×
635
      purgeAuthCaches(zone.operator const DNSName&().toString() + "$");
×
636
      DNSSECKeeper::clearMetaCache(zone);
×
637
    }
×
638
    else
×
639
      ret << *i << " no such domain\n";
×
640
  }
×
641
  if (ret.str().empty())
×
642
    ret << "no domains reloaded";
×
643
  return ret.str();
×
644
}
×
645

646
string Bind2Backend::DLDomStatusHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
647
{
23✔
648
  ostringstream ret;
23✔
649

650
  if (parts.size() > 1) {
23!
651
    for (auto i = parts.begin() + 1; i < parts.end(); ++i) {
×
652
      BB2DomainInfo bbd;
×
653
      if (safeGetBBDomainInfo(ZoneName(*i), &bbd)) {
×
654
        ret << *i << ": " << (bbd.d_loaded ? "" : "[rejected]") << "\t" << bbd.d_status << "\n";
×
655
      }
×
656
      else {
×
657
        ret << *i << " no such domain\n";
×
658
      }
×
659
    }
×
660
  }
×
661
  else {
23✔
662
    auto state = s_state.read_lock();
23✔
663
    for (const auto& i : *state) {
231✔
664
      ret << i.d_name << ": " << (i.d_loaded ? "" : "[rejected]") << "\t" << i.d_status << "\n";
231✔
665
    }
231✔
666
  }
23✔
667

668
  if (ret.str().empty())
23!
669
    ret << "no domains passed";
×
670

671
  return ret.str();
23✔
672
}
23✔
673

674
static void printDomainExtendedStatus(ostringstream& ret, const BB2DomainInfo& info)
675
{
×
676
  ret << info.d_name << ": " << std::endl;
×
677
  ret << "\t Status: " << info.d_status << std::endl;
×
678
  ret << "\t Internal ID: " << info.d_id << std::endl;
×
679
  ret << "\t On-disk file: " << info.d_filename << " (" << info.d_ctime << ")" << std::endl;
×
680
  ret << "\t Kind: ";
×
681
  switch (info.d_kind) {
×
682
  case DomainInfo::Primary:
×
683
    ret << "Primary";
×
684
    break;
×
685
  case DomainInfo::Secondary:
×
686
    ret << "Secondary";
×
687
    break;
×
688
  default:
×
689
    ret << "Native";
×
690
  }
×
691
  ret << std::endl;
×
692
  ret << "\t Primaries: " << std::endl;
×
693
  for (const auto& primary : info.d_primaries) {
×
694
    ret << "\t\t - " << primary.toStringWithPort() << std::endl;
×
695
  }
×
696
  ret << "\t Also Notify: " << std::endl;
×
697
  for (const auto& also : info.d_also_notify) {
×
698
    ret << "\t\t - " << also << std::endl;
×
699
  }
×
700
  ret << "\t Number of records: " << info.d_records.getEntriesCount() << std::endl;
×
701
  ret << "\t Loaded: " << info.d_loaded << std::endl;
×
702
  ret << "\t Check now: " << info.d_checknow << std::endl;
×
703
  ret << "\t Check interval: " << info.getCheckInterval() << std::endl;
×
704
  ret << "\t Last check: " << info.d_lastcheck << std::endl;
×
705
  ret << "\t Last notified: " << info.d_lastnotified << std::endl;
×
706
}
×
707

708
string Bind2Backend::DLDomExtendedStatusHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
709
{
×
710
  ostringstream ret;
×
711

712
  if (parts.size() > 1) {
×
713
    for (auto i = parts.begin() + 1; i < parts.end(); ++i) {
×
714
      BB2DomainInfo bbd;
×
715
      if (safeGetBBDomainInfo(ZoneName(*i), &bbd)) {
×
716
        printDomainExtendedStatus(ret, bbd);
×
717
      }
×
718
      else {
×
719
        ret << *i << " no such domain" << std::endl;
×
720
      }
×
721
    }
×
722
  }
×
723
  else {
×
724
    auto rstate = s_state.read_lock();
×
725
    for (const auto& state : *rstate) {
×
726
      printDomainExtendedStatus(ret, state);
×
727
    }
×
728
  }
×
729

730
  if (ret.str().empty()) {
×
731
    ret << "no domains passed" << std::endl;
×
732
  }
×
733

734
  return ret.str();
×
735
}
×
736

737
string Bind2Backend::DLListRejectsHandler(const vector<string>& /* parts */, Utility::pid_t /* ppid */)
738
{
×
739
  ostringstream ret;
×
740
  auto rstate = s_state.read_lock();
×
741
  for (const auto& i : *rstate) {
×
742
    if (!i.d_loaded)
×
743
      ret << i.d_name << "\t" << i.d_status << endl;
×
744
  }
×
745
  return ret.str();
×
746
}
×
747

748
string Bind2Backend::DLAddDomainHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
749
{
12✔
750
  if (parts.size() < 3)
12!
751
    return "ERROR: Domain name and zone filename are required";
×
752

753
  ZoneName domainname(parts[1]);
12✔
754
  const string& filename = parts[2];
12✔
755
  BB2DomainInfo bbd;
12✔
756
  if (safeGetBBDomainInfo(domainname, &bbd))
12✔
757
    return "Already loaded";
6✔
758

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

762
  struct stat buf;
6✔
763
  if (stat(filename.c_str(), &buf) != 0)
6!
764
    return "Unable to load zone " + domainname.toLogString() + " from " + filename + ": " + strerror(errno);
×
765

766
  Bind2Backend bb2; // createdomainentry needs access to our configuration
6✔
767
  bbd = bb2.createDomainEntry(domainname, filename);
6✔
768
  bbd.d_filename = filename;
6✔
769
  bbd.d_checknow = true;
6✔
770
  bbd.d_loaded = true;
6✔
771
  bbd.d_lastcheck = 0;
6✔
772
  bbd.d_status = "parsing into memory";
6✔
773
  bbd.setCtime();
6✔
774

775
  safePutBBDomainInfo(bbd);
6✔
776

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

779
  g_log << Logger::Warning << "Zone " << domainname << " loaded" << endl;
6✔
780
  return "Loaded zone " + domainname.toLogString() + " from " + filename;
6✔
781
}
6✔
782

783
Bind2Backend::Bind2Backend(const string& suffix, bool loadZones)
784
{
3,271✔
785
  d_getAllDomainMetadataQuery_stmt = nullptr;
3,271✔
786
  d_getDomainMetadataQuery_stmt = nullptr;
3,271✔
787
  d_deleteDomainMetadataQuery_stmt = nullptr;
3,271✔
788
  d_insertDomainMetadataQuery_stmt = nullptr;
3,271✔
789
  d_getDomainKeysQuery_stmt = nullptr;
3,271✔
790
  d_deleteDomainKeyQuery_stmt = nullptr;
3,271✔
791
  d_insertDomainKeyQuery_stmt = nullptr;
3,271✔
792
  d_GetLastInsertedKeyIdQuery_stmt = nullptr;
3,271✔
793
  d_activateDomainKeyQuery_stmt = nullptr;
3,271✔
794
  d_deactivateDomainKeyQuery_stmt = nullptr;
3,271✔
795
  d_getTSIGKeyQuery_stmt = nullptr;
3,271✔
796
  d_setTSIGKeyQuery_stmt = nullptr;
3,271✔
797
  d_deleteTSIGKeyQuery_stmt = nullptr;
3,271✔
798
  d_getTSIGKeysQuery_stmt = nullptr;
3,271✔
799

800
  setArgPrefix("bind" + suffix);
3,271✔
801
  d_logprefix = "[bind" + suffix + "backend]";
3,271✔
802
  d_hybrid = mustDo("hybrid");
3,271✔
803
  if (d_hybrid && g_zoneCache.isEnabled()) {
3,271!
804
    throw PDNSException("bind-hybrid and the zone cache currently interoperate badly. Please disable the zone cache or stop using bind-hybrid");
×
805
  }
×
806

807
  d_transaction_id = UnknownDomainID;
3,271✔
808
  s_ignore_broken_records = mustDo("ignore-broken-records");
3,271✔
809
  d_upgradeContent = ::arg().mustDo("upgrade-unknown-types");
3,271✔
810

811
  if (!loadZones && d_hybrid)
3,271!
812
    return;
×
813

814
  auto lock = std::scoped_lock(s_startup_lock);
3,271✔
815

816
  setupDNSSEC();
3,271✔
817
  if (s_first == 0) {
3,271✔
818
    return;
2,529✔
819
  }
2,529✔
820

821
  if (loadZones) {
742✔
822
    loadConfig();
304✔
823
    s_first = 0;
304✔
824
  }
304✔
825

826
  DynListener::registerFunc("BIND-RELOAD-NOW", &DLReloadNowHandler, "bindbackend: reload domains", "<domains>");
742✔
827
  DynListener::registerFunc("BIND-DOMAIN-STATUS", &DLDomStatusHandler, "bindbackend: list status of all domains", "[domains]");
742✔
828
  DynListener::registerFunc("BIND-DOMAIN-EXTENDED-STATUS", &DLDomExtendedStatusHandler, "bindbackend: list the extended status of all domains", "[domains]");
742✔
829
  DynListener::registerFunc("BIND-LIST-REJECTS", &DLListRejectsHandler, "bindbackend: list rejected domains");
742✔
830
  DynListener::registerFunc("BIND-ADD-ZONE", &DLAddDomainHandler, "bindbackend: add zone", "<domain> <filename>");
742✔
831
}
742✔
832

833
Bind2Backend::~Bind2Backend()
834
{
3,167✔
835
  freeStatements();
3,167✔
836
} // deallocate statements
3,167✔
837

838
void Bind2Backend::rediscover(string* status)
839
{
×
840
  loadConfig(status);
×
841
}
×
842

843
void Bind2Backend::reload()
844
{
×
845
  auto state = s_state.write_lock();
×
846
  for (const auto& i : *state) {
×
847
    i.d_checknow = true; // being a bit cheeky here, don't index state_t on this (mutable)
×
848
  }
×
849
}
×
850

851
void Bind2Backend::fixupOrderAndAuth(std::shared_ptr<recordstorage_t>& records, const ZoneName& zoneName, bool nsec3zone, const NSEC3PARAMRecordContent& ns3pr)
852
{
2,658✔
853
  bool skip;
2,658✔
854
  DNSName shorter;
2,658✔
855
  set<DNSName> nssets, dssets;
2,658✔
856

857
  for (const auto& bdr : *records) {
3,274,022✔
858
    if (!bdr.qname.isRoot() && bdr.qtype == QType::NS)
3,274,022✔
859
      nssets.insert(bdr.qname);
3,008✔
860
    else if (bdr.qtype == QType::DS)
3,271,014✔
861
      dssets.insert(bdr.qname);
499✔
862
  }
3,274,022✔
863

864
  for (auto iter = records->begin(); iter != records->end(); iter++) {
3,276,680✔
865
    skip = false;
3,274,022✔
866
    shorter = iter->qname;
3,274,022✔
867

868
    if (!iter->qname.isRoot() && shorter.chopOff() && !iter->qname.isRoot()) {
3,274,022!
869
      do {
3,272,484✔
870
        if (nssets.count(shorter) != 0u) {
3,272,484✔
871
          skip = true;
1,359✔
872
          break;
1,359✔
873
        }
1,359✔
874
      } while (shorter.chopOff() && !iter->qname.isRoot());
3,272,484!
875
    }
3,262,914✔
876

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

879
    if (!skip && nsec3zone && iter->qtype != QType::RRSIG && (iter->auth || (iter->qtype == QType::NS && (ns3pr.d_flags == 0u)) || (dssets.count(iter->qname) != 0u))) {
3,274,022✔
880
      Bind2DNSRecord bdr = *iter;
1,439,445✔
881
      bdr.nsec3hash = toBase32Hex(hashQNameWithSalt(ns3pr, bdr.qname + zoneName.operator const DNSName&()));
1,439,445✔
882
      records->replace(iter, bdr);
1,439,445✔
883
    }
1,439,445✔
884

885
    // cerr<<iter->qname<<"\t"<<QType(iter->qtype).toString()<<"\t"<<iter->nsec3hash<<"\t"<<iter->auth<<endl;
886
  }
3,274,022✔
887
}
2,658✔
888

889
void Bind2Backend::doEmptyNonTerminals(std::shared_ptr<recordstorage_t>& records, const ZoneName& zoneName, bool nsec3zone, const NSEC3PARAMRecordContent& ns3pr)
890
{
2,658✔
891
  bool auth = false;
2,658✔
892
  DNSName shorter;
2,658✔
893
  std::unordered_set<DNSName> qnames;
2,658✔
894
  std::unordered_map<DNSName, bool> nonterm;
2,658✔
895

896
  uint32_t maxent = ::arg().asNum("max-ent-entries");
2,658✔
897

898
  for (const auto& bdr : *records)
2,658✔
899
    qnames.insert(bdr.qname);
3,274,022✔
900

901
  for (const auto& bdr : *records) {
3,274,022✔
902

903
    if (!bdr.auth && bdr.qtype == QType::NS)
3,274,022✔
904
      auth = (!nsec3zone || (ns3pr.d_flags == 0u));
3,008✔
905
    else
3,271,014✔
906
      auth = bdr.auth;
3,271,014✔
907

908
    shorter = bdr.qname;
3,274,022✔
909
    while (shorter.chopOff()) {
6,547,865✔
910
      if (qnames.count(shorter) == 0u) {
3,273,843✔
911
        if (!(maxent)) {
9,405!
912
          g_log << Logger::Error << "Zone '" << zoneName << "' has too many empty non terminals." << endl;
×
913
          return;
×
914
        }
×
915

916
        if (nonterm.count(shorter) == 0u) {
9,405✔
917
          nonterm.emplace(shorter, auth);
4,610✔
918
          --maxent;
4,610✔
919
        }
4,610✔
920
        else if (auth)
4,795✔
921
          nonterm[shorter] = true;
4,699✔
922
      }
9,405✔
923
    }
3,273,843✔
924
  }
3,274,022✔
925

926
  DNSResourceRecord rr;
2,658✔
927
  rr.qtype = "#0";
2,658✔
928
  rr.content = "";
2,658✔
929
  rr.ttl = 0;
2,658✔
930
  for (auto& nt : nonterm) {
4,610✔
931
    string hashed;
4,610✔
932
    rr.qname = nt.first + zoneName.operator const DNSName&();
4,610✔
933
    if (nsec3zone && nt.second)
4,610✔
934
      hashed = toBase32Hex(hashQNameWithSalt(ns3pr, rr.qname));
1,782✔
935
    insertRecord(records, zoneName, rr.qname, rr.qtype, rr.content, rr.ttl, hashed, &nt.second);
4,610✔
936

937
    // cerr<<rr.qname<<"\t"<<rr.qtype.toString()<<"\t"<<hashed<<"\t"<<nt.second<<endl;
938
  }
4,610✔
939
}
2,658✔
940

941
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
942
{
304✔
943
  static domainid_t domain_id = 1;
304✔
944

945
  if (!getArg("config").empty()) {
304!
946
    BindParser BP;
304✔
947
    try {
304✔
948
      BP.parse(getArg("config"));
304✔
949
    }
304✔
950
    catch (PDNSException& ae) {
304✔
951
      g_log << Logger::Error << "Error parsing bind configuration: " << ae.reason << endl;
×
952
      throw;
×
953
    }
×
954

955
    vector<BindDomainInfo> domains = BP.getDomains();
304✔
956
    this->alsoNotify = BP.getAlsoNotify();
304✔
957

958
    s_binddirectory = BP.getDirectory();
304✔
959
    //    ZP.setDirectory(d_binddirectory);
960

961
    g_log << Logger::Warning << d_logprefix << " Parsing " << domains.size() << " domain(s), will report when done" << endl;
304✔
962

963
    set<ZoneName> oldnames;
304✔
964
    set<ZoneName> newnames;
304✔
965
    {
304✔
966
      auto state = s_state.read_lock();
304✔
967
      for (const BB2DomainInfo& bbd : *state) {
304!
968
        oldnames.insert(bbd.d_name);
×
969
      }
×
970
    }
304✔
971
    int rejected = 0;
304✔
972
    int newdomains = 0;
304✔
973

974
    struct stat st;
304✔
975

976
    for (auto& domain : domains) {
2,784✔
977
      if (stat(domain.filename.c_str(), &st) == 0) {
2,652✔
978
        domain.d_dev = st.st_dev;
2,580✔
979
        domain.d_ino = st.st_ino;
2,580✔
980
      }
2,580✔
981
    }
2,652✔
982

983
    sort(domains.begin(), domains.end()); // put stuff in inode order
304✔
984
    for (const auto& domain : domains) {
2,784✔
985
      if (!(domain.hadFileDirective)) {
2,652!
986
        g_log << Logger::Warning << d_logprefix << " Zone '" << domain.name << "' has no 'file' directive set in " << getArg("config") << endl;
×
987
        rejected++;
×
988
        continue;
×
989
      }
×
990

991
      if (domain.type.empty()) {
2,652!
992
        g_log << Logger::Notice << d_logprefix << " Zone '" << domain.name << "' has no type specified, assuming 'native'" << endl;
×
993
      }
×
994
      if (domain.type != "primary" && domain.type != "secondary" && domain.type != "native" && !domain.type.empty() && domain.type != "master" && domain.type != "slave") {
2,652!
995
        g_log << Logger::Warning << d_logprefix << " Warning! Skipping zone '" << domain.name << "' because type '" << domain.type << "' is invalid" << endl;
×
996
        rejected++;
×
997
        continue;
×
998
      }
×
999

1000
      BB2DomainInfo bbd;
2,652✔
1001
      bool isNew = false;
2,652✔
1002

1003
      if (!safeGetBBDomainInfo(domain.name, &bbd)) {
2,652!
1004
        isNew = true;
2,652✔
1005
        bbd.d_id = domain_id++;
2,652✔
1006
        bbd.setCheckInterval(getArgAsNum("check-interval"));
2,652✔
1007
        bbd.d_lastnotified = 0;
2,652✔
1008
        bbd.d_loaded = false;
2,652✔
1009
      }
2,652✔
1010

1011
      // overwrite what we knew about the domain
1012
      bbd.d_name = domain.name;
2,652✔
1013
      bool filenameChanged = (bbd.d_filename != domain.filename);
2,652✔
1014
      bool addressesChanged = (bbd.d_primaries != domain.primaries || bbd.d_also_notify != domain.alsoNotify);
2,652!
1015
      bbd.d_filename = domain.filename;
2,652✔
1016
      bbd.d_primaries = domain.primaries;
2,652✔
1017
      bbd.d_also_notify = domain.alsoNotify;
2,652✔
1018

1019
      DomainInfo::DomainKind kind = DomainInfo::Native;
2,652✔
1020
      if (domain.type == "primary" || domain.type == "master") {
2,652!
1021
        kind = DomainInfo::Primary;
2,580✔
1022
      }
2,580✔
1023
      if (domain.type == "secondary" || domain.type == "slave") {
2,652!
1024
        kind = DomainInfo::Secondary;
72✔
1025
      }
72✔
1026

1027
      bool kindChanged = (bbd.d_kind != kind);
2,652✔
1028
      bbd.d_kind = kind;
2,652✔
1029

1030
      newnames.insert(bbd.d_name);
2,652✔
1031
      if (filenameChanged || !bbd.d_loaded || !bbd.current()) {
2,652!
1032
        g_log << Logger::Info << d_logprefix << " parsing '" << domain.name << "' from file '" << domain.filename << "'" << endl;
2,652✔
1033

1034
        try {
2,652✔
1035
          parseZoneFile(&bbd);
2,652✔
1036
        }
2,652✔
1037
        catch (PDNSException& ae) {
2,652✔
1038
          ostringstream msg;
×
1039
          msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.reason;
×
1040

1041
          if (status != nullptr)
×
1042
            *status += msg.str();
×
1043
          bbd.d_status = msg.str();
×
1044

1045
          g_log << Logger::Warning << d_logprefix << msg.str() << endl;
×
1046
          rejected++;
×
1047
        }
×
1048
        catch (std::system_error& ae) {
2,652✔
1049
          ostringstream msg;
72✔
1050
          if (ae.code().value() == ENOENT && isNew && domain.type == "slave")
72!
1051
            msg << " error at " + nowTime() << " no file found for new secondary domain '" << domain.name << "'. Has not been AXFR'd yet";
×
1052
          else
72✔
1053
            msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.what();
72✔
1054

1055
          if (status != nullptr)
72!
1056
            *status += msg.str();
×
1057
          bbd.d_status = msg.str();
72✔
1058
          g_log << Logger::Warning << d_logprefix << msg.str() << endl;
72✔
1059
          rejected++;
72✔
1060
        }
72✔
1061
        catch (std::exception& ae) {
2,652✔
1062
          ostringstream msg;
×
1063
          msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.what();
×
1064

1065
          if (status != nullptr)
×
1066
            *status += msg.str();
×
1067
          bbd.d_status = msg.str();
×
1068

1069
          g_log << Logger::Warning << d_logprefix << msg.str() << endl;
×
1070
          rejected++;
×
1071
        }
×
1072
        safePutBBDomainInfo(bbd);
2,652✔
1073
      }
2,652✔
1074
      else if (addressesChanged || kindChanged) {
×
1075
        safePutBBDomainInfo(bbd);
×
1076
      }
×
1077
    }
2,652✔
1078
    vector<ZoneName> diff;
304✔
1079

1080
    set_difference(oldnames.begin(), oldnames.end(), newnames.begin(), newnames.end(), back_inserter(diff));
304✔
1081
    unsigned int remdomains = diff.size();
304✔
1082

1083
    for (const ZoneName& name : diff) {
304!
1084
      safeRemoveBBDomainInfo(name);
×
1085
    }
×
1086

1087
    // count number of entirely new domains
1088
    diff.clear();
304✔
1089
    set_difference(newnames.begin(), newnames.end(), oldnames.begin(), oldnames.end(), back_inserter(diff));
304✔
1090
    newdomains = diff.size();
304✔
1091

1092
    ostringstream msg;
304✔
1093
    msg << " Done parsing domains, " << rejected << " rejected, " << newdomains << " new, " << remdomains << " removed";
304✔
1094
    if (status != nullptr)
304!
1095
      *status = msg.str();
×
1096

1097
    g_log << Logger::Error << d_logprefix << msg.str() << endl;
304✔
1098
  }
304✔
1099
}
304✔
1100

1101
// NOLINTNEXTLINE(readability-identifier-length)
1102
void Bind2Backend::queueReloadAndStore(domainid_t id)
1103
{
78✔
1104
  BB2DomainInfo bbold;
78✔
1105
  try {
78✔
1106
    if (!safeGetBBDomainInfo(id, &bbold))
78!
1107
      return;
×
1108
    bbold.d_checknow = false;
78✔
1109
    BB2DomainInfo bbnew(bbold);
78✔
1110
    /* make sure that nothing will be able to alter the existing records,
1111
       we will load them from the zone file instead */
1112
    bbnew.d_records = LookButDontTouch<recordstorage_t>();
78✔
1113
    parseZoneFile(&bbnew);
78✔
1114
    bbnew.d_wasRejectedLastReload = false;
78✔
1115
    safePutBBDomainInfo(bbnew);
78✔
1116
    g_log << Logger::Warning << "Zone '" << bbnew.d_name << "' (" << bbnew.d_filename << ") reloaded" << endl;
78✔
1117
  }
78✔
1118
  catch (PDNSException& ae) {
78✔
1119
    ostringstream msg;
×
1120
    msg << " error at " + nowTime() + " parsing '" << bbold.d_name << "' from file '" << bbold.d_filename << "': " << ae.reason;
×
1121
    g_log << Logger::Warning << "Error parsing '" << bbold.d_name << "' from file '" << bbold.d_filename << "': " << ae.reason << endl;
×
1122
    bbold.d_status = msg.str();
×
1123
    bbold.d_lastcheck = time(nullptr);
×
1124
    bbold.d_wasRejectedLastReload = true;
×
1125
    safePutBBDomainInfo(bbold);
×
1126
  }
×
1127
  catch (std::exception& ae) {
78✔
1128
    ostringstream msg;
×
1129
    msg << " error at " + nowTime() + " parsing '" << bbold.d_name << "' from file '" << bbold.d_filename << "': " << ae.what();
×
1130
    g_log << Logger::Warning << "Error parsing '" << bbold.d_name << "' from file '" << bbold.d_filename << "': " << ae.what() << endl;
×
1131
    bbold.d_status = msg.str();
×
1132
    bbold.d_lastcheck = time(nullptr);
×
1133
    bbold.d_wasRejectedLastReload = true;
×
1134
    safePutBBDomainInfo(bbold);
×
1135
  }
×
1136
}
78✔
1137

1138
bool Bind2Backend::findBeforeAndAfterUnhashed(std::shared_ptr<const recordstorage_t>& records, const DNSName& qname, DNSName& /* unhashed */, DNSName& before, DNSName& after)
1139
{
2,705✔
1140
  // for(const auto& record: *records)
1141
  //   cerr<<record.qname<<"\t"<<makeHexDump(record.qname.toDNSString())<<endl;
1142

1143
  recordstorage_t::const_iterator iterBefore, iterAfter;
2,705✔
1144

1145
  iterBefore = iterAfter = records->upper_bound(qname.makeLowerCase());
2,705✔
1146

1147
  if (iterBefore != records->begin())
2,705!
1148
    --iterBefore;
2,705✔
1149
  while ((!iterBefore->auth && iterBefore->qtype != QType::NS) || !iterBefore->qtype)
3,234✔
1150
    --iterBefore;
529✔
1151
  before = iterBefore->qname;
2,705✔
1152

1153
  if (iterAfter == records->end()) {
2,705✔
1154
    iterAfter = records->begin();
375✔
1155
  }
375✔
1156
  else {
2,330✔
1157
    while ((!iterAfter->auth && iterAfter->qtype != QType::NS) || !iterAfter->qtype) {
3,494✔
1158
      ++iterAfter;
1,164✔
1159
      if (iterAfter == records->end()) {
1,164!
1160
        iterAfter = records->begin();
×
1161
        break;
×
1162
      }
×
1163
    }
1,164✔
1164
  }
2,330✔
1165
  after = iterAfter->qname;
2,705✔
1166

1167
  return true;
2,705✔
1168
}
2,705✔
1169

1170
// NOLINTNEXTLINE(readability-identifier-length)
1171
bool Bind2Backend::getBeforeAndAfterNamesAbsolute(domainid_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
1172
{
7,041✔
1173
  BB2DomainInfo bbd;
7,041✔
1174
  if (!safeGetBBDomainInfo(id, &bbd))
7,041!
1175
    return false;
×
1176

1177
  shared_ptr<const recordstorage_t> records = bbd.d_records.get();
7,041✔
1178
  if (!bbd.d_nsec3zone) {
7,041✔
1179
    return findBeforeAndAfterUnhashed(records, qname, unhashed, before, after);
2,705✔
1180
  }
2,705✔
1181
  else {
4,336✔
1182
    const auto& hashindex = boost::multi_index::get<NSEC3Tag>(*records);
4,336✔
1183

1184
    // for(auto iter = first; iter != hashindex.end(); iter++)
1185
    //  cerr<<iter->nsec3hash<<endl;
1186

1187
    auto first = hashindex.upper_bound("");
4,336✔
1188
    auto iter = hashindex.upper_bound(qname.toStringNoDot());
4,336✔
1189

1190
    if (iter == hashindex.end()) {
4,336✔
1191
      --iter;
362✔
1192
      before = DNSName(iter->nsec3hash);
362✔
1193
      after = DNSName(first->nsec3hash);
362✔
1194
    }
362✔
1195
    else {
3,974✔
1196
      after = DNSName(iter->nsec3hash);
3,974✔
1197
      if (iter != first)
3,974✔
1198
        --iter;
3,896✔
1199
      else
78✔
1200
        iter = --hashindex.end();
78✔
1201
      before = DNSName(iter->nsec3hash);
3,974✔
1202
    }
3,974✔
1203
    unhashed = iter->qname + bbd.d_name.operator const DNSName&();
4,336✔
1204

1205
    return true;
4,336✔
1206
  }
4,336✔
1207
}
7,041✔
1208

1209
void Bind2Backend::lookup(const QType& qtype, const DNSName& qname, domainid_t zoneId, DNSPacket* /* pkt_p */)
1210
{
4,093✔
1211
  d_handle.reset();
4,093✔
1212

1213
  static bool mustlog = ::arg().mustDo("query-logging");
4,093✔
1214

1215
  bool found = false;
4,093✔
1216
  ZoneName domain;
4,093✔
1217
  BB2DomainInfo bbd;
4,093✔
1218

1219
  if (mustlog)
4,093!
1220
    g_log << Logger::Warning << "Lookup for '" << qtype.toString() << "' of '" << qname << "' within zoneID " << zoneId << endl;
×
1221

1222
  if (zoneId != UnknownDomainID) {
4,093✔
1223
    if ((found = (safeGetBBDomainInfo(zoneId, &bbd) && qname.isPartOf(bbd.d_name)))) {
3,492!
1224
      domain = std::move(bbd.d_name);
3,492✔
1225
    }
3,492✔
1226
  }
3,492✔
1227
  else {
601✔
1228
    domain = ZoneName(qname);
601✔
1229
    do {
601✔
1230
      found = safeGetBBDomainInfo(domain, &bbd);
601✔
1231
    } while (!found && qtype != QType::SOA && domain.chopOff());
601!
1232
  }
601✔
1233

1234
  if (!found) {
4,093✔
1235
    if (mustlog)
20!
1236
      g_log << Logger::Warning << "Found no authoritative zone for '" << qname << "' and/or id " << zoneId << endl;
×
1237
    d_handle.d_list = false;
20✔
1238
    return;
20✔
1239
  }
20✔
1240

1241
  if (mustlog)
4,073!
1242
    g_log << Logger::Warning << "Found a zone '" << domain << "' (with id " << bbd.d_id << ") that might contain data " << endl;
×
1243

1244
  d_handle.id = bbd.d_id;
4,073✔
1245
  d_handle.qname = qname.makeRelative(domain); // strip domain name
4,073✔
1246
  d_handle.qtype = qtype;
4,073✔
1247
  d_handle.domain = std::move(domain);
4,073✔
1248

1249
  if (!bbd.current()) {
4,073✔
1250
    g_log << Logger::Warning << "Zone '" << d_handle.domain << "' (" << bbd.d_filename << ") needs reloading" << endl;
6✔
1251
    queueReloadAndStore(bbd.d_id);
6✔
1252
    if (!safeGetBBDomainInfo(d_handle.domain, &bbd))
6!
1253
      throw DBException("Zone '" + bbd.d_name.toLogString() + "' (" + bbd.d_filename + ") gone after reload"); // if we don't throw here, we crash for some reason
×
1254
  }
6✔
1255

1256
  if (!bbd.d_loaded) {
4,073✔
1257
    d_handle.reset();
216✔
1258
    throw DBException("Zone for '" + d_handle.domain.toLogString() + "' in '" + bbd.d_filename + "' not loaded (file missing, corrupt or primary dead)"); // fsck
216✔
1259
  }
216✔
1260

1261
  d_handle.d_records = bbd.d_records.get();
3,857✔
1262

1263
  if (d_handle.d_records->empty())
3,857!
1264
    DLOG(g_log << "Query with no results" << endl);
×
1265

1266
  d_handle.mustlog = mustlog;
3,857✔
1267

1268
  const auto& hashedidx = boost::multi_index::get<UnorderedNameTag>(*d_handle.d_records);
3,857✔
1269
  auto range = hashedidx.equal_range(d_handle.qname);
3,857✔
1270

1271
  d_handle.d_list = false;
3,857✔
1272
  d_handle.d_iter = range.first;
3,857✔
1273
  d_handle.d_end_iter = range.second;
3,857✔
1274
}
3,857✔
1275

1276
Bind2Backend::handle::handle()
1277
{
3,271✔
1278
  mustlog = false;
3,271✔
1279
}
3,271✔
1280

1281
bool Bind2Backend::get(DNSResourceRecord& r)
1282
{
197,767✔
1283
  if (!d_handle.d_records) {
197,767✔
1284
    if (d_handle.mustlog)
20!
1285
      g_log << Logger::Warning << "There were no answers" << endl;
×
1286
    return false;
20✔
1287
  }
20✔
1288

1289
  if (!d_handle.get(r)) {
197,747✔
1290
    if (d_handle.mustlog)
4,184!
1291
      g_log << Logger::Warning << "End of answers" << endl;
×
1292

1293
    d_handle.reset();
4,184✔
1294

1295
    return false;
4,184✔
1296
  }
4,184✔
1297
  if (d_handle.mustlog)
193,563!
1298
    g_log << Logger::Warning << "Returning: '" << r.qtype.toString() << "' of '" << r.qname << "', content: '" << r.content << "'" << endl;
×
1299
  return true;
193,563✔
1300
}
197,747✔
1301

1302
bool Bind2Backend::handle::get(DNSResourceRecord& r)
1303
{
197,747✔
1304
  if (d_list)
197,747✔
1305
    return get_list(r);
188,884✔
1306
  else
8,863✔
1307
    return get_normal(r);
8,863✔
1308
}
197,747✔
1309

1310
void Bind2Backend::handle::reset()
1311
{
8,820✔
1312
  d_records.reset();
8,820✔
1313
  qname.clear();
8,820✔
1314
  mustlog = false;
8,820✔
1315
}
8,820✔
1316

1317
//#define DLOG(x) x
1318
bool Bind2Backend::handle::get_normal(DNSResourceRecord& r)
1319
{
8,863✔
1320
  DLOG(g_log << "Bind2Backend get() was called for " << qtype.toString() << " record for '" << qname << "' - " << d_records->size() << " available in total!" << endl);
8,863✔
1321

1322
  if (d_iter == d_end_iter) {
8,863✔
1323
    return false;
3,857✔
1324
  }
3,857✔
1325

1326
  while (d_iter != d_end_iter && !(qtype.getCode() == QType::ANY || (d_iter)->qtype == qtype.getCode())) {
7,719!
1327
    DLOG(g_log << Logger::Warning << "Skipped " << qname << "/" << QType(d_iter->qtype).toString() << ": '" << d_iter->content << "'" << endl);
2,713✔
1328
    d_iter++;
2,713✔
1329
  }
2,713✔
1330
  if (d_iter == d_end_iter) {
5,006!
1331
    return false;
×
1332
  }
×
1333
  DLOG(g_log << "Bind2Backend get() returning a rr with a " << QType(d_iter->qtype).getCode() << endl);
5,006✔
1334

1335
  const DNSName& domainName(domain);
5,006✔
1336
  r.qname = qname.empty() ? domainName : (qname + domainName);
5,006!
1337
  r.domain_id = id;
5,006✔
1338
  r.content = (d_iter)->content;
5,006✔
1339
  //  r.domain_id=(d_iter)->domain_id;
1340
  r.qtype = (d_iter)->qtype;
5,006✔
1341
  r.ttl = (d_iter)->ttl;
5,006✔
1342

1343
  //if(!d_iter->auth && r.qtype.getCode() != QType::A && r.qtype.getCode()!=QType::AAAA && r.qtype.getCode() != QType::NS)
1344
  //  cerr<<"Warning! Unauth response for qtype "<< r.qtype.toString() << " for '"<<r.qname<<"'"<<endl;
1345
  r.auth = d_iter->auth;
5,006✔
1346

1347
  d_iter++;
5,006✔
1348

1349
  return true;
5,006✔
1350
}
5,006✔
1351

1352
bool Bind2Backend::list(const ZoneName& /* target */, domainid_t domainId, bool /* include_disabled */)
1353
{
327✔
1354
  BB2DomainInfo bbd;
327✔
1355

1356
  if (!safeGetBBDomainInfo(domainId, &bbd)) {
327!
1357
    return false;
×
1358
  }
×
1359

1360
  d_handle.reset();
327✔
1361
  DLOG(g_log << "Bind2Backend constructing handle for list of " << domainId << endl);
327✔
1362

1363
  if (!bbd.d_loaded) {
327!
1364
    throw PDNSException("zone was not loaded, perhaps because of: " + bbd.d_status);
×
1365
  }
×
1366

1367
  d_handle.d_records = bbd.d_records.get(); // give it a copy, which will stay around
327✔
1368
  d_handle.d_qname_iter = d_handle.d_records->begin();
327✔
1369
  d_handle.d_qname_end = d_handle.d_records->end(); // iter now points to a vector of pointers to vector<BBResourceRecords>
327✔
1370

1371
  d_handle.id = domainId;
327✔
1372
  d_handle.domain = bbd.d_name;
327✔
1373
  d_handle.d_list = true;
327✔
1374
  return true;
327✔
1375
}
327✔
1376

1377
bool Bind2Backend::handle::get_list(DNSResourceRecord& r)
1378
{
188,884✔
1379
  if (d_qname_iter != d_qname_end) {
188,884✔
1380
    const DNSName& domainName(domain);
188,557✔
1381
    r.qname = d_qname_iter->qname.empty() ? domainName : (d_qname_iter->qname + domainName);
188,557!
1382
    r.domain_id = id;
188,557✔
1383
    r.content = (d_qname_iter)->content;
188,557✔
1384
    r.qtype = (d_qname_iter)->qtype;
188,557✔
1385
    r.ttl = (d_qname_iter)->ttl;
188,557✔
1386
    r.auth = d_qname_iter->auth;
188,557✔
1387
    d_qname_iter++;
188,557✔
1388
    return true;
188,557✔
1389
  }
188,557✔
1390
  return false;
327✔
1391
}
188,884✔
1392

1393
bool Bind2Backend::autoPrimariesList(std::vector<AutoPrimary>& primaries)
1394
{
×
1395
  if (getArg("autoprimary-config").empty())
×
1396
    return false;
×
1397

1398
  ifstream c_if(getArg("autoprimaries"), std::ios::in);
×
1399
  if (!c_if) {
×
1400
    g_log << Logger::Error << "Unable to open autoprimaries file for read: " << stringerror() << endl;
×
1401
    return false;
×
1402
  }
×
1403

1404
  string line, sip, saccount;
×
1405
  while (getline(c_if, line)) {
×
1406
    std::istringstream ii(line);
×
1407
    ii >> sip;
×
1408
    if (!sip.empty()) {
×
1409
      ii >> saccount;
×
1410
      primaries.emplace_back(sip, "", saccount);
×
1411
    }
×
1412
  }
×
1413

1414
  c_if.close();
×
1415
  return true;
×
1416
}
×
1417

1418
bool Bind2Backend::autoPrimaryBackend(const string& ipAddress, const ZoneName& /* domain */, const vector<DNSResourceRecord>& /* nsset */, string* /* nameserver */, string* account, DNSBackend** backend)
1419
{
×
1420
  // Check whether we have a configfile available.
1421
  if (getArg("autoprimary-config").empty())
×
1422
    return false;
×
1423

1424
  ifstream c_if(getArg("autoprimaries").c_str(), std::ios::in); // this was nocreate?
×
1425
  if (!c_if) {
×
1426
    g_log << Logger::Error << "Unable to open autoprimaries file for read: " << stringerror() << endl;
×
1427
    return false;
×
1428
  }
×
1429

1430
  // Format:
1431
  // <ip> <accountname>
1432
  string line, sip, saccount;
×
1433
  while (getline(c_if, line)) {
×
1434
    std::istringstream ii(line);
×
1435
    ii >> sip;
×
1436
    if (sip == ipAddress) {
×
1437
      ii >> saccount;
×
1438
      break;
×
1439
    }
×
1440
  }
×
1441
  c_if.close();
×
1442

1443
  if (sip != ipAddress) { // ip not found in authorization list - reject
×
1444
    return false;
×
1445
  }
×
1446

1447
  // ip authorized as autoprimary - accept
1448
  *backend = this;
×
1449
  if (saccount.length() > 0)
×
1450
    *account = saccount.c_str();
×
1451

1452
  return true;
×
1453
}
×
1454

1455
BB2DomainInfo Bind2Backend::createDomainEntry(const ZoneName& domain, const string& filename)
1456
{
6✔
1457
  domainid_t newid = 1;
6✔
1458
  { // Find a free zone id nr.
6✔
1459
    auto state = s_state.read_lock();
6✔
1460
    if (!state->empty()) {
6!
1461
      // older (1.53) versions of boost have an expression for s_state.rbegin()
1462
      // that is ambiguous in C++17. So construct it explicitly
1463
      newid = boost::make_reverse_iterator(state->end())->d_id + 1;
6✔
1464
    }
6✔
1465
  }
6✔
1466

1467
  BB2DomainInfo bbd;
6✔
1468
  bbd.d_kind = DomainInfo::Native;
6✔
1469
  bbd.d_id = newid;
6✔
1470
  bbd.d_records = std::make_shared<recordstorage_t>();
6✔
1471
  bbd.d_name = domain;
6✔
1472
  bbd.setCheckInterval(getArgAsNum("check-interval"));
6✔
1473
  bbd.d_filename = filename;
6✔
1474

1475
  return bbd;
6✔
1476
}
6✔
1477

1478
bool Bind2Backend::createSecondaryDomain(const string& ipAddress, const ZoneName& domain, const string& /* nameserver */, const string& account)
1479
{
×
1480
  std::string domainname = domain.toStringNoDot();
×
1481

1482
  // Reject domain name if it embeds quotes; this may happen if 8bit-dns is
1483
  // used, and bind currently does not allow for character escapes in zone
1484
  // names.
1485
  if (domainname.find_first_of("\"") != std::string::npos) {
×
1486
    SLOG(g_log << Logger::Error << d_logprefix
×
1487
               << " Unable to accept autosecondary zone '" << domain
×
1488
               << "' from autoprimary " << ipAddress
×
1489
               << " due to unauthorized characters in domain name for bind configuration file"
×
1490
               << endl,
×
1491
         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)));
×
1492
    throw PDNSException("Unauthorized characters in domain name for bind configuration file");
×
1493
  }
×
1494

1495
  string filename = getArg("autoprimary-destdir") + '/';
×
1496
  if (domainname.empty()) {
×
1497
    filename.append("rootzone.");
×
1498
  }
×
1499
  else {
×
1500
    // Make sure the zone file name does not contain path separators.
1501
    filename.append(boost::replace_all_copy(domainname, "/", "\\047"));
×
1502
  }
×
1503

1504
  g_log << Logger::Warning << d_logprefix
×
1505
        << " Writing bind config zone statement for superslave zone '" << domain
×
1506
        << "' from autoprimary " << ipAddress << endl;
×
1507

1508
  {
×
1509
    auto lock = std::scoped_lock(s_autosecondary_config_lock);
×
1510

1511
    ofstream c_of(getArg("autoprimary-config").c_str(), std::ios::app);
×
1512
    if (!c_of) {
×
1513
      g_log << Logger::Error << "Unable to open autoprimary configfile for append: " << stringerror() << endl;
×
1514
      throw DBException("Unable to open autoprimary configfile for append: " + stringerror());
×
1515
    }
×
1516

1517
    c_of << endl;
×
1518
    c_of << "# AutoSecondary zone '" << domainname << "' (added: " << nowTime() << ") (account: " << account << ')' << endl;
×
1519
    c_of << "zone \"" << domainname << "\" {" << endl;
×
1520
    c_of << "\ttype secondary;" << endl;
×
1521
    c_of << "\tfile \"" << filename << "\";" << endl;
×
1522
    c_of << "\tprimaries { " << ipAddress << "; };" << endl;
×
1523
    c_of << "};" << endl;
×
1524
    c_of.close();
×
1525
  }
×
1526

1527
  BB2DomainInfo bbd = createDomainEntry(domain, filename);
×
1528
  bbd.d_kind = DomainInfo::Secondary;
×
1529
  bbd.d_primaries.emplace_back(ComboAddress(ipAddress, 53));
×
1530
  bbd.setCtime();
×
1531
  safePutBBDomainInfo(bbd);
×
1532

1533
  return true;
×
1534
}
×
1535

1536
bool Bind2Backend::searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result)
1537
{
23✔
1538
  SimpleMatch sm(pattern, true);
23✔
1539
  static bool mustlog = ::arg().mustDo("query-logging");
23✔
1540
  if (mustlog)
23!
1541
    g_log << Logger::Warning << "Search for pattern '" << pattern << "'" << endl;
×
1542

1543
  {
23✔
1544
    auto state = s_state.read_lock();
23✔
1545

1546
    for (const auto& i : *state) {
23!
1547
      BB2DomainInfo h;
×
1548
      if (!safeGetBBDomainInfo(i.d_id, &h)) {
×
1549
        continue;
×
1550
      }
×
1551

1552
      if (!h.d_loaded) {
×
1553
        continue;
×
1554
      }
×
1555

1556
      shared_ptr<const recordstorage_t> rhandle = h.d_records.get();
×
1557

1558
      for (recordstorage_t::const_iterator ri = rhandle->begin(); result.size() < maxResults && ri != rhandle->end(); ri++) {
×
1559
        const DNSName& domainName(i.d_name);
×
1560
        DNSName name = ri->qname.empty() ? domainName : (ri->qname + domainName);
×
1561
        if (sm.match(name) || sm.match(ri->content)) {
×
1562
          DNSResourceRecord r;
×
1563
          r.qname = std::move(name);
×
1564
          r.domain_id = i.d_id;
×
1565
          r.content = ri->content;
×
1566
          r.qtype = ri->qtype;
×
1567
          r.ttl = ri->ttl;
×
1568
          r.auth = ri->auth;
×
1569
          result.push_back(std::move(r));
×
1570
        }
×
1571
      }
×
1572
    }
×
1573
  }
23✔
1574

1575
  return true;
23✔
1576
}
23✔
1577

1578
class Bind2Factory : public BackendFactory
1579
{
1580
public:
1581
  Bind2Factory() :
1582
    BackendFactory("bind") {}
5,380✔
1583

1584
  void declareArguments(const string& suffix = "") override
1585
  {
544✔
1586
    declare(suffix, "ignore-broken-records", "Ignore records that are out-of-bound for the zone.", "no");
544✔
1587
    declare(suffix, "config", "Location of named.conf", "");
544✔
1588
    declare(suffix, "check-interval", "Interval for zonefile changes", "0");
544✔
1589
    declare(suffix, "autoprimary-config", "Location of (part of) named.conf where pdns can write zone-statements to", "");
544✔
1590
    declare(suffix, "autoprimaries", "List of IP-addresses of autoprimaries", "");
544✔
1591
    declare(suffix, "autoprimary-destdir", "Destination directory for newly added secondary zones", ::arg()["config-dir"]);
544✔
1592
    declare(suffix, "dnssec-db", "Filename to store & access our DNSSEC metadatabase, empty for none", "");
544✔
1593
    declare(suffix, "dnssec-db-journal-mode", "SQLite3 journal mode", "WAL");
544✔
1594
    declare(suffix, "hybrid", "Store DNSSEC metadata in other backend", "no");
544✔
1595
  }
544✔
1596

1597
  DNSBackend* make(const string& suffix = "") override
1598
  {
2,382✔
1599
    assertEmptySuffix(suffix);
2,382✔
1600
    return new Bind2Backend(suffix);
2,382✔
1601
  }
2,382✔
1602

1603
  DNSBackend* makeMetadataOnly(const string& suffix = "") override
1604
  {
883✔
1605
    assertEmptySuffix(suffix);
883✔
1606
    return new Bind2Backend(suffix, false);
883✔
1607
  }
883✔
1608

1609
private:
1610
  void assertEmptySuffix(const string& suffix)
1611
  {
3,265✔
1612
    if (!suffix.empty())
3,265!
1613
      throw PDNSException("launch= suffixes are not supported on the bindbackend");
×
1614
  }
3,265✔
1615
};
1616

1617
//! Magic class that is activated when the dynamic library is loaded
1618
class Bind2Loader
1619
{
1620
public:
1621
  Bind2Loader()
1622
  {
5,380✔
1623
    BackendMakers().report(std::make_unique<Bind2Factory>());
5,380✔
1624
    g_log << Logger::Info << "[bind2backend] This is the bind backend version " << VERSION
5,380✔
1625
#ifndef REPRODUCIBLE
5,380✔
1626
          << " (" __DATE__ " " __TIME__ ")"
5,380✔
1627
#endif
5,380✔
1628
#ifdef HAVE_SQLITE3
5,380✔
1629
          << " (with bind-dnssec-db support)"
5,380✔
1630
#endif
5,380✔
1631
          << " reporting" << endl;
5,380✔
1632
  }
5,380✔
1633
};
1634
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