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

PowerDNS / pdns / 24668902038

20 Apr 2026 01:21PM UTC coverage: 71.017% (+0.08%) from 70.938%
24668902038

Pull #16725

github

web-flow
Merge 37753fa0f into e7536fdc5
Pull Request #16725: auth lmdb: split domains table

45756 of 80560 branches covered (56.8%)

Branch coverage included in aggregate %.

27 of 84 new or added lines in 1 file covered. (32.14%)

5556 existing lines in 66 files now uncovered.

131473 of 168998 relevant lines covered (77.8%)

6552358.49 hits per line

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

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

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

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

104
  if (!d_checkinterval)
4,148!
105
    return true;
4,148✔
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()
128
{
×
129
  struct stat buf;
×
130

131
  for (auto& fileinfo : d_fileinfo) {
×
132
    if (stat(fileinfo.first.c_str(), &buf) < 0) {
×
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
{
11,237✔
142
  auto state = s_state.read_lock();
11,237✔
143
  state_t::const_iterator iter = state->find(id);
11,237✔
144
  if (iter == state->end()) {
11,237!
145
    return false;
×
146
  }
×
147
  *bbd = *iter;
11,237✔
148
  return true;
11,237✔
149
}
11,237✔
150

151
bool Bind2Backend::safeGetBBDomainInfo(const ZoneName& name, BB2DomainInfo* bbd)
152
{
4,367✔
153
  auto state = s_state.read_lock();
4,367✔
154
  const auto& nameindex = boost::multi_index::get<NameTag>(*state);
4,367✔
155
  auto iter = nameindex.find(name);
4,367✔
156
  if (iter == nameindex.end()) {
4,367✔
157
    return false;
3,320✔
158
  }
3,320✔
159
  *bbd = *iter;
1,047✔
160
  return true;
1,047✔
161
}
4,367✔
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
  }
×
173
  nameindex.erase(iter);
×
174
  return true;
×
175
}
×
176

177
void Bind2Backend::safePutBBDomainInfo(const BB2DomainInfo& bbd)
178
{
2,815✔
179
  auto state = s_state.write_lock();
2,815✔
180
  replacing_insert(*state, bbd);
2,815✔
181
}
2,815✔
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
{
72✔
196
  BB2DomainInfo bbd;
72✔
197
  if (safeGetBBDomainInfo(domain_id, &bbd)) {
72!
198
    bbd.d_lastcheck = lastcheck;
72✔
199
    safePutBBDomainInfo(bbd);
72✔
200
  }
72✔
201
}
72✔
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
{
72✔
210
  Bind2Backend::setLastCheck(domain_id, time(nullptr));
72✔
211
}
72✔
212

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

225
  d_transaction_id = domainId;
72✔
226
  d_transaction_qname = qname;
72✔
227
  BB2DomainInfo bbd;
72✔
228
  if (safeGetBBDomainInfo(domainId, &bbd)) {
72!
229
    d_transaction_tmpname = bbd.main_filename() + "XXXXXX";
72✔
230
    int fd = mkstemp(&d_transaction_tmpname.at(0));
72✔
231
    if (fd == -1) {
72!
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);
72✔
236
    if (!*d_of) {
72!
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);
72✔
244
    fd = -1;
72✔
245

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

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

255
bool Bind2Backend::commitTransaction()
256
{
221✔
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) {
221✔
260
    return false;
149✔
261
  }
149✔
262
  d_of.reset();
72✔
263

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

272
  d_transaction_id = UnknownDomainID;
72✔
273

274
  return true;
72✔
275
}
72✔
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
{
416✔
293
  if (domain.size() <= suffix.size()) {
416✔
294
    return false;
52✔
295
  }
52✔
296

297
  string::size_type dpos = domain.size() - suffix.size() - 1;
364✔
298
  if (domain[dpos++] != '.') {
364✔
299
    return false;
88✔
300
  }
88✔
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};
276✔
305
  while (dpos >= 2 + slashes && domain.at(dpos - 2 - slashes) == '\\') {
276!
UNCOV
306
    ++slashes;
×
UNCOV
307
  }
×
308
  if ((slashes % 2) != 0) {
276!
UNCOV
309
    return false;
×
UNCOV
310
  }
×
311

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

319
  return true;
264✔
320
}
276✔
321

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

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

340
bool Bind2Backend::feedRecord(const DNSResourceRecord& rr, const DNSName& /* ordername */, bool /* ordernameIsNSEC3 */)
341
{
202,805✔
342
  if (d_transaction_id == UnknownDomainID) {
202,805!
UNCOV
343
    throw DBException("Bind2Backend::feedRecord() called outside of transaction");
×
UNCOV
344
  }
×
345

346
  string qname;
202,805✔
347
  if (d_transaction_qname.empty()) {
202,805!
348
    qname = rr.qname.toString();
×
UNCOV
349
  }
×
350
  else if (rr.qname.isPartOf(d_transaction_qname)) {
202,805!
351
    if (rr.qname == d_transaction_qname.operator const DNSName&()) {
202,805✔
352
      qname = "@";
633✔
353
    }
633✔
354
    else {
202,172✔
355
      DNSName relName = rr.qname.makeRelative(d_transaction_qname);
202,172✔
356
      qname = relName.toStringNoDot();
202,172✔
357
    }
202,172✔
358
  }
202,805✔
359
  else {
×
UNCOV
360
    throw DBException("out-of-zone data '" + rr.qname.toLogString() + "' during AXFR of zone '" + d_transaction_qname.toLogString() + "'");
×
UNCOV
361
  }
×
362

363
  shared_ptr<DNSRecordContent> drc(DNSRecordContent::make(rr.qtype.getCode(), QClass::IN, rr.content));
202,805✔
364
  string content = drc->getZoneRepresentation();
202,805✔
365

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

383
void Bind2Backend::getUpdatedPrimaries(vector<DomainInfo>& changedDomains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
UNCOV
384
{
×
UNCOV
385
  vector<DomainInfo> consider;
×
UNCOV
386
  {
×
UNCOV
387
    auto state = s_state.read_lock();
×
388

UNCOV
389
    for (const auto& i : *state) {
×
UNCOV
390
      if (i.d_kind != DomainInfo::Primary && this->alsoNotify.empty() && i.d_also_notify.empty())
×
UNCOV
391
        continue;
×
392

UNCOV
393
      DomainInfo di;
×
UNCOV
394
      di.id = i.d_id;
×
UNCOV
395
      di.zone = i.d_name;
×
UNCOV
396
      di.last_check = i.d_lastcheck;
×
UNCOV
397
      di.notified_serial = i.d_lastnotified;
×
UNCOV
398
      di.backend = this;
×
399
      di.kind = DomainInfo::Primary;
×
400
      consider.push_back(std::move(di));
×
401
    }
×
402
  }
×
403

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

427
void Bind2Backend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool /* include_disabled */)
428
{
98✔
429
  SOAData soadata;
98✔
430

431
  // prevent deadlock by using getSOA() later on
432
  {
98✔
433
    auto state = s_state.read_lock();
98✔
434
    domains->reserve(state->size());
98✔
435

436
    for (const auto& i : *state) {
258✔
437
      DomainInfo di;
185✔
438
      di.id = i.d_id;
185✔
439
      di.zone = i.d_name;
185✔
440
      di.last_check = i.d_lastcheck;
185✔
441
      di.kind = i.d_kind;
185✔
442
      di.primaries = i.d_primaries;
185✔
443
      di.backend = this;
185✔
444
      domains->push_back(std::move(di));
185✔
445
    };
185✔
446
  }
98✔
447

448
  if (getSerial) {
98✔
449
    for (DomainInfo& di : *domains) {
1,498✔
450
      // do not corrupt di if domain supplied by another backend.
451
      if (di.backend != this)
1,498!
452
        continue;
1,498✔
UNCOV
453
      try {
×
UNCOV
454
        this->getSOA(di.zone, di.id, soadata);
×
UNCOV
455
      }
×
UNCOV
456
      catch (...) {
×
UNCOV
457
        continue;
×
UNCOV
458
      }
×
UNCOV
459
      di.serial = soadata.serial;
×
UNCOV
460
    }
×
461
  }
42✔
462
}
98✔
463

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

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

501
bool Bind2Backend::getDomainInfo(const ZoneName& domain, DomainInfo& info, bool getSerial)
502
{
1,073✔
503
  BB2DomainInfo bbd;
1,073✔
504
  if (!safeGetBBDomainInfo(domain, &bbd))
1,073✔
505
    return false;
628✔
506

507
  info.id = bbd.d_id;
445✔
508
  info.zone = domain;
445✔
509
  info.primaries = bbd.d_primaries;
445✔
510
  info.last_check = bbd.d_lastcheck;
445✔
511
  info.backend = this;
445✔
512
  info.kind = bbd.d_kind;
445✔
513
  info.serial = 0;
445✔
514
  if (getSerial) {
445✔
515
    try {
135✔
516
      SOAData sd;
135✔
517
      sd.serial = 0;
135✔
518

519
      getSOA(bbd.d_name, bbd.d_id, sd); // we might not *have* a SOA yet
135✔
520
      info.serial = sd.serial;
135✔
521
    }
135✔
522
    catch (...) {
135✔
523
    }
72✔
524
  }
135✔
525

526
  return true;
445✔
527
}
445✔
528

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

553
// only parses, does NOT add to s_state!
554
void Bind2Backend::parseZoneFile(BB2DomainInfo* bbd)
555
{
2,737✔
556
  NSEC3PARAMRecordContent ns3pr;
2,737✔
557
  bool nsec3zone = false;
2,737✔
558
  if (d_hybrid) {
2,737!
559
    DNSSECKeeper dk(d_slog);
×
560
    nsec3zone = dk.getNSEC3PARAM(bbd->d_name, &ns3pr);
×
561
  }
×
562
  else
2,737✔
563
    nsec3zone = getNSEC3PARAMuncached(bbd->d_name, &ns3pr);
2,737✔
564

565
  auto records = std::make_shared<recordstorage_t>();
2,737✔
566
  ZoneParserTNG zpt(bbd->main_filename(), bbd->d_name, s_binddirectory, d_upgradeContent);
2,737✔
567
  zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
2,737✔
568
  zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
2,737✔
569
  DNSResourceRecord rr;
2,737✔
570
  string hashed;
2,737✔
571
  while (zpt.get(rr)) {
3,277,596✔
572
    if (rr.qtype.getCode() == QType::NSEC || rr.qtype.getCode() == QType::NSEC3 || rr.qtype.getCode() == QType::NSEC3PARAM)
3,274,859!
UNCOV
573
      continue; // we synthesise NSECs on demand
×
574

575
    insertRecord(records, bbd->d_name, rr.qname, rr.qtype, rr.content, rr.ttl, "");
3,274,859✔
576
  }
3,274,859✔
577
  fixupOrderAndAuth(records, bbd->d_name, nsec3zone, ns3pr);
2,737✔
578
  doEmptyNonTerminals(records, bbd->d_name, nsec3zone, ns3pr);
2,737✔
579
  bbd->d_fileinfo = zpt.getFileset();
2,737✔
580
  bbd->d_loaded = true;
2,737✔
581
  bbd->d_checknow = false;
2,737✔
582
  bbd->d_status = "parsed into memory at " + nowTime();
2,737✔
583
  bbd->d_records = LookButDontTouch<recordstorage_t>(std::move(records));
2,737✔
584
  bbd->d_nsec3zone = nsec3zone;
2,737✔
585
  bbd->d_nsec3param = std::move(ns3pr);
2,737✔
586
}
2,737✔
587

588
/** THIS IS AN INTERNAL FUNCTION! It does moadnsparser prio impedance matching
589
    Much of the complication is due to the efforts to benefit from std::string reference counting copy on write semantics */
590
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)
591
{
3,279,630✔
592
  Bind2DNSRecord bdr;
3,279,630✔
593
  bdr.qname = qname;
3,279,630✔
594

595
  if (zoneName.empty())
3,279,630!
UNCOV
596
    ;
×
597
  else if (bdr.qname.isPartOf(zoneName))
3,279,630✔
598
    bdr.qname.makeUsRelative(zoneName);
3,279,328✔
599
  else {
302✔
600
    string msg = "Trying to insert non-zone data, name='" + bdr.qname.toLogString() + "', qtype=" + qtype.toString() + ", zone='" + zoneName.toLogString() + "'";
302✔
601
    if (s_ignore_broken_records) {
302!
602
      SLOG(g_log << Logger::Warning << msg << " ignored" << endl,
302!
603
           d_slog->info(Logr::Warning, "Non-zone data record ignored", "zone", Logging::Loggable(zoneName), "name", Logging::Loggable(bdr.qname), "qtype", Logging::Loggable(qtype)));
302✔
604
      return;
302✔
605
    }
302✔
UNCOV
606
    throw PDNSException(std::move(msg));
×
607
  }
302✔
608

609
  //  bdr.qname.swap(bdr.qname);
610

611
  if (!records->empty() && bdr.qname == boost::prior(records->end())->qname)
3,279,328✔
612
    bdr.qname = boost::prior(records->end())->qname;
8,861✔
613

614
  bdr.qname = bdr.qname;
3,279,328✔
615
  bdr.qtype = qtype.getCode();
3,279,328✔
616
  bdr.content = content;
3,279,328✔
617
  bdr.nsec3hash = hashed;
3,279,328✔
618

619
  if (auth != nullptr) // Set auth on empty non-terminals
3,279,328✔
620
    bdr.auth = *auth;
4,771✔
621
  else
3,274,557✔
622
    bdr.auth = true;
3,274,557✔
623

624
  bdr.ttl = ttl;
3,279,328✔
625
  records->insert(std::move(bdr));
3,279,328✔
626
}
3,279,328✔
627

628
string Bind2Backend::DLReloadNowHandler(const vector<string>& parts, Utility::pid_t /* ppid */, Logr::log_t /* slog */)
UNCOV
629
{
×
UNCOV
630
  ostringstream ret;
×
631

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

653
string Bind2Backend::DLDomStatusHandler(const vector<string>& parts, Utility::pid_t /* ppid */, Logr::log_t /* slog */)
654
{
27✔
655
  ostringstream ret;
27✔
656

657
  if (parts.size() > 1) {
27!
658
    for (auto i = parts.begin() + 1; i < parts.end(); ++i) {
×
659
      BB2DomainInfo bbd;
×
660
      if (safeGetBBDomainInfo(ZoneName(*i), &bbd)) {
×
661
        ret << *i << ": " << (bbd.d_loaded ? "" : "[rejected]") << "\t" << bbd.d_status << "\n";
×
662
      }
×
663
      else {
×
664
        ret << *i << " no such domain\n";
×
665
      }
×
666
    }
×
UNCOV
667
  }
×
668
  else {
27✔
669
    auto state = s_state.read_lock();
27✔
670
    for (const auto& i : *state) {
235✔
671
      ret << i.d_name << ": " << (i.d_loaded ? "" : "[rejected]") << "\t" << i.d_status << "\n";
235✔
672
    }
235✔
673
  }
27✔
674

675
  if (ret.str().empty())
27!
676
    ret << "no domains passed";
×
677

678
  return ret.str();
27✔
679
}
27✔
680

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

715
string Bind2Backend::DLDomExtendedStatusHandler(const vector<string>& parts, Utility::pid_t /* ppid */, Logr::log_t /* slog */)
716
{
×
717
  ostringstream ret;
×
718

719
  if (parts.size() > 1) {
×
720
    for (auto i = parts.begin() + 1; i < parts.end(); ++i) {
×
721
      BB2DomainInfo bbd;
×
722
      if (safeGetBBDomainInfo(ZoneName(*i), &bbd)) {
×
723
        printDomainExtendedStatus(ret, bbd);
×
724
      }
×
725
      else {
×
726
        ret << *i << " no such domain" << std::endl;
×
727
      }
×
728
    }
×
UNCOV
729
  }
×
UNCOV
730
  else {
×
731
    auto rstate = s_state.read_lock();
×
732
    for (const auto& state : *rstate) {
×
UNCOV
733
      printDomainExtendedStatus(ret, state);
×
734
    }
×
735
  }
×
736

737
  if (ret.str().empty()) {
×
738
    ret << "no domains passed" << std::endl;
×
739
  }
×
740

741
  return ret.str();
×
742
}
×
743

744
string Bind2Backend::DLListRejectsHandler(const vector<string>& /* parts */, Utility::pid_t /* ppid */, Logr::log_t /* slog */)
745
{
×
746
  ostringstream ret;
×
747
  auto rstate = s_state.read_lock();
×
748
  for (const auto& i : *rstate) {
×
749
    if (!i.d_loaded)
×
750
      ret << i.d_name << "\t" << i.d_status << endl;
×
UNCOV
751
  }
×
752
  return ret.str();
×
753
}
×
754

755
string Bind2Backend::DLAddDomainHandler(const vector<string>& parts, Utility::pid_t /* ppid */, Logr::log_t /* slog */)
756
{
12✔
757
  if (parts.size() < 3)
12!
UNCOV
758
    return "ERROR: Domain name and zone filename are required";
×
759

760
  ZoneName domainname(parts[1]);
12✔
761
  const string& filename = parts[2];
12✔
762
  BB2DomainInfo bbd;
12✔
763
  if (safeGetBBDomainInfo(domainname, &bbd))
12✔
764
    return "Already loaded";
6✔
765

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

769
  struct stat buf;
6✔
770
  if (stat(filename.c_str(), &buf) != 0)
6!
UNCOV
771
    return "Unable to load zone " + domainname.toLogString() + " from " + filename + ": " + strerror(errno);
×
772

773
  Bind2Backend bb2; // createdomainentry needs access to our configuration
6✔
774
  bbd = bb2.createDomainEntry(domainname);
6✔
775
  bbd.d_fileinfo.emplace_back(std::make_pair(filename, buf.st_ctime));
6✔
776
  bbd.d_checknow = true;
6✔
777
  bbd.d_loaded = true;
6✔
778
  bbd.d_lastcheck = 0;
6✔
779
  bbd.d_status = "parsing into memory";
6✔
780

781
  safePutBBDomainInfo(bbd);
6✔
782

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

785
  SLOG(g_log << Logger::Warning << "Zone " << domainname << " loaded" << endl,
6!
786
       bb2.d_slog->info(Logr::Info, "Zone loaded", "zone", Logging::Loggable(domainname), "file", Logging::Loggable(filename)));
6✔
787
  return "Loaded zone " + domainname.toLogString() + " from " + filename;
6✔
788
}
6✔
789

790
Bind2Backend::Bind2Backend(const string& suffix, bool loadZones)
791
{
3,430✔
792
  d_getAllDomainMetadataQuery_stmt = nullptr;
3,430✔
793
  d_getDomainMetadataQuery_stmt = nullptr;
3,430✔
794
  d_deleteDomainMetadataQuery_stmt = nullptr;
3,430✔
795
  d_insertDomainMetadataQuery_stmt = nullptr;
3,430✔
796
  d_getDomainKeysQuery_stmt = nullptr;
3,430✔
797
  d_deleteDomainKeyQuery_stmt = nullptr;
3,430✔
798
  d_insertDomainKeyQuery_stmt = nullptr;
3,430✔
799
  d_GetLastInsertedKeyIdQuery_stmt = nullptr;
3,430✔
800
  d_activateDomainKeyQuery_stmt = nullptr;
3,430✔
801
  d_deactivateDomainKeyQuery_stmt = nullptr;
3,430✔
802
  d_getTSIGKeyQuery_stmt = nullptr;
3,430✔
803
  d_setTSIGKeyQuery_stmt = nullptr;
3,430✔
804
  d_deleteTSIGKeyQuery_stmt = nullptr;
3,430✔
805
  d_getTSIGKeysQuery_stmt = nullptr;
3,430✔
806

807
  setArgPrefix("bind" + suffix);
3,430✔
808
  d_logprefix = "[bind" + suffix + "backend]";
3,430✔
809
  if (g_slogStructured) {
3,430!
UNCOV
810
    d_slog = g_slog->withName("bind" + suffix);
×
UNCOV
811
    d_handle.setSLog(d_slog);
×
UNCOV
812
  }
×
813
  d_hybrid = mustDo("hybrid");
3,430✔
814
  if (d_hybrid && g_zoneCache.isEnabled()) {
3,430!
UNCOV
815
    throw PDNSException("bind-hybrid and the zone cache currently interoperate badly. Please disable the zone cache or stop using bind-hybrid");
×
UNCOV
816
  }
×
817

818
  d_transaction_id = UnknownDomainID;
3,430✔
819
  s_ignore_broken_records = mustDo("ignore-broken-records");
3,430✔
820
  d_upgradeContent = ::arg().mustDo("upgrade-unknown-types");
3,430✔
821

822
  if (!loadZones && d_hybrid)
3,430!
UNCOV
823
    return;
×
824

825
  auto lock = std::scoped_lock(s_startup_lock);
3,430✔
826

827
  setupDNSSEC();
3,430✔
828
  if (s_first == 0) {
3,430✔
829
    return;
2,670✔
830
  }
2,670✔
831

832
  if (loadZones) {
760✔
833
    loadConfig();
311✔
834
    s_first = 0;
311✔
835
  }
311✔
836

837
  DynListener::registerFunc("BIND-ADD-ZONE", &DLAddDomainHandler, "bindbackend: add zone", "<domain> <filename>");
760✔
838
  DynListener::registerFunc("BIND-DOMAIN-EXTENDED-STATUS", &DLDomExtendedStatusHandler, "bindbackend: list the extended status of all domains", "[domains]");
760✔
839
  DynListener::registerFunc("BIND-DOMAIN-STATUS", &DLDomStatusHandler, "bindbackend: list status of all domains", "[domains]");
760✔
840
  DynListener::registerFunc("BIND-LIST-REJECTS", &DLListRejectsHandler, "bindbackend: list rejected domains");
760✔
841
  DynListener::registerFunc("BIND-RELOAD-NOW", &DLReloadNowHandler, "bindbackend: reload domains", "<domains>");
760✔
842
}
760✔
843

844
Bind2Backend::~Bind2Backend()
845
{
3,306✔
846
  freeStatements();
3,306✔
847
} // deallocate statements
3,306✔
848

849
void Bind2Backend::rediscover(string* status)
UNCOV
850
{
×
UNCOV
851
  loadConfig(status);
×
UNCOV
852
}
×
853

854
void Bind2Backend::reload()
UNCOV
855
{
×
UNCOV
856
  auto state = s_state.write_lock();
×
UNCOV
857
  for (const auto& i : *state) {
×
UNCOV
858
    i.d_checknow = true; // being a bit cheeky here, don't index state_t on this (mutable)
×
UNCOV
859
  }
×
UNCOV
860
}
×
861

862
void Bind2Backend::fixupOrderAndAuth(std::shared_ptr<recordstorage_t>& records, const ZoneName& zoneName, bool nsec3zone, const NSEC3PARAMRecordContent& ns3pr)
863
{
2,665✔
864
  bool skip;
2,665✔
865
  DNSName shorter;
2,665✔
866
  set<DNSName> nssets, dssets;
2,665✔
867

868
  for (const auto& bdr : *records) {
3,274,557✔
869
    if (!bdr.qname.isRoot() && bdr.qtype == QType::NS)
3,274,557✔
870
      nssets.insert(bdr.qname);
3,010✔
871
    else if (bdr.qtype == QType::DS)
3,271,547✔
872
      dssets.insert(bdr.qname);
499✔
873
  }
3,274,557✔
874

875
  for (auto iter = records->begin(); iter != records->end(); iter++) {
3,277,222✔
876
    skip = false;
3,274,557✔
877
    shorter = iter->qname;
3,274,557✔
878

879
    if (!iter->qname.isRoot() && shorter.chopOff() && !iter->qname.isRoot()) {
3,274,557!
880
      do {
3,273,483✔
881
        if (nssets.count(shorter) != 0u) {
3,273,483✔
882
          skip = true;
1,359✔
883
          break;
1,359✔
884
        }
1,359✔
885
      } while (shorter.chopOff() && !iter->qname.isRoot());
3,273,483!
886
    }
3,263,426✔
887

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

890
    if (!skip && nsec3zone && iter->qtype != QType::RRSIG && (iter->auth || (iter->qtype == QType::NS && (ns3pr.d_flags == 0u)) || (dssets.count(iter->qname) != 0u))) {
3,274,557✔
891
      Bind2DNSRecord bdr = *iter;
1,439,658✔
892
      bdr.nsec3hash = toBase32Hex(hashQNameWithSalt(ns3pr, bdr.qname + zoneName.operator const DNSName&()));
1,439,658✔
893
      records->replace(iter, bdr);
1,439,658✔
894
    }
1,439,658✔
895

896
    // cerr<<iter->qname<<"\t"<<QType(iter->qtype).toString()<<"\t"<<iter->nsec3hash<<"\t"<<iter->auth<<endl;
897
  }
3,274,557✔
898
}
2,665✔
899

900
void Bind2Backend::doEmptyNonTerminals(std::shared_ptr<recordstorage_t>& records, const ZoneName& zoneName, bool nsec3zone, const NSEC3PARAMRecordContent& ns3pr)
901
{
2,665✔
902
  bool auth = false;
2,665✔
903
  DNSName shorter;
2,665✔
904
  std::unordered_set<DNSName> qnames;
2,665✔
905
  std::unordered_map<DNSName, bool> nonterm;
2,665✔
906

907
  uint32_t maxent = ::arg().asNum("max-ent-entries");
2,665✔
908

909
  for (const auto& bdr : *records)
2,665✔
910
    qnames.insert(bdr.qname);
3,274,557✔
911

912
  for (const auto& bdr : *records) {
3,274,557✔
913

914
    if (!bdr.auth && bdr.qtype == QType::NS)
3,274,557✔
915
      auth = (!nsec3zone || (ns3pr.d_flags == 0u));
3,010✔
916
    else
3,271,547✔
917
      auth = bdr.auth;
3,271,547✔
918

919
    shorter = bdr.qname;
3,274,557✔
920
    while (shorter.chopOff()) {
6,549,399✔
921
      if (qnames.count(shorter) == 0u) {
3,274,842✔
922
        if (!(maxent)) {
9,891!
UNCOV
923
          SLOG(g_log << Logger::Error << "Zone '" << zoneName << "' has too many empty non terminals." << endl,
×
UNCOV
924
               d_slog->info(Logr::Error, "Zone has too many empty non terminals.", "zone", Logging::Loggable(zoneName)));
×
UNCOV
925
          return;
×
UNCOV
926
        }
×
927

928
        if (nonterm.count(shorter) == 0u) {
9,891✔
929
          nonterm.emplace(shorter, auth);
4,771✔
930
          --maxent;
4,771✔
931
        }
4,771✔
932
        else if (auth)
5,120✔
933
          nonterm[shorter] = true;
5,024✔
934
      }
9,891✔
935
    }
3,274,842✔
936
  }
3,274,557✔
937

938
  DNSResourceRecord rr;
2,665✔
939
  rr.qtype = "#0";
2,665✔
940
  rr.content = "";
2,665✔
941
  rr.ttl = 0;
2,665✔
942
  for (auto& nt : nonterm) {
4,772✔
943
    string hashed;
4,771✔
944
    rr.qname = nt.first + zoneName.operator const DNSName&();
4,771✔
945
    if (nsec3zone && nt.second)
4,771✔
946
      hashed = toBase32Hex(hashQNameWithSalt(ns3pr, rr.qname));
1,853✔
947
    insertRecord(records, zoneName, rr.qname, rr.qtype, rr.content, rr.ttl, hashed, &nt.second);
4,771✔
948

949
    // cerr<<rr.qname<<"\t"<<rr.qtype.toString()<<"\t"<<hashed<<"\t"<<nt.second<<endl;
950
  }
4,771✔
951
}
2,665✔
952

953
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
954
{
311✔
955
  static domainid_t domain_id = 1;
311✔
956

957
  if (!getArg("config").empty()) {
311!
958
    BindParser BP;
311✔
959
    try {
311✔
960
      BP.parse(getArg("config"));
311✔
961
    }
311✔
962
    catch (PDNSException& ae) {
311✔
UNCOV
963
      SLOG(g_log << Logger::Error << "Error parsing bind configuration: " << ae.reason << endl,
×
UNCOV
964
           d_slog->error(Logr::Error, ae.reason, "Error parsing bind configuration"));
×
UNCOV
965
      throw;
×
UNCOV
966
    }
×
967

968
    vector<BindDomainInfo> domains = BP.getDomains();
311✔
969
    this->alsoNotify = BP.getAlsoNotify();
311✔
970

971
    s_binddirectory = BP.getDirectory();
311✔
972
    //    ZP.setDirectory(d_binddirectory);
973

974
    SLOG(g_log << Logger::Warning << d_logprefix << " Parsing " << domains.size() << " domain(s), will report when done" << endl,
311!
975
         d_slog->info(Logr::Info, "Parsing " + std::to_string(domains.size()) + " domain(s), will report when done"));
311✔
976

977
    set<ZoneName> oldnames;
311✔
978
    set<ZoneName> newnames;
311✔
979
    {
311✔
980
      auto state = s_state.read_lock();
311✔
981
      for (const BB2DomainInfo& bbd : *state) {
311!
UNCOV
982
        oldnames.insert(bbd.d_name);
×
UNCOV
983
      }
×
984
    }
311✔
985
    int rejected = 0;
311✔
986
    int newdomains = 0;
311✔
987

988
    struct stat st;
311✔
989

990
    for (auto& domain : domains) {
2,791✔
991
      if (stat(domain.filename.c_str(), &st) == 0) {
2,659✔
992
        domain.d_dev = st.st_dev;
2,587✔
993
        domain.d_ino = st.st_ino;
2,587✔
994
      }
2,587✔
995
    }
2,659✔
996

997
    sort(domains.begin(), domains.end()); // put stuff in inode order
311✔
998
    for (const auto& domain : domains) {
2,791✔
999
      if (!(domain.hadFileDirective)) {
2,659!
UNCOV
1000
        SLOG(g_log << Logger::Warning << d_logprefix << " Zone '" << domain.name << "' has no 'file' directive set in " << getArg("config") << endl,
×
UNCOV
1001
             d_slog->info(Logr::Warning, "Zone has no 'file' directive set", "zone", Logging::Loggable(domain.name), "filename", Logging::Loggable(getArg("config"))));
×
UNCOV
1002
        rejected++;
×
UNCOV
1003
        continue;
×
UNCOV
1004
      }
×
1005

1006
      if (domain.type.empty()) {
2,659!
UNCOV
1007
        SLOG(g_log << Logger::Notice << d_logprefix << " Zone '" << domain.name << "' has no type specified, assuming 'native'" << endl,
×
UNCOV
1008
             d_slog->info(Logr::Notice, "Zone has no type specified, assuming 'native'", "zone", Logging::Loggable(domain.name)));
×
UNCOV
1009
      }
×
1010
      if (domain.type != "primary" && domain.type != "secondary" && domain.type != "native" && !domain.type.empty() && domain.type != "master" && domain.type != "slave") {
2,659!
UNCOV
1011
        SLOG(g_log << Logger::Warning << d_logprefix << " Warning! Skipping zone '" << domain.name << "' because type '" << domain.type << "' is invalid" << endl,
×
UNCOV
1012
             d_slog->info(Logr::Warning, "Skipping zone because type is invalid", "zone", Logging::Loggable(domain.name), "type", Logging::Loggable(domain.type)));
×
UNCOV
1013
        rejected++;
×
UNCOV
1014
        continue;
×
1015
      }
×
1016

1017
      BB2DomainInfo bbd;
2,659✔
1018
      bool isNew = false;
2,659✔
1019

1020
      if (!safeGetBBDomainInfo(domain.name, &bbd)) {
2,659!
1021
        isNew = true;
2,659✔
1022
        bbd.d_id = domain_id++;
2,659✔
1023
        bbd.setCheckInterval(getArgAsNum("check-interval"));
2,659✔
1024
        bbd.d_lastnotified = 0;
2,659✔
1025
        bbd.d_loaded = false;
2,659✔
1026
      }
2,659✔
1027

1028
      // overwrite what we knew about the domain
1029
      bbd.d_name = domain.name;
2,659✔
1030
      bool filenameChanged = bbd.d_fileinfo.empty() || (bbd.main_filename() != domain.filename);
2,659!
1031
      bool addressesChanged = (bbd.d_primaries != domain.primaries || bbd.d_also_notify != domain.alsoNotify);
2,659!
1032
      // Preserve existing fileinfo in case we won't reread anything.
1033
      if (filenameChanged) {
2,659!
1034
        bbd.d_fileinfo.clear();
2,659✔
1035
        bbd.d_fileinfo.emplace_back(std::make_pair(domain.filename, 0));
2,659✔
1036
      }
2,659✔
1037
      bbd.d_primaries = domain.primaries;
2,659✔
1038
      bbd.d_also_notify = domain.alsoNotify;
2,659✔
1039

1040
      DomainInfo::DomainKind kind = DomainInfo::Native;
2,659✔
1041
      if (domain.type == "primary" || domain.type == "master") {
2,659!
1042
        kind = DomainInfo::Primary;
2,587✔
1043
      }
2,587✔
1044
      if (domain.type == "secondary" || domain.type == "slave") {
2,659!
1045
        kind = DomainInfo::Secondary;
72✔
1046
      }
72✔
1047

1048
      bool kindChanged = (bbd.d_kind != kind);
2,659✔
1049
      bbd.d_kind = kind;
2,659✔
1050

1051
      newnames.insert(bbd.d_name);
2,659✔
1052
      if (filenameChanged || !bbd.d_loaded || !bbd.current()) {
2,659!
1053
        SLOG(g_log << Logger::Info << d_logprefix << " parsing '" << domain.name << "' from file '" << domain.filename << "'" << endl,
2,659!
1054
             d_slog->info(Logr::Info, "Parsing zone from file", "zone", Logging::Loggable(domain.name), "file", Logging::Loggable(domain.filename)));
2,659✔
1055

1056
        try {
2,659✔
1057
          parseZoneFile(&bbd);
2,659✔
1058
        }
2,659✔
1059
        catch (PDNSException& ae) {
2,659✔
UNCOV
1060
          ostringstream msg;
×
UNCOV
1061
          msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.reason;
×
1062

UNCOV
1063
          if (status != nullptr)
×
UNCOV
1064
            *status += msg.str();
×
UNCOV
1065
          bbd.d_status = msg.str();
×
1066

UNCOV
1067
          SLOG(g_log << Logger::Warning << d_logprefix << msg.str() << endl,
×
UNCOV
1068
               d_slog->error(Logr::Error, ae.reason, "Error in zone file", "zone", Logging::Loggable(domain.name), "file", Logging::Loggable(domain.filename)));
×
UNCOV
1069
          rejected++;
×
UNCOV
1070
        }
×
1071
        catch (std::system_error& ae) {
2,659✔
1072
          ostringstream msg;
72✔
1073
          bool missingNewSecondary = ae.code().value() == ENOENT && isNew && kind == DomainInfo::Secondary;
72!
1074
          if (missingNewSecondary) {
72!
1075
            msg << " error at " + nowTime() << " no file found for new secondary domain '" << domain.name << "'. Has not been AXFR'd yet";
72✔
1076
          }
72✔
UNCOV
1077
          else {
×
1078
            msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.what();
×
1079
          }
×
1080

1081
          if (status != nullptr)
72!
1082
            *status += msg.str();
×
1083
          bbd.d_status = msg.str();
72✔
1084
          SLOG(
72!
1085
            g_log << Logger::Warning << d_logprefix << msg.str() << endl,
72✔
1086
            if (missingNewSecondary) {
72✔
1087
              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));
72✔
1088
            } else {
72✔
1089
              d_slog->error(Logr::Warning, ae.what(), "Parse error", "domain", Logging::Loggable(domain.name), "file", Logging::Loggable(domain.filename));
72✔
1090
            });
72✔
1091
          rejected++;
72✔
1092
        }
72✔
1093
        catch (std::exception& ae) {
2,659✔
1094
          ostringstream msg;
×
UNCOV
1095
          msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.what();
×
1096

1097
          if (status != nullptr)
×
UNCOV
1098
            *status += msg.str();
×
UNCOV
1099
          bbd.d_status = msg.str();
×
1100

UNCOV
1101
          SLOG(g_log << Logger::Warning << d_logprefix << msg.str() << endl,
×
UNCOV
1102
               d_slog->error(Logr::Warning, ae.what(), "Parse error", "domain", Logging::Loggable(domain.name), "file", Logging::Loggable(domain.filename)));
×
UNCOV
1103
          rejected++;
×
UNCOV
1104
        }
×
1105
        safePutBBDomainInfo(bbd);
2,659✔
1106
      }
2,659✔
UNCOV
1107
      else if (addressesChanged || kindChanged) {
×
UNCOV
1108
        safePutBBDomainInfo(bbd);
×
1109
      }
×
1110
    }
2,659✔
1111
    vector<ZoneName> diff;
311✔
1112

1113
    set_difference(oldnames.begin(), oldnames.end(), newnames.begin(), newnames.end(), back_inserter(diff));
311✔
1114
    unsigned int remdomains = diff.size();
311✔
1115

1116
    for (const ZoneName& name : diff) {
311!
1117
      safeRemoveBBDomainInfo(name);
×
1118
    }
×
1119

1120
    // count number of entirely new domains
1121
    diff.clear();
311✔
1122
    set_difference(newnames.begin(), newnames.end(), oldnames.begin(), oldnames.end(), back_inserter(diff));
311✔
1123
    newdomains = diff.size();
311✔
1124

1125
    ostringstream msg;
311✔
1126
    msg << " Done parsing domains, " << rejected << " rejected, " << newdomains << " new, " << remdomains << " removed";
311✔
1127
    if (status != nullptr)
311!
UNCOV
1128
      *status = msg.str();
×
1129

1130
    SLOG(
311!
1131
      g_log << Logger::Error << d_logprefix << msg.str() << endl,
311✔
1132
      if (rejected == 0) {
311✔
1133
        d_slog->info(Logr::Info, "Done parsing domains", "new", Logging::Loggable(newdomains), "removed", Logging::Loggable(remdomains));
311✔
1134
      } else {
311✔
1135
        d_slog->info(Logr::Error, "Done parsing domains", "new", Logging::Loggable(newdomains), "removed", Logging::Loggable(remdomains), "rejected", Logging::Loggable(rejected));
311✔
1136
      });
311✔
1137
  }
311✔
1138
}
311✔
1139

1140
// NOLINTNEXTLINE(readability-identifier-length)
1141
void Bind2Backend::queueReloadAndStore(domainid_t id)
1142
{
78✔
1143
  BB2DomainInfo bbold;
78✔
1144
  try {
78✔
1145
    if (!safeGetBBDomainInfo(id, &bbold))
78!
UNCOV
1146
      return;
×
1147
    bbold.d_checknow = false;
78✔
1148
    BB2DomainInfo bbnew(bbold);
78✔
1149
    /* make sure that nothing will be able to alter the existing records,
1150
       we will load them from the zone file instead */
1151
    bbnew.d_records = LookButDontTouch<recordstorage_t>();
78✔
1152
    parseZoneFile(&bbnew);
78✔
1153
    bbnew.d_wasRejectedLastReload = false;
78✔
1154
    safePutBBDomainInfo(bbnew);
78✔
1155
    SLOG(g_log << Logger::Warning << "Zone '" << bbnew.d_name << "' (" << bbnew.main_filename() << ") reloaded" << endl,
78!
1156
         d_slog->info(Logr::Info, "Zone reloaded", "zone", Logging::Loggable(bbnew.d_name), "file", Logging::Loggable(bbnew.main_filename())));
78✔
1157
  }
78✔
1158
  catch (PDNSException& ae) {
78✔
UNCOV
1159
    ostringstream msg;
×
UNCOV
1160
    msg << " error at " + nowTime() + " parsing '" << bbold.d_name << "' from file '" << bbold.main_filename() << "': " << ae.reason;
×
1161
    SLOG(g_log << Logger::Warning << "Error parsing '" << bbold.d_name << "' from file '" << bbold.main_filename() << "': " << ae.reason << endl,
×
UNCOV
1162
         d_slog->error(Logr::Error, ae.reason, "Error reloading zone", "zone", Logging::Loggable(bbold.d_name), "file", Logging::Loggable(bbold.main_filename())));
×
UNCOV
1163
    bbold.d_status = msg.str();
×
UNCOV
1164
    bbold.d_lastcheck = time(nullptr);
×
UNCOV
1165
    bbold.d_wasRejectedLastReload = true;
×
UNCOV
1166
    safePutBBDomainInfo(bbold);
×
UNCOV
1167
  }
×
1168
  catch (std::exception& ae) {
78✔
UNCOV
1169
    ostringstream msg;
×
UNCOV
1170
    msg << " error at " + nowTime() + " parsing '" << bbold.d_name << "' from file '" << bbold.main_filename() << "': " << ae.what();
×
UNCOV
1171
    SLOG(g_log << Logger::Warning << "Error parsing '" << bbold.d_name << "' from file '" << bbold.main_filename() << "': " << ae.what() << endl,
×
UNCOV
1172
         d_slog->error(Logr::Error, ae.what(), "Error reloading zone", "zone", Logging::Loggable(bbold.d_name), "file", Logging::Loggable(bbold.main_filename())));
×
UNCOV
1173
    bbold.d_status = msg.str();
×
1174
    bbold.d_lastcheck = time(nullptr);
×
1175
    bbold.d_wasRejectedLastReload = true;
×
1176
    safePutBBDomainInfo(bbold);
×
1177
  }
×
1178
}
78✔
1179

1180
bool Bind2Backend::findBeforeAndAfterUnhashed(std::shared_ptr<const recordstorage_t>& records, const DNSName& qname, DNSName& /* unhashed */, DNSName& before, DNSName& after)
1181
{
2,718✔
1182
  // for(const auto& record: *records)
1183
  //   cerr<<record.qname<<"\t"<<makeHexDump(record.qname.toDNSString())<<endl;
1184

1185
  recordstorage_t::const_iterator iterBefore, iterAfter;
2,718✔
1186

1187
  iterBefore = iterAfter = records->upper_bound(qname.makeLowerCase());
2,718✔
1188

1189
  if (iterBefore != records->begin())
2,718!
1190
    --iterBefore;
2,718✔
1191
  while ((!iterBefore->auth && iterBefore->qtype != QType::NS) || !iterBefore->qtype)
3,247✔
1192
    --iterBefore;
529✔
1193
  before = iterBefore->qname;
2,718✔
1194

1195
  if (iterAfter == records->end()) {
2,718✔
1196
    iterAfter = records->begin();
376✔
1197
  }
376✔
1198
  else {
2,342✔
1199
    while ((!iterAfter->auth && iterAfter->qtype != QType::NS) || !iterAfter->qtype) {
3,524✔
1200
      ++iterAfter;
1,182✔
1201
      if (iterAfter == records->end()) {
1,182!
UNCOV
1202
        iterAfter = records->begin();
×
UNCOV
1203
        break;
×
UNCOV
1204
      }
×
1205
    }
1,182✔
1206
  }
2,342✔
1207
  after = iterAfter->qname;
2,718✔
1208

1209
  return true;
2,718✔
1210
}
2,718✔
1211

1212
// NOLINTNEXTLINE(readability-identifier-length)
1213
bool Bind2Backend::getBeforeAndAfterNamesAbsolute(domainid_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
1214
{
7,048✔
1215
  BB2DomainInfo bbd;
7,048✔
1216
  if (!safeGetBBDomainInfo(id, &bbd))
7,048!
1217
    return false;
×
1218

1219
  shared_ptr<const recordstorage_t> records = bbd.d_records.get();
7,048✔
1220
  if (!bbd.d_nsec3zone) {
7,048✔
1221
    return findBeforeAndAfterUnhashed(records, qname, unhashed, before, after);
2,718✔
1222
  }
2,718✔
1223
  else {
4,330✔
1224
    const auto& hashindex = boost::multi_index::get<NSEC3Tag>(*records);
4,330✔
1225

1226
    // for(auto iter = first; iter != hashindex.end(); iter++)
1227
    //  cerr<<iter->nsec3hash<<endl;
1228

1229
    auto first = hashindex.upper_bound("");
4,330✔
1230
    auto iter = hashindex.upper_bound(qname.toStringNoDot());
4,330✔
1231

1232
    if (iter == hashindex.end()) {
4,330✔
1233
      --iter;
346✔
1234
      before = DNSName(iter->nsec3hash);
346✔
1235
      after = DNSName(first->nsec3hash);
346✔
1236
    }
346✔
1237
    else {
3,984✔
1238
      after = DNSName(iter->nsec3hash);
3,984✔
1239
      if (iter != first)
3,984✔
1240
        --iter;
3,894✔
1241
      else
90✔
1242
        iter = --hashindex.end();
90✔
1243
      before = DNSName(iter->nsec3hash);
3,984✔
1244
    }
3,984✔
1245
    unhashed = iter->qname + bbd.d_name.operator const DNSName&();
4,330✔
1246

1247
    return true;
4,330✔
1248
  }
4,330✔
1249
}
7,048✔
1250

1251
void Bind2Backend::lookup(const QType& qtype, const DNSName& qname, domainid_t zoneId, DNSPacket* /* pkt_p */)
1252
{
4,178✔
1253
  d_handle.reset();
4,178✔
1254

1255
  static bool mustlog = ::arg().mustDo("query-logging");
4,178✔
1256

1257
  bool found = false;
4,178✔
1258
  ZoneName domain;
4,178✔
1259
  BB2DomainInfo bbd;
4,178✔
1260

1261
  if (mustlog) {
4,178!
UNCOV
1262
    SLOG(g_log << Logger::Warning << "Lookup for '" << qtype.toString() << "' of '" << qname << "' within zoneID " << zoneId << endl,
×
UNCOV
1263
         d_slog->info(Logr::Warning, "Record lookup", "name", Logging::Loggable(qname), "qtype", Logging::Loggable(qtype), "zone id", Logging::Loggable(zoneId)));
×
UNCOV
1264
  }
×
1265

1266
  if (zoneId != UnknownDomainID) {
4,178✔
1267
    if ((found = (safeGetBBDomainInfo(zoneId, &bbd) && qname.isPartOf(bbd.d_name)))) {
3,564!
1268
      domain = std::move(bbd.d_name);
3,564✔
1269
    }
3,564✔
1270
  }
3,564✔
1271
  else {
614✔
1272
    domain = ZoneName(qname);
614✔
1273
    do {
617✔
1274
      found = safeGetBBDomainInfo(domain, &bbd);
617✔
1275
    } while (!found && qtype != QType::SOA && domain.chopOff());
617✔
1276
  }
614✔
1277

1278
  if (!found) {
4,178✔
1279
    if (mustlog) {
24!
UNCOV
1280
      SLOG(g_log << Logger::Warning << "Found no authoritative zone for '" << qname << "' and/or id " << zoneId << endl,
×
UNCOV
1281
           d_slog->info(Logr::Warning, "Found no authoritative zone", "name", Logging::Loggable(qname), "zone id", Logging::Loggable(zoneId)));
×
UNCOV
1282
    }
×
1283
    d_handle.d_list = false;
24✔
1284
    return;
24✔
1285
  }
24✔
1286

1287
  if (mustlog) {
4,154!
UNCOV
1288
    SLOG(g_log << Logger::Warning << "Found a zone '" << domain << "' (with id " << bbd.d_id << ") that might contain data " << endl,
×
UNCOV
1289
         d_slog->info(Logr::Warning, "Found authoritative zone", "zone", Logging::Loggable(domain), "zone id", Logging::Loggable(bbd.d_id)));
×
UNCOV
1290
  }
×
1291

1292
  d_handle.id = bbd.d_id;
4,154✔
1293
  d_handle.qname = qname.makeRelative(domain); // strip domain name
4,154✔
1294
  d_handle.qtype = qtype;
4,154✔
1295
  d_handle.domain = std::move(domain);
4,154✔
1296

1297
  if (!bbd.current()) {
4,154✔
1298
    SLOG(g_log << Logger::Warning << "Zone '" << d_handle.domain << "' (" << bbd.main_filename() << ") needs reloading" << endl,
6!
1299
         d_slog->info(Logr::Warning, "Zone needs reloading", "zone", Logging::Loggable(d_handle.domain), "file", Logging::Loggable(bbd.main_filename())));
6✔
1300
    queueReloadAndStore(bbd.d_id);
6✔
1301
    if (!safeGetBBDomainInfo(d_handle.domain, &bbd))
6!
UNCOV
1302
      throw DBException("Zone '" + bbd.d_name.toLogString() + "' (" + bbd.main_filename() + ") gone after reload"); // if we don't throw here, we crash for some reason
×
1303
  }
6✔
1304

1305
  if (!bbd.d_loaded) {
4,154✔
1306
    d_handle.reset();
216✔
1307
    throw DBException("Zone for '" + d_handle.domain.toLogString() + "' in '" + bbd.main_filename() + "' not loaded (file missing, corrupt or primary dead)"); // fsck
216✔
1308
  }
216✔
1309

1310
  d_handle.d_records = bbd.d_records.get();
3,938✔
1311

1312
  if (d_handle.d_records->empty()) {
3,938!
UNCOV
1313
    DLOG(SLOG(g_log << "Query with no results" << endl,
×
UNCOV
1314
              d_slog->info(Logr::Debug, "No results", "name", Logging::Loggable(qname), "qtype", Logging::Loggable(qtype))));
×
UNCOV
1315
  }
×
1316

1317
  d_handle.mustlog = mustlog;
3,938✔
1318

1319
  const auto& hashedidx = boost::multi_index::get<UnorderedNameTag>(*d_handle.d_records);
3,938✔
1320
  auto range = hashedidx.equal_range(d_handle.qname);
3,938✔
1321

1322
  d_handle.d_list = false;
3,938✔
1323
  d_handle.d_iter = range.first;
3,938✔
1324
  d_handle.d_end_iter = range.second;
3,938✔
1325
}
3,938✔
1326

1327
bool Bind2Backend::get(DNSResourceRecord& r)
1328
{
198,039✔
1329
  if (!d_handle.d_records) {
198,039✔
1330
    if (d_handle.mustlog) {
24!
UNCOV
1331
      SLOG(g_log << Logger::Warning << "There were no answers" << endl,
×
UNCOV
1332
           d_slog->info(Logr::Warning, "No answers"));
×
UNCOV
1333
    }
×
1334
    return false;
24✔
1335
  }
24✔
1336

1337
  if (!d_handle.get(r)) {
198,015✔
1338
    if (d_handle.mustlog) {
4,241!
UNCOV
1339
      SLOG(g_log << Logger::Warning << "End of answers" << endl,
×
UNCOV
1340
           d_slog->info(Logr::Warning, "No more answers"));
×
UNCOV
1341
    }
×
1342

1343
    d_handle.reset();
4,241✔
1344

1345
    return false;
4,241✔
1346
  }
4,241✔
1347
  if (d_handle.mustlog) {
193,774!
1348
    SLOG(g_log << Logger::Warning << "Returning: '" << r.qtype.toString() << "' of '" << r.qname << "', content: '" << r.content << "'" << endl,
×
UNCOV
1349
         d_slog->info(Logr::Warning, "Returning record", "name", Logging::Loggable(r.qname), "type", Logging::Loggable(r.qtype), "content", Logging::Loggable(r.content)));
×
UNCOV
1350
  }
×
1351
  return true;
193,774✔
1352
}
198,015✔
1353

1354
void Bind2Backend::lookupEnd()
1355
{
28✔
1356
  d_handle.reset();
28✔
1357
}
28✔
1358

1359
bool Bind2Backend::handle::get(DNSResourceRecord& r)
1360
{
198,015✔
1361
  if (d_list)
198,015✔
1362
    return get_list(r);
188,936✔
1363
  else
9,079✔
1364
    return get_normal(r);
9,079✔
1365
}
198,015✔
1366

1367
void Bind2Backend::handle::reset()
1368
{
8,994✔
1369
  d_records.reset();
8,994✔
1370
  qname.clear();
8,994✔
1371
  mustlog = false;
8,994✔
1372
}
8,994✔
1373

1374
bool Bind2Backend::handle::get_normal(DNSResourceRecord& r)
1375
{
9,079✔
1376
  DLOG(SLOG(g_log << "Bind2Backend get() was called for " << qtype.toString() << " record for '" << qname << "' - " << d_records->size() << " available in total!" << endl,
9,079✔
1377
            d_slog->info(Logr::Debug, "Bind2Backend get() invoked", "name", Logging::Loggable(qname), "type", Logging::Loggable(qtype), "results", Logging::Loggable(d_records->size()))));
9,079✔
1378

1379
  if (d_iter == d_end_iter) {
9,079✔
1380
    return false;
3,910✔
1381
  }
3,910✔
1382

1383
  while (d_iter != d_end_iter && !(qtype.getCode() == QType::ANY || (d_iter)->qtype == qtype.getCode())) {
7,902!
1384
    DLOG(SLOG(g_log << Logger::Warning << "Skipped " << qname << "/" << QType(d_iter->qtype).toString() << ": '" << d_iter->content << "'" << endl,
2,733✔
1385
              d_slog->info(Logr::Debug, "Skipped record", "name", Logging::Loggable(qname), "type", Logging::Loggable(d_iter->qtype), "content", Logging::Loggable(d_iter->content))));
2,733✔
1386
    d_iter++;
2,733✔
1387
  }
2,733✔
1388
  if (d_iter == d_end_iter) {
5,169!
UNCOV
1389
    return false;
×
UNCOV
1390
  }
×
1391
  DLOG(SLOG(g_log << "Bind2Backend get() returning a rr with a " << QType(d_iter->qtype).getCode() << endl,
5,169✔
1392
            d_slog->info(Logr::Debug, "Bind2Backend get() returning a rr", "type", Logging::Loggable(d_iter->qtype))));
5,169✔
1393

1394
  const DNSName& domainName(domain);
5,169✔
1395
  r.qname = qname.empty() ? domainName : (qname + domainName);
5,169!
1396
  r.domain_id = id;
5,169✔
1397
  r.content = (d_iter)->content;
5,169✔
1398
  //  r.domain_id=(d_iter)->domain_id;
1399
  r.qtype = (d_iter)->qtype;
5,169✔
1400
  r.ttl = (d_iter)->ttl;
5,169✔
1401

1402
  //if(!d_iter->auth && r.qtype.getCode() != QType::A && r.qtype.getCode()!=QType::AAAA && r.qtype.getCode() != QType::NS)
1403
  //  cerr<<"Warning! Unauth response for qtype "<< r.qtype.toString() << " for '"<<r.qname<<"'"<<endl;
1404
  r.auth = d_iter->auth;
5,169✔
1405

1406
  d_iter++;
5,169✔
1407

1408
  return true;
5,169✔
1409
}
5,169✔
1410

1411
bool Bind2Backend::list(const ZoneName& /* target */, domainid_t domainId, bool /* include_disabled */)
1412
{
331✔
1413
  BB2DomainInfo bbd;
331✔
1414

1415
  if (!safeGetBBDomainInfo(domainId, &bbd)) {
331!
UNCOV
1416
    return false;
×
UNCOV
1417
  }
×
1418

1419
  d_handle.reset();
331✔
1420
  DLOG(SLOG(g_log << "Bind2Backend constructing handle for list of " << domainId << endl,
331✔
1421
            d_slog->info(Logr::Debug, "Bind2Backend constructing handle for zone list", "zone id", Logging::Loggable(domainId))));
331✔
1422

1423
  if (!bbd.d_loaded) {
331!
UNCOV
1424
    throw PDNSException("zone was not loaded, perhaps because of: " + bbd.d_status);
×
UNCOV
1425
  }
×
1426

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

1431
  d_handle.id = domainId;
331✔
1432
  d_handle.domain = bbd.d_name;
331✔
1433
  d_handle.d_list = true;
331✔
1434
  return true;
331✔
1435
}
331✔
1436

1437
bool Bind2Backend::handle::get_list(DNSResourceRecord& r)
1438
{
188,936✔
1439
  if (d_qname_iter != d_qname_end) {
188,936✔
1440
    const DNSName& domainName(domain);
188,605✔
1441
    r.qname = d_qname_iter->qname.empty() ? domainName : (d_qname_iter->qname + domainName);
188,605!
1442
    r.domain_id = id;
188,605✔
1443
    r.content = (d_qname_iter)->content;
188,605✔
1444
    r.qtype = (d_qname_iter)->qtype;
188,605✔
1445
    r.ttl = (d_qname_iter)->ttl;
188,605✔
1446
    r.auth = d_qname_iter->auth;
188,605✔
1447
    d_qname_iter++;
188,605✔
1448
    return true;
188,605✔
1449
  }
188,605✔
1450
  return false;
331✔
1451
}
188,936✔
1452

1453
bool Bind2Backend::autoPrimariesList(std::vector<AutoPrimary>& primaries)
UNCOV
1454
{
×
UNCOV
1455
  if (getArg("autoprimary-config").empty())
×
UNCOV
1456
    return false;
×
1457

UNCOV
1458
  std::string filename(getArg("autoprimaries"));
×
UNCOV
1459
  ifstream c_if(filename, std::ios::in);
×
UNCOV
1460
  if (!c_if) {
×
UNCOV
1461
    SLOG(g_log << Logger::Error << "Unable to open autoprimaries file for read: " << stringerror() << endl,
×
UNCOV
1462
         d_slog->error(Logr::Error, errno, "Unable to open autoprimaries file", "file", Logging::Loggable(filename)));
×
UNCOV
1463
    return false;
×
UNCOV
1464
  }
×
1465

UNCOV
1466
  string line, sip, saccount;
×
UNCOV
1467
  while (getline(c_if, line)) {
×
UNCOV
1468
    std::istringstream ii(line);
×
1469
    ii >> sip;
×
1470
    if (!sip.empty()) {
×
1471
      ii >> saccount;
×
UNCOV
1472
      primaries.emplace_back(sip, "", saccount);
×
1473
    }
×
1474
  }
×
1475

1476
  c_if.close();
×
1477
  return true;
×
1478
}
×
1479

1480
bool Bind2Backend::autoPrimaryBackend(const string& ipAddress, const ZoneName& /* domain */, const vector<DNSResourceRecord>& /* nsset */, string* /* nameserver */, string* account, DNSBackend** backend)
1481
{
×
1482
  // Check whether we have a configfile available.
1483
  if (getArg("autoprimary-config").empty())
×
1484
    return false;
×
1485

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

1494
  // Format:
1495
  // <ip> <accountname>
1496
  string line, sip, saccount;
×
UNCOV
1497
  while (getline(c_if, line)) {
×
1498
    std::istringstream ii(line);
×
1499
    ii >> sip;
×
UNCOV
1500
    if (sip == ipAddress) {
×
1501
      ii >> saccount;
×
1502
      break;
×
1503
    }
×
1504
  }
×
1505
  c_if.close();
×
1506

1507
  if (sip != ipAddress) { // ip not found in authorization list - reject
×
UNCOV
1508
    return false;
×
UNCOV
1509
  }
×
1510

1511
  // ip authorized as autoprimary - accept
1512
  *backend = this;
×
1513
  if (saccount.length() > 0)
×
1514
    *account = saccount.c_str();
×
1515

1516
  return true;
×
1517
}
×
1518

1519
BB2DomainInfo Bind2Backend::createDomainEntry(const ZoneName& domain)
1520
{
6✔
1521
  domainid_t newid = 1;
6✔
1522
  { // Find a free zone id nr.
6✔
1523
    auto state = s_state.read_lock();
6✔
1524
    if (!state->empty()) {
6!
1525
      newid = state->rbegin()->d_id + 1;
6✔
1526
    }
6✔
1527
  }
6✔
1528

1529
  BB2DomainInfo bbd;
6✔
1530
  bbd.d_kind = DomainInfo::Native;
6✔
1531
  bbd.d_id = newid;
6✔
1532
  bbd.d_records = std::make_shared<recordstorage_t>();
6✔
1533
  bbd.d_name = domain;
6✔
1534
  bbd.setCheckInterval(getArgAsNum("check-interval"));
6✔
1535

1536
  return bbd;
6✔
1537
}
6✔
1538

1539
bool Bind2Backend::createSecondaryDomain(const string& ipAddress, const ZoneName& domain, const string& /* nameserver */, const string& account)
UNCOV
1540
{
×
UNCOV
1541
  string filename = getArg("autoprimary-destdir") + '/' + domain.toStringNoDot();
×
1542

UNCOV
1543
  SLOG(g_log << Logger::Warning << d_logprefix
×
UNCOV
1544
             << " Writing bind config zone statement for autosecondary zone '" << domain
×
UNCOV
1545
             << "' from autoprimary " << ipAddress << endl,
×
UNCOV
1546
       d_slog->info(Logr::Warning, "Writing bind config zone statement for autosecondary zone", "zone", Logging::Loggable(domain), "autoprimary address", Logging::Loggable(ipAddress)));
×
1547

UNCOV
1548
  {
×
UNCOV
1549
    auto lock = std::scoped_lock(s_autosecondary_config_lock);
×
1550

UNCOV
1551
    std::string configfile(getArg("autoprimary-config"));
×
UNCOV
1552
    ofstream c_of(configfile.c_str(), std::ios::app);
×
UNCOV
1553
    if (!c_of) {
×
UNCOV
1554
      int err = errno;
×
1555
      auto errorMessage = stringerror();
×
1556
      SLOG(g_log << Logger::Error << "Unable to open autoprimary configfile for append: " << errorMessage << endl,
×
UNCOV
1557
           d_slog->error(Logr::Error, err, "Unable to open autoprimary configuration file for append", "file", Logging::Loggable(configfile)));
×
1558
      throw DBException("Unable to open autoprimary configfile for append: " + errorMessage);
×
1559
    }
×
1560

1561
    c_of << endl;
×
UNCOV
1562
    c_of << "# AutoSecondary zone '" << domain.toString() << "' (added: " << nowTime() << ") (account: " << account << ')' << endl;
×
1563
    c_of << "zone \"" << domain.toStringNoDot() << "\" {" << endl;
×
1564
    c_of << "\ttype secondary;" << endl;
×
UNCOV
1565
    c_of << "\tfile \"" << filename << "\";" << endl;
×
1566
    c_of << "\tprimaries { " << ipAddress << "; };" << endl;
×
1567
    c_of << "};" << endl;
×
1568
    c_of.close();
×
1569
  }
×
1570

1571
  BB2DomainInfo bbd = createDomainEntry(domain);
×
1572
  bbd.d_kind = DomainInfo::Secondary;
×
1573
  bbd.d_primaries.emplace_back(ComboAddress(ipAddress, 53));
×
1574
  bbd.d_fileinfo.emplace_back(std::make_pair(filename, 0));
×
UNCOV
1575
  bbd.updateCtime();
×
1576
  safePutBBDomainInfo(bbd);
×
1577

1578
  return true;
×
1579
}
×
1580

1581
bool Bind2Backend::searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result)
1582
{
23✔
1583
  SimpleMatch sm(pattern, true);
23✔
1584
  static bool mustlog = ::arg().mustDo("query-logging");
23✔
1585
  if (mustlog) {
23!
1586
    SLOG(g_log << Logger::Warning << "Search for pattern '" << pattern << "'" << endl,
×
1587
         d_slog->info(Logr::Debug, "Search for pattern", "pattern", Logging::Loggable(pattern)));
×
1588
  }
×
1589

1590
  {
23✔
1591
    auto state = s_state.read_lock();
23✔
1592

1593
    for (const auto& i : *state) {
23!
1594
      BB2DomainInfo h;
×
UNCOV
1595
      if (!safeGetBBDomainInfo(i.d_id, &h)) {
×
UNCOV
1596
        continue;
×
UNCOV
1597
      }
×
1598

UNCOV
1599
      if (!h.d_loaded) {
×
UNCOV
1600
        continue;
×
1601
      }
×
1602

1603
      shared_ptr<const recordstorage_t> rhandle = h.d_records.get();
×
1604

UNCOV
1605
      for (recordstorage_t::const_iterator ri = rhandle->begin(); result.size() < maxResults && ri != rhandle->end(); ri++) {
×
UNCOV
1606
        const DNSName& domainName(i.d_name);
×
UNCOV
1607
        DNSName name = ri->qname.empty() ? domainName : (ri->qname + domainName);
×
UNCOV
1608
        if (sm.match(name) || sm.match(ri->content)) {
×
1609
          DNSResourceRecord r;
×
1610
          r.qname = std::move(name);
×
1611
          r.domain_id = i.d_id;
×
1612
          r.content = ri->content;
×
UNCOV
1613
          r.qtype = ri->qtype;
×
1614
          r.ttl = ri->ttl;
×
1615
          r.auth = ri->auth;
×
1616
          result.push_back(std::move(r));
×
UNCOV
1617
        }
×
1618
      }
×
UNCOV
1619
    }
×
1620
  }
23✔
1621

1622
  return true;
23✔
1623
}
23✔
1624

1625
class Bind2Factory : public BackendFactory
1626
{
1627
public:
1628
  Bind2Factory() :
1629
    BackendFactory("bind") {}
5,904✔
1630

1631
  void declareArguments(const string& suffix = "") override
1632
  {
569✔
1633
    declare(suffix, "ignore-broken-records", "Ignore records that are out-of-bound for the zone.", "no");
569✔
1634
    declare(suffix, "config", "Location of named.conf", "");
569✔
1635
    declare(suffix, "check-interval", "Interval for zonefile changes", "0");
569✔
1636
    declare(suffix, "autoprimary-config", "Location of (part of) named.conf where pdns can write zone-statements to", "");
569✔
1637
    declare(suffix, "autoprimaries", "List of IP-addresses of autoprimaries", "");
569✔
1638
    declare(suffix, "autoprimary-destdir", "Destination directory for newly added secondary zones", ::arg()["config-dir"]);
569✔
1639
    declare(suffix, "dnssec-db", "Filename to store & access our DNSSEC metadatabase, empty for none", "");
569✔
1640
    declare(suffix, "dnssec-db-journal-mode", "SQLite3 journal mode", "WAL");
569✔
1641
    declare(suffix, "hybrid", "Store DNSSEC metadata in other backend", "no");
569✔
1642
  }
569✔
1643

1644
  DNSBackend* make(const string& suffix = "") override
1645
  {
2,518✔
1646
    assertEmptySuffix(suffix);
2,518✔
1647
    return new Bind2Backend(suffix);
2,518✔
1648
  }
2,518✔
1649

1650
  DNSBackend* makeMetadataOnly(const string& suffix = "") override
1651
  {
906✔
1652
    assertEmptySuffix(suffix);
906✔
1653
    return new Bind2Backend(suffix, false);
906✔
1654
  }
906✔
1655

1656
private:
1657
  void assertEmptySuffix(const string& suffix)
1658
  {
3,424✔
1659
    if (!suffix.empty())
3,424!
UNCOV
1660
      throw PDNSException("launch= suffixes are not supported on the bindbackend");
×
1661
  }
3,424✔
1662
};
1663

1664
//! Magic class that is activated when the dynamic library is loaded
1665
class Bind2Loader
1666
{
1667
public:
1668
  Bind2Loader()
1669
  {
5,904✔
1670
    BackendMakers().report(std::make_unique<Bind2Factory>());
5,904✔
1671
    // If this module is not loaded dynamically at runtime, this code runs
1672
    // as part of a global constructor, before the structured logger has a
1673
    // chance to be set up, so fallback to simple logging.
1674
    g_log << Logger::Info << "[bind2backend] This is the bind backend version " << VERSION
5,904✔
1675
#ifndef REPRODUCIBLE
5,904✔
1676
          << " (" __DATE__ " " __TIME__ ")"
5,904✔
1677
#endif
5,904✔
1678
#ifdef HAVE_SQLITE3
5,904✔
1679
          << " (with bind-dnssec-db support)"
5,904✔
1680
#endif
5,904✔
1681
          << " reporting" << endl;
5,904✔
1682
  }
5,904✔
1683
};
1684
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