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

PowerDNS / pdns / 30434956763

29 Jul 2026 08:17AM UTC coverage: 71.165% (+5.2%) from 66.001%
30434956763

Pull #17809

github

web-flow
Merge 9df5145e4 into 1be40d547
Pull Request #17809: auth: domain transactions

47035 of 82764 branches covered (56.83%)

Branch coverage included in aggregate %.

121 of 249 new or added lines in 13 files covered. (48.59%)

36 existing lines in 12 files now uncovered.

134971 of 172987 relevant lines covered (78.02%)

7272165.82 hits per line

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

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

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

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

104
  if (!d_checkinterval)
4,159!
105
    return true;
4,159✔
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,312✔
142
  auto state = s_state.read_lock();
11,312✔
143
  state_t::const_iterator iter = state->find(id);
11,312✔
144
  if (iter == state->end()) {
11,312!
145
    return false;
×
146
  }
×
147
  *bbd = *iter;
11,312✔
148
  return true;
11,312✔
149
}
11,312✔
150

151
bool Bind2Backend::safeGetBBDomainInfo(const ZoneName& name, BB2DomainInfo* bbd)
152
{
4,403✔
153
  auto state = s_state.read_lock();
4,403✔
154
  const auto& nameindex = boost::multi_index::get<NameTag>(*state);
4,403✔
155
  auto iter = nameindex.find(name);
4,403✔
156
  if (iter == nameindex.end()) {
4,403✔
157
    return false;
3,352✔
158
  }
3,352✔
159
  *bbd = *iter;
1,051✔
160
  return true;
1,051✔
161
}
4,403✔
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,827✔
179
  auto state = s_state.write_lock();
2,827✔
180
  replacing_insert(*state, bbd);
2,827✔
181
}
2,827✔
182

183
// NOLINTNEXTLINE(readability-identifier-length)
184
void Bind2Backend::setNotified(domainid_t id, uint32_t serial)
185
{
×
186
  BB2DomainInfo bbd;
×
NEW
187
  if (!safeGetBBDomainInfo(id, &bbd)) {
×
NEW
188
    return;
×
NEW
189
  }
×
190
  // Ignore pending domains unless we are the backend running the transaction
191
  // in which they are being created. This can't be done in safeGetBBDomainInfo
192
  // because it needs to be a static method.
NEW
193
  if (bbd.d_pending && bbd.d_id != d_transaction_id) {
×
194
    return;
×
NEW
195
  }
×
196
  bbd.d_lastnotified = serial;
×
197
  safePutBBDomainInfo(bbd);
×
198
}
×
199

200
// NOLINTNEXTLINE(readability-identifier-length)
201
void Bind2Backend::setLastCheck(domainid_t domain_id, time_t lastcheck)
202
{
72✔
203
  BB2DomainInfo bbd;
72✔
204
  if (!safeGetBBDomainInfo(domain_id, &bbd)) {
72!
NEW
205
    return;
×
NEW
206
  }
×
207
  // Ignore pending domains unless we are the backend running the transaction
208
  // in which they are being created. This can't be done in safeGetBBDomainInfo
209
  // because it needs to be a static method.
210
  if (bbd.d_pending && bbd.d_id != d_transaction_id) {
72!
NEW
211
    return;
×
UNCOV
212
  }
×
213
  bbd.d_lastcheck = lastcheck;
72✔
214
  safePutBBDomainInfo(bbd);
72✔
215
}
72✔
216

217
void Bind2Backend::setStale(domainid_t domain_id)
218
{
×
NEW
219
  setLastCheck(domain_id, 0);
×
220
}
×
221

222
void Bind2Backend::setFresh(domainid_t domain_id)
223
{
72✔
224
  setLastCheck(domain_id, time(nullptr));
72✔
225
}
72✔
226

227
bool Bind2Backend::startDomainCreationTransactionInternal(BB2DomainInfo& bbd)
228
{
72✔
229
  d_transaction_qname = bbd.d_name;
72✔
230
  d_transaction_id = bbd.d_id;
72✔
231
  d_transaction_tmpname = bbd.main_filename() + "XXXXXX";
72✔
232
  // NOLINTNEXTLINE(readability-identifier-length)
233
  int fd = mkstemp(&d_transaction_tmpname.at(0));
72✔
234
  if (fd == -1) {
72!
NEW
235
    throw DBException("Unable to create a unique temporary zonefile '" + d_transaction_tmpname + "': " + stringerror());
×
UNCOV
236
  }
×
237

238
  d_of = std::make_unique<ofstream>(d_transaction_tmpname);
72✔
239
  if (!*d_of) {
72!
NEW
240
    unlink(d_transaction_tmpname.c_str());
×
NEW
241
    close(fd);
×
NEW
242
    fd = -1; // NOLINT(clang-analyzer-deadcode.DeadStores)
×
NEW
243
    d_of.reset();
×
NEW
244
    throw DBException("Unable to open temporary zonefile '" + d_transaction_tmpname + "': " + stringerror());
×
NEW
245
  }
×
246
  close(fd);
72✔
247
  fd = -1; // NOLINT(clang-analyzer-deadcode.DeadStores)
72✔
248

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

253
  return true;
72✔
254
}
72✔
255

256
bool Bind2Backend::startDomainCreationTransaction(const ZoneName& /* qname */, domainid_t domainId)
257
{
72✔
258
  d_transaction_tmpname.clear();
72✔
259
  d_transaction_id = UnknownDomainID;
72✔
260

261
  if (domainId == 0) {
72!
262
    throw DBException("domain_id 0 is invalid for this backend.");
×
263
  }
×
264

265
  BB2DomainInfo bbd;
72✔
266
  if (!safeGetBBDomainInfo(domainId, &bbd)) {
72!
NEW
267
    return false;
×
NEW
268
  }
×
269
  // Ignore pending domains since we don't have an active transaction at this
270
  // point, so we can't be the backend currently creating them.
271
  if (bbd.d_pending) {
72!
NEW
272
    return false;
×
NEW
273
  }
×
274
  return startDomainCreationTransactionInternal(bbd);
72✔
275
}
72✔
276

277
bool Bind2Backend::startDomainModificationTransaction(const ZoneName& /* qname */)
278
{
149✔
279
  d_transaction_tmpname.clear();
149✔
280
  d_transaction_id = UnknownDomainID;
149✔
281

282
  // No support for domain contents modification, except as a "delete and
283
  // recreate in its entirety" operation.
284
  return false;
149✔
285
}
149✔
286

287
bool Bind2Backend::commitTransaction()
288
{
221✔
289
  // d_transaction_id is only set to a valid domain id if we are actually
290
  // setting up a replacement zone file with the updated data.
291
  if (d_transaction_id == UnknownDomainID) {
221✔
292
    return false;
149✔
293
  }
149✔
294
  d_of.reset();
72✔
295

296
  BB2DomainInfo bbd;
72✔
297
  if (safeGetBBDomainInfo(d_transaction_id, &bbd)) {
72!
298
    if (rename(d_transaction_tmpname.c_str(), bbd.main_filename().c_str()) < 0) {
72!
299
      throw DBException("Unable to commit (rename to: '" + bbd.main_filename() + "') AXFRed zone: " + stringerror());
×
300
    }
×
301
    // If that domain was part of a transaction, we can finally mark it as
302
    // available to everyone.
303
    if (bbd.d_pending) {
72!
NEW
304
      bbd.d_pending = false;
×
NEW
305
      safePutBBDomainInfo(bbd);
×
NEW
306
    }
×
307
    queueReloadAndStore(bbd.d_id);
72✔
308
  }
72✔
309

310
  d_transaction_id = UnknownDomainID;
72✔
311

312
  return true;
72✔
313
}
72✔
314

315
bool Bind2Backend::abortTransaction()
316
{
×
317
  // d_transaction_id is only set to a valid domain id if we are actually
318
  // setting up a replacement zone file with the updated data.
319
  if (d_transaction_id != UnknownDomainID) {
×
NEW
320
    BB2DomainInfo bbd;
×
NEW
321
    if (safeGetBBDomainInfo(d_transaction_id, &bbd)) {
×
NEW
322
      if (bbd.d_pending) {
×
NEW
323
        safeRemoveBBDomainInfo(bbd.d_name);
×
NEW
324
      }
×
NEW
325
    }
×
326
    unlink(d_transaction_tmpname.c_str());
×
327
    d_of.reset();
×
328
    d_transaction_id = UnknownDomainID;
×
329
  }
×
330

331
  return true;
×
332
}
×
333

334
/** does domain end on suffix? Is smart about "wwwds9a.nl" "ds9a.nl" not matching */
335
static bool endsOn(const string& domain, const string& suffix)
336
{
416✔
337
  if (domain.size() <= suffix.size()) {
416✔
338
    return false;
52✔
339
  }
52✔
340

341
  string::size_type dpos = domain.size() - suffix.size() - 1;
364✔
342
  if (domain[dpos++] != '.') {
364✔
343
    return false;
88✔
344
  }
88✔
345
  // That dot might have been escaped. So we now need to count how many '\'
346
  // characters we can find in a row before it; if their number is odd, the
347
  // dot is escaped and we are not a proper suffix.
348
  size_t slashes{0};
276✔
349
  while (dpos >= 2 + slashes && domain.at(dpos - 2 - slashes) == '\\') {
276!
350
    ++slashes;
×
351
  }
×
352
  if ((slashes % 2) != 0) {
276!
353
    return false;
×
354
  }
×
355

356
  string::size_type spos = 0;
276✔
357
  for (; dpos < domain.size(); ++dpos, ++spos) {
3,928✔
358
    if (!pdns_iequals_ch(domain[dpos], suffix[spos])) {
3,664✔
359
      return false;
12✔
360
    }
12✔
361
  }
3,664✔
362

363
  return true;
264✔
364
}
276✔
365

366
/** strips a domain suffix from a domain */
367
static void stripDomainSuffix(string* qname, const ZoneName& zonename)
368
{
432✔
369
  std::string domain = zonename.operator const DNSName&().toString();
432✔
370

371
  if (domain.empty()) {
432!
372
    return;
×
373
  }
×
374
  if (pdns_iequals(*qname, domain)) {
432✔
375
    *qname = "@";
16✔
376
    return;
16✔
377
  }
16✔
378
  if (endsOn(*qname, domain)) {
416✔
379
    auto prefix = qname->size() - domain.size();
264✔
380
    qname->resize(prefix - 1); // also strip dot
264✔
381
  }
264✔
382
}
416✔
383

384
// Perform adequate escaping of characters which have special meaning in
385
// Bind zone files.
386
// Note that the input is supposed to be a DNSName::toString() - or any of
387
// its variants - so we assume \ and . have been correctly escaped by
388
// DNSName::appendEscapedLabel already.
389
static const std::string bindEscape(const std::string& name)
390
{
202,172✔
391
  std::string ret;
202,172✔
392
  std::array<char, 5> ebuf{};
202,172✔
393

394
  for (char letter : name) {
2,818,960✔
395
    switch (letter) {
2,818,960✔
396
    case '$':
×
397
    case '@':
×
398
    case '"':
×
399
    case ';':
×
400
    case '(':
×
401
    case ')':
×
402
      snprintf(ebuf.data(), ebuf.size(), "\\%03u", static_cast<unsigned char>(letter));
×
403
      ret += ebuf.data();
×
404
      break;
×
405
    default:
2,818,960!
406
      ret += letter;
2,818,960✔
407
      break;
2,818,960✔
408
    }
2,818,960✔
409
  }
2,818,960✔
410
  return ret;
202,172✔
411
}
202,172✔
412

413
bool Bind2Backend::feedRecord(const DNSResourceRecord& rr, const DNSName& /* ordername */, bool /* ordernameIsNSEC3 */)
414
{
202,805✔
415
  if (d_transaction_id == UnknownDomainID) {
202,805!
416
    throw DBException("Bind2Backend::feedRecord() called outside of transaction");
×
417
  }
×
418

419
  string qname;
202,805✔
420
  if (d_transaction_qname.empty()) {
202,805!
421
    qname = bindEscape(rr.qname.toString());
×
422
  }
×
423
  else if (rr.qname.isPartOf(d_transaction_qname)) {
202,805!
424
    if (rr.qname == d_transaction_qname.operator const DNSName&()) {
202,805✔
425
      qname = "@";
633✔
426
    }
633✔
427
    else {
202,172✔
428
      DNSName relName = rr.qname.makeRelative(d_transaction_qname);
202,172✔
429
      qname = bindEscape(relName.toStringNoDot());
202,172✔
430
    }
202,172✔
431
  }
202,805✔
432
  else {
×
433
    throw DBException("out-of-zone data '" + rr.qname.toLogString() + "' during AXFR of zone '" + d_transaction_qname.toLogString() + "'");
×
434
  }
×
435

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

439
  // SOA needs stripping too! XXX FIXME - also, this should not be here I think
440
  switch (rr.qtype.getCode()) {
202,805✔
441
  case QType::MX:
56✔
442
  case QType::SRV:
76✔
443
  case QType::CNAME:
208✔
444
  case QType::DNAME:
212✔
445
  case QType::NS:
432✔
446
    stripDomainSuffix(&content, d_transaction_qname);
432✔
447
    [[fallthrough]];
432✔
448
  default:
202,805✔
449
    if (d_of && *d_of) {
202,805!
450
      *d_of << qname << "\t" << rr.ttl << "\t" << rr.qtype.toString() << "\t" << content << endl;
202,805✔
451
    }
202,805✔
452
  }
202,805✔
453
  return true;
202,805✔
454
}
202,805✔
455

456
void Bind2Backend::getUpdatedPrimaries(vector<DomainInfo>& changedDomains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
457
{
×
458
  vector<DomainInfo> consider;
×
459
  {
×
460
    auto state = s_state.read_lock();
×
461

NEW
462
    for (const auto& bbd : *state) {
×
463
      // Ignore pending domains unless we are the backend running the
464
      // transaction in which they are being created.
NEW
465
      if (bbd.d_pending && bbd.d_id != d_transaction_id) {
×
NEW
466
        continue;
×
NEW
467
      }
×
NEW
468
      if (bbd.d_kind != DomainInfo::Primary && this->alsoNotify.empty() && bbd.d_also_notify.empty()) {
×
UNCOV
469
        continue;
×
NEW
470
      }
×
471

NEW
472
      DomainInfo info;
×
NEW
473
      info.id = bbd.d_id;
×
NEW
474
      info.zone = bbd.d_name;
×
NEW
475
      info.last_check = bbd.d_lastcheck;
×
NEW
476
      info.notified_serial = bbd.d_lastnotified;
×
NEW
477
      info.backend = this;
×
NEW
478
      info.kind = DomainInfo::Primary;
×
NEW
479
      consider.push_back(std::move(info));
×
480
    }
×
481
  }
×
482

483
  SOAData soadata;
×
484
  for (DomainInfo& di : consider) {
×
485
    soadata.serial = 0;
×
486
    try {
×
487
      this->getSOA(di.zone, di.id, soadata); // we might not *have* a SOA yet, but this might trigger a load of it
×
488
    }
×
489
    catch (...) {
×
490
      continue;
×
491
    }
×
492
    if (di.notified_serial != soadata.serial) {
×
493
      BB2DomainInfo bbd;
×
494
      // Note that we don't need to filter pending domains here as the loop
495
      // initializing [consider] above has already done that filtering.
496
      if (safeGetBBDomainInfo(di.id, &bbd)) {
×
497
        bbd.d_lastnotified = soadata.serial;
×
498
        safePutBBDomainInfo(bbd);
×
499
      }
×
500
      if (di.notified_serial) { // don't do notification storm on startup
×
501
        di.serial = soadata.serial;
×
502
        changedDomains.push_back(std::move(di));
×
503
      }
×
504
    }
×
505
  }
×
506
}
×
507

508
void Bind2Backend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool /* include_disabled */)
509
{
119✔
510
  SOAData soadata;
119✔
511

512
  // prevent deadlock by using getSOA() later on
513
  {
119✔
514
    auto state = s_state.read_lock();
119✔
515
    domains->reserve(state->size());
119✔
516

517
    for (const auto& i : *state) {
279✔
518
      DomainInfo di;
185✔
519
      di.id = i.d_id;
185✔
520
      di.zone = i.d_name;
185✔
521
      di.last_check = i.d_lastcheck;
185✔
522
      di.kind = i.d_kind;
185✔
523
      di.primaries = i.d_primaries;
185✔
524
      di.backend = this;
185✔
525
      domains->push_back(std::move(di));
185✔
526
    };
185✔
527
  }
119✔
528

529
  if (getSerial) {
119✔
530
    for (DomainInfo& di : *domains) {
1,554✔
531
      // do not corrupt di if domain supplied by another backend.
532
      if (di.backend != this)
1,554!
533
        continue;
1,554✔
534
      try {
×
535
        this->getSOA(di.zone, di.id, soadata);
×
536
      }
×
537
      catch (...) {
×
538
        continue;
×
539
      }
×
540
      di.serial = soadata.serial;
×
541
    }
×
542
  }
42✔
543
}
119✔
544

545
void Bind2Backend::getUnfreshSecondaryInfos(vector<DomainInfo>* unfreshDomains)
546
{
8✔
547
  vector<DomainInfo> domains;
8✔
548
  {
8✔
549
    auto state = s_state.read_lock();
8✔
550
    domains.reserve(state->size());
8✔
551
    for (const auto& i : *state) {
72✔
552
      if (i.d_kind != DomainInfo::Secondary)
72!
553
        continue;
×
554
      DomainInfo sd;
72✔
555
      sd.id = i.d_id;
72✔
556
      sd.zone = i.d_name;
72✔
557
      sd.primaries = i.d_primaries;
72✔
558
      sd.last_check = i.d_lastcheck;
72✔
559
      sd.backend = this;
72✔
560
      sd.kind = DomainInfo::Secondary;
72✔
561
      domains.push_back(std::move(sd));
72✔
562
    }
72✔
563
  }
8✔
564
  unfreshDomains->reserve(domains.size());
8✔
565

566
  for (DomainInfo& sd : domains) {
72✔
567
    SOAData soadata;
72✔
568
    soadata.refresh = 0;
72✔
569
    soadata.serial = 0;
72✔
570
    try {
72✔
571
      getSOA(sd.zone, sd.id, soadata); // we might not *have* a SOA yet
72✔
572
    }
72✔
573
    catch (...) {
72✔
574
    }
72✔
575
    sd.serial = soadata.serial;
72✔
576
    if (sd.last_check + soadata.refresh < time(nullptr))
72!
577
      unfreshDomains->push_back(std::move(sd));
72✔
578
  }
72✔
579
}
8✔
580

581
bool Bind2Backend::getDomainInfo(const ZoneName& domain, DomainInfo& info, bool getSerial)
582
{
1,099✔
583
  BB2DomainInfo bbd;
1,099✔
584
  if (!safeGetBBDomainInfo(domain, &bbd))
1,099✔
585
    return false;
648✔
586
  // Ignore pending domains unless we are the backend running the transaction
587
  // in which they are being created. This can't be done in safeGetBBDomainInfo
588
  // because it needs to be a static method.
589
  if (bbd.d_pending && bbd.d_id != d_transaction_id) {
451!
NEW
590
    return false;
×
NEW
591
  }
×
592

593
  info.id = bbd.d_id;
451✔
594
  info.zone = domain;
451✔
595
  info.primaries = bbd.d_primaries;
451✔
596
  info.last_check = bbd.d_lastcheck;
451✔
597
  info.backend = this;
451✔
598
  info.kind = bbd.d_kind;
451✔
599
  info.serial = 0;
451✔
600
  if (getSerial) {
451✔
601
    try {
141✔
602
      SOAData sd;
141✔
603
      sd.serial = 0;
141✔
604

605
      getSOA(bbd.d_name, bbd.d_id, sd); // we might not *have* a SOA yet
141✔
606
      info.serial = sd.serial;
141✔
607
    }
141✔
608
    catch (...) {
141✔
609
    }
72✔
610
  }
141✔
611

612
  return true;
451✔
613
}
451✔
614

615
void Bind2Backend::alsoNotifies(const ZoneName& domain, set<string>* ips)
616
{
4✔
617
  // combine global list with local list
618
  for (const auto& i : this->alsoNotify) {
4!
619
    (*ips).insert(i);
×
620
  }
×
621
  // check metadata too if available
622
  vector<string> meta;
4✔
623
  if (getDomainMetadata(domain, "ALSO-NOTIFY", meta)) {
4!
624
    for (const auto& str : meta) {
×
625
      (*ips).insert(str);
×
626
    }
×
627
  }
×
628
  auto state = s_state.read_lock();
4✔
629
  for (const auto& i : *state) {
4!
630
    if (i.d_name == domain) {
×
631
      for (const auto& it : i.d_also_notify) {
×
632
        (*ips).insert(it);
×
633
      }
×
634
      return;
×
635
    }
×
636
  }
×
637
}
4✔
638

639
// only parses, does NOT add to s_state!
640
void Bind2Backend::parseZoneFile(BB2DomainInfo* bbd)
641
{
2,749✔
642
  NSEC3PARAMRecordContent ns3pr;
2,749✔
643
  bool nsec3zone = false;
2,749✔
644
  if (d_hybrid) {
2,749!
645
    DNSSECKeeper dk(d_slog);
×
646
    nsec3zone = dk.getNSEC3PARAM(bbd->d_name, &ns3pr);
×
647
  }
×
648
  else
2,749✔
649
    nsec3zone = getNSEC3PARAMuncached(bbd->d_name, &ns3pr);
2,749✔
650

651
  auto records = std::make_shared<recordstorage_t>();
2,749✔
652
  ZoneParserTNG zpt(bbd->main_filename(), bbd->d_name, s_binddirectory, d_upgradeContent);
2,749✔
653
  zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
2,749✔
654
  zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
2,749✔
655
  DNSResourceRecord rr;
2,749✔
656
  string hashed;
2,749✔
657
  while (zpt.get(rr)) {
3,277,668✔
658
    if (rr.qtype.getCode() == QType::NSEC || rr.qtype.getCode() == QType::NSEC3 || rr.qtype.getCode() == QType::NSEC3PARAM)
3,274,919!
659
      continue; // we synthesise NSECs on demand
×
660

661
    insertRecord(records, bbd->d_name, rr.qname, rr.qtype, rr.content, rr.ttl, "");
3,274,919✔
662
  }
3,274,919✔
663
  fixupOrderAndAuth(records, bbd->d_name, nsec3zone, ns3pr);
2,749✔
664
  doEmptyNonTerminals(records, bbd->d_name, nsec3zone, ns3pr);
2,749✔
665
  bbd->d_fileinfo = zpt.getFileset();
2,749✔
666
  bbd->d_loaded = true;
2,749✔
667
  bbd->d_checknow = false;
2,749✔
668
  bbd->d_status = "parsed into memory at " + nowTime();
2,749✔
669
  bbd->d_records = LookButDontTouch<recordstorage_t>(std::move(records));
2,749✔
670
  bbd->d_nsec3zone = nsec3zone;
2,749✔
671
  bbd->d_nsec3param = std::move(ns3pr);
2,749✔
672
}
2,749✔
673

674
/** THIS IS AN INTERNAL FUNCTION! It does moadnsparser prio impedance matching
675
    Much of the complication is due to the efforts to benefit from std::string reference counting copy on write semantics */
676
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)
677
{
3,279,690✔
678
  Bind2DNSRecord bdr;
3,279,690✔
679
  bdr.qname = qname;
3,279,690✔
680

681
  if (zoneName.empty())
3,279,690!
682
    ;
×
683
  else if (bdr.qname.isPartOf(zoneName))
3,279,690✔
684
    bdr.qname.makeUsRelative(zoneName);
3,279,388✔
685
  else {
302✔
686
    string msg = "Trying to insert non-zone data, name='" + bdr.qname.toLogString() + "', qtype=" + qtype.toString() + ", zone='" + zoneName.toLogString() + "'";
302✔
687
    if (s_ignore_broken_records) {
302!
688
      SLOG(g_log << Logger::Warning << msg << " ignored" << endl,
302✔
689
           d_slog->info(Logr::Warning, "Non-zone data record ignored", "zone", Logging::Loggable(zoneName), "name", Logging::Loggable(bdr.qname), "qtype", Logging::Loggable(qtype)));
302✔
690
      return;
302✔
691
    }
302✔
692
    throw PDNSException(std::move(msg));
×
693
  }
302✔
694

695
  //  bdr.qname.swap(bdr.qname);
696

697
  if (!records->empty() && bdr.qname == boost::prior(records->end())->qname)
3,279,388✔
698
    bdr.qname = boost::prior(records->end())->qname;
8,936✔
699

700
  bdr.qname = bdr.qname;
3,279,388✔
701
  bdr.qtype = qtype.getCode();
3,279,388✔
702
  bdr.content = content;
3,279,388✔
703
  bdr.nsec3hash = hashed;
3,279,388✔
704

705
  if (auth != nullptr) // Set auth on empty non-terminals
3,279,388✔
706
    bdr.auth = *auth;
4,771✔
707
  else
3,274,617✔
708
    bdr.auth = true;
3,274,617✔
709

710
  bdr.ttl = ttl;
3,279,388✔
711
  records->insert(std::move(bdr));
3,279,388✔
712
}
3,279,388✔
713

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

718
  for (auto i = parts.begin() + 1; i < parts.end(); ++i) {
×
719
    BB2DomainInfo bbd;
×
720
    ZoneName zone(*i);
×
721
    if (safeGetBBDomainInfo(zone, &bbd)) {
×
NEW
722
      if (bbd.d_pending) {
×
NEW
723
        ret << *i << ": not commited yet\n";
×
NEW
724
        continue;
×
NEW
725
      }
×
726
      Bind2Backend bb2;
×
727
      bb2.queueReloadAndStore(bbd.d_id);
×
728
      if (!safeGetBBDomainInfo(zone, &bbd)) // Read the *new* domain status
×
729
        ret << *i << ": [missing]\n";
×
730
      else
×
731
        ret << *i << ": " << (bbd.d_wasRejectedLastReload ? "[rejected]" : "") << "\t" << bbd.d_status << "\n";
×
732
      purgeAuthCaches(zone.operator const DNSName&().toString() + "$");
×
733
      DNSSECKeeper::clearMetaCache(zone);
×
734
    }
×
735
    else
×
NEW
736
      ret << *i << ": no such domain\n";
×
737
  }
×
738
  if (ret.str().empty())
×
739
    ret << "no domains reloaded";
×
740
  return ret.str();
×
741
}
×
742

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

747
  if (parts.size() > 1) {
27!
748
    for (auto i = parts.begin() + 1; i < parts.end(); ++i) {
×
NEW
749
      ret << *i << ": ";
×
750
      BB2DomainInfo bbd;
×
751
      if (safeGetBBDomainInfo(ZoneName(*i), &bbd)) {
×
NEW
752
        if (bbd.d_pending) {
×
NEW
753
          ret << "not commited yet\n";
×
NEW
754
        }
×
NEW
755
        else {
×
NEW
756
          ret << (bbd.d_loaded ? "" : "[rejected]") << "\t" << bbd.d_status << "\n";
×
NEW
757
        }
×
758
      }
×
759
      else {
×
NEW
760
        ret << "no such domain\n";
×
761
      }
×
762
    }
×
763
  }
×
764
  else {
27✔
765
    auto state = s_state.read_lock();
27✔
766
    for (const auto& bbd : *state) {
235✔
767
      ret << bbd.d_name << ": ";
235✔
768
      if (bbd.d_pending) {
235!
NEW
769
        ret << "not commited yet\n";
×
NEW
770
      }
×
771
      else {
235✔
772
        ret << (bbd.d_loaded ? "" : "[rejected]") << "\t" << bbd.d_status << "\n";
235✔
773
      }
235✔
774
    }
235✔
775
  }
27✔
776

777
  if (ret.str().empty())
27!
778
    ret << "no domains passed";
×
779

780
  return ret.str();
27✔
781
}
27✔
782

783
static void printDomainExtendedStatus(ostringstream& ret, const BB2DomainInfo& info)
784
{
×
785
  ret << info.d_name << ": " << std::endl;
×
786
  ret << "\t Status: " << info.d_status << std::endl;
×
787
  ret << "\t Internal ID: " << info.d_id << std::endl;
×
788
  ret << "\t On-disk file: " << info.main_filename() << " (" << info.d_fileinfo.front().second << ")" << std::endl;
×
789
  ret << "\t Kind: ";
×
790
  switch (info.d_kind) {
×
791
  case DomainInfo::Primary:
×
792
    ret << "Primary";
×
793
    break;
×
794
  case DomainInfo::Secondary:
×
795
    ret << "Secondary";
×
796
    break;
×
797
  default:
×
798
    ret << "Native";
×
799
  }
×
800
  ret << std::endl;
×
801
  ret << "\t Primaries: " << std::endl;
×
802
  for (const auto& primary : info.d_primaries) {
×
803
    ret << "\t\t - " << primary.toStringWithPort() << std::endl;
×
804
  }
×
805
  ret << "\t Also Notify: " << std::endl;
×
806
  for (const auto& also : info.d_also_notify) {
×
807
    ret << "\t\t - " << also << std::endl;
×
808
  }
×
809
  ret << "\t Number of records: " << info.d_records.getEntriesCount() << std::endl;
×
810
  ret << "\t Loaded: " << info.d_loaded << std::endl;
×
811
  ret << "\t Check now: " << info.d_checknow << std::endl;
×
812
  ret << "\t Check interval: " << info.getCheckInterval() << std::endl;
×
813
  ret << "\t Last check: " << info.d_lastcheck << std::endl;
×
814
  ret << "\t Last notified: " << info.d_lastnotified << std::endl;
×
815
}
×
816

817
string Bind2Backend::DLDomExtendedStatusHandler(const vector<string>& parts, Utility::pid_t /* ppid */, Logr::log_t /* slog */)
818
{
×
819
  ostringstream ret;
×
820

821
  if (parts.size() > 1) {
×
822
    for (auto i = parts.begin() + 1; i < parts.end(); ++i) {
×
823
      BB2DomainInfo bbd;
×
824
      if (safeGetBBDomainInfo(ZoneName(*i), &bbd)) {
×
NEW
825
        if (bbd.d_pending) {
×
NEW
826
          ret << *i << ": not commited yet\n";
×
NEW
827
          continue;
×
NEW
828
        }
×
829
        printDomainExtendedStatus(ret, bbd);
×
830
      }
×
831
      else {
×
NEW
832
        ret << *i << ": no such domain" << std::endl;
×
833
      }
×
834
    }
×
835
  }
×
836
  else {
×
837
    auto rstate = s_state.read_lock();
×
838
    for (const auto& state : *rstate) {
×
839
      printDomainExtendedStatus(ret, state);
×
840
    }
×
841
  }
×
842

843
  if (ret.str().empty()) {
×
844
    ret << "no domains passed" << std::endl;
×
845
  }
×
846

847
  return ret.str();
×
848
}
×
849

850
string Bind2Backend::DLListRejectsHandler(const vector<string>& /* parts */, Utility::pid_t /* ppid */, Logr::log_t /* slog */)
851
{
×
852
  ostringstream ret;
×
853
  auto rstate = s_state.read_lock();
×
854
  for (const auto& i : *rstate) {
×
855
    if (!i.d_loaded)
×
856
      ret << i.d_name << "\t" << i.d_status << endl;
×
857
  }
×
858
  return ret.str();
×
859
}
×
860

861
string Bind2Backend::DLAddDomainHandler(const vector<string>& parts, Utility::pid_t /* ppid */, Logr::log_t /* slog */)
862
{
12✔
863
  if (parts.size() < 3)
12!
864
    return "ERROR: Domain name and zone filename are required";
×
865

866
  ZoneName domainname(parts[1]);
12✔
867
  const string& filename = parts[2];
12✔
868
  BB2DomainInfo bbd;
12✔
869
  if (safeGetBBDomainInfo(domainname, &bbd)) {
12✔
870
    if (bbd.d_pending) {
6!
NEW
871
      return "Not commited yet";
×
NEW
872
    }
×
873
    return "Already loaded";
6✔
874
  }
6✔
875

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

879
  struct stat buf;
6✔
880
  if (stat(filename.c_str(), &buf) != 0)
6!
881
    return "Unable to load zone " + domainname.toLogString() + " from " + filename + ": " + strerror(errno);
×
882

883
  Bind2Backend bb2; // createdomainentry needs access to our configuration
6✔
884
  bbd = bb2.createDomainEntry(domainname);
6✔
885
  bbd.d_fileinfo.emplace_back(std::make_pair(filename, buf.st_ctime));
6✔
886
  bbd.d_checknow = true;
6✔
887
  bbd.d_loaded = true;
6✔
888
  bbd.d_lastcheck = 0;
6✔
889
  bbd.d_status = "parsing into memory";
6✔
890

891
  safePutBBDomainInfo(bbd);
6✔
892

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

895
  SLOG(g_log << Logger::Warning << "Zone " << domainname << " loaded" << endl,
6!
896
       bb2.d_slog->info(Logr::Info, "Zone loaded", "zone", Logging::Loggable(domainname), "file", Logging::Loggable(filename)));
6✔
897
  return "Loaded zone " + domainname.toLogString() + " from " + filename;
6✔
898
}
6✔
899

900
Bind2Backend::Bind2Backend(const string& suffix, bool loadZones)
901
{
3,511✔
902
  d_getAllDomainMetadataQuery_stmt = nullptr;
3,511✔
903
  d_getDomainMetadataQuery_stmt = nullptr;
3,511✔
904
  d_deleteDomainMetadataQuery_stmt = nullptr;
3,511✔
905
  d_insertDomainMetadataQuery_stmt = nullptr;
3,511✔
906
  d_getDomainKeysQuery_stmt = nullptr;
3,511✔
907
  d_deleteDomainKeyQuery_stmt = nullptr;
3,511✔
908
  d_insertDomainKeyQuery_stmt = nullptr;
3,511✔
909
  d_GetLastInsertedKeyIdQuery_stmt = nullptr;
3,511✔
910
  d_activateDomainKeyQuery_stmt = nullptr;
3,511✔
911
  d_deactivateDomainKeyQuery_stmt = nullptr;
3,511✔
912
  d_getTSIGKeyQuery_stmt = nullptr;
3,511✔
913
  d_setTSIGKeyQuery_stmt = nullptr;
3,511✔
914
  d_deleteTSIGKeyQuery_stmt = nullptr;
3,511✔
915
  d_getTSIGKeysQuery_stmt = nullptr;
3,511✔
916

917
  setArgPrefix("bind" + suffix);
3,511✔
918
  d_logprefix = "[bind" + suffix + "backend]";
3,511✔
919
  if (g_slogStructured) {
3,511✔
920
    d_slog = g_slog->withName("bind" + suffix);
669✔
921
    d_handle.setSLog(d_slog);
669✔
922
  }
669✔
923
  d_hybrid = mustDo("hybrid");
3,511✔
924
  if (d_hybrid && g_zoneCache.isEnabled()) {
3,511!
925
    throw PDNSException("bind-hybrid and the zone cache currently interoperate badly. Please disable the zone cache or stop using bind-hybrid");
×
926
  }
×
927

928
  d_transaction_id = UnknownDomainID;
3,511✔
929
  s_ignore_broken_records = mustDo("ignore-broken-records");
3,511✔
930
  d_upgradeContent = ::arg().mustDo("upgrade-unknown-types");
3,511✔
931

932
  if (!loadZones && d_hybrid)
3,511!
933
    return;
×
934

935
  auto lock = std::scoped_lock(s_startup_lock);
3,511✔
936

937
  setupDNSSEC();
3,511✔
938
  if (s_first == 0) {
3,511✔
939
    return;
2,732✔
940
  }
2,732✔
941

942
  if (loadZones) {
779✔
943
    loadConfig();
317✔
944
    s_first = 0;
317✔
945
  }
317✔
946

947
  DynListener::registerFunc("BIND-ADD-ZONE", &DLAddDomainHandler, "bindbackend: add zone", "<domain> <filename>");
779✔
948
  DynListener::registerFunc("BIND-DOMAIN-EXTENDED-STATUS", &DLDomExtendedStatusHandler, "bindbackend: list the extended status of all domains", "[domains]");
779✔
949
  DynListener::registerFunc("BIND-DOMAIN-STATUS", &DLDomStatusHandler, "bindbackend: list status of all domains", "[domains]");
779✔
950
  DynListener::registerFunc("BIND-LIST-REJECTS", &DLListRejectsHandler, "bindbackend: list rejected domains");
779✔
951
  DynListener::registerFunc("BIND-RELOAD-NOW", &DLReloadNowHandler, "bindbackend: reload domains", "<domains>");
779✔
952
}
779✔
953

954
Bind2Backend::~Bind2Backend()
955
{
3,386✔
956
  freeStatements();
3,386✔
957
} // deallocate statements
3,386✔
958

959
void Bind2Backend::rediscover(string* status)
960
{
×
961
  loadConfig(status);
×
962
}
×
963

964
void Bind2Backend::reload()
965
{
×
966
  auto state = s_state.write_lock();
×
967
  for (const auto& i : *state) {
×
968
    i.d_checknow = true; // being a bit cheeky here, don't index state_t on this (mutable)
×
969
  }
×
970
}
×
971

972
void Bind2Backend::fixupOrderAndAuth(std::shared_ptr<recordstorage_t>& records, const ZoneName& zoneName, bool nsec3zone, const NSEC3PARAMRecordContent& ns3pr)
973
{
2,677✔
974
  bool skip;
2,677✔
975
  DNSName shorter;
2,677✔
976
  set<DNSName> nssets, dssets;
2,677✔
977

978
  for (const auto& bdr : *records) {
3,274,617✔
979
    if (!bdr.qname.isRoot() && bdr.qtype == QType::NS)
3,274,617✔
980
      nssets.insert(bdr.qname);
3,010✔
981
    else if (bdr.qtype == QType::DS)
3,271,607✔
982
      dssets.insert(bdr.qname);
499✔
983
  }
3,274,617✔
984

985
  for (auto iter = records->begin(); iter != records->end(); iter++) {
3,277,294✔
986
    skip = false;
3,274,617✔
987
    shorter = iter->qname;
3,274,617✔
988

989
    if (!iter->qname.isRoot() && shorter.chopOff() && !iter->qname.isRoot()) {
3,274,617!
990
      do {
3,273,507✔
991
        if (nssets.count(shorter) != 0u) {
3,273,507✔
992
          skip = true;
1,359✔
993
          break;
1,359✔
994
        }
1,359✔
995
      } while (shorter.chopOff() && !iter->qname.isRoot());
3,273,507!
996
    }
3,263,450✔
997

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

1000
    if (!skip && nsec3zone && iter->qtype != QType::RRSIG && (iter->auth || (iter->qtype == QType::NS && (ns3pr.d_flags == 0u)) || (dssets.count(iter->qname) != 0u))) {
3,274,617✔
1001
      Bind2DNSRecord bdr = *iter;
1,439,658✔
1002
      bdr.nsec3hash = toBase32Hex(hashQNameWithSalt(ns3pr, bdr.qname + zoneName.operator const DNSName&()));
1,439,658✔
1003
      records->replace(iter, bdr);
1,439,658✔
1004
    }
1,439,658✔
1005

1006
    // cerr<<iter->qname<<"\t"<<QType(iter->qtype).toString()<<"\t"<<iter->nsec3hash<<"\t"<<iter->auth<<endl;
1007
  }
3,274,617✔
1008
}
2,677✔
1009

1010
void Bind2Backend::doEmptyNonTerminals(std::shared_ptr<recordstorage_t>& records, const ZoneName& zoneName, bool nsec3zone, const NSEC3PARAMRecordContent& ns3pr)
1011
{
2,677✔
1012
  bool auth = false;
2,677✔
1013
  DNSName shorter;
2,677✔
1014
  std::unordered_set<DNSName> qnames;
2,677✔
1015
  std::unordered_map<DNSName, bool> nonterm;
2,677✔
1016

1017
  uint32_t maxent = ::arg().asNum("max-ent-entries");
2,677✔
1018

1019
  for (const auto& bdr : *records)
2,677✔
1020
    qnames.insert(bdr.qname);
3,274,617✔
1021

1022
  for (const auto& bdr : *records) {
3,274,617✔
1023

1024
    if (!bdr.auth && bdr.qtype == QType::NS)
3,274,617✔
1025
      auth = (!nsec3zone || (ns3pr.d_flags == 0u));
3,010✔
1026
    else
3,271,607✔
1027
      auth = bdr.auth;
3,271,607✔
1028

1029
    shorter = bdr.qname;
3,274,617✔
1030
    while (shorter.chopOff()) {
6,549,483✔
1031
      if (qnames.count(shorter) == 0u) {
3,274,866✔
1032
        if (!(maxent)) {
9,891!
1033
          SLOG(g_log << Logger::Error << "Zone '" << zoneName << "' has too many empty non terminals." << endl,
×
1034
               d_slog->info(Logr::Error, "Zone has too many empty non terminals.", "zone", Logging::Loggable(zoneName)));
×
1035
          return;
×
1036
        }
×
1037

1038
        if (nonterm.count(shorter) == 0u) {
9,891✔
1039
          nonterm.emplace(shorter, auth);
4,771✔
1040
          --maxent;
4,771✔
1041
        }
4,771✔
1042
        else if (auth)
5,120✔
1043
          nonterm[shorter] = true;
5,024✔
1044
      }
9,891✔
1045
    }
3,274,866✔
1046
  }
3,274,617✔
1047

1048
  DNSResourceRecord rr;
2,677✔
1049
  rr.qtype = "#0";
2,677✔
1050
  rr.content = "";
2,677✔
1051
  rr.ttl = 0;
2,677✔
1052
  for (auto& nt : nonterm) {
4,784✔
1053
    string hashed;
4,771✔
1054
    rr.qname = nt.first + zoneName.operator const DNSName&();
4,771✔
1055
    if (nsec3zone && nt.second)
4,771✔
1056
      hashed = toBase32Hex(hashQNameWithSalt(ns3pr, rr.qname));
1,853✔
1057
    insertRecord(records, zoneName, rr.qname, rr.qtype, rr.content, rr.ttl, hashed, &nt.second);
4,771✔
1058

1059
    // cerr<<rr.qname<<"\t"<<rr.qtype.toString()<<"\t"<<hashed<<"\t"<<nt.second<<endl;
1060
  }
4,771✔
1061
}
2,677✔
1062

1063
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
1064
{
317✔
1065
  static domainid_t domain_id = 1;
317✔
1066

1067
  if (!getArg("config").empty()) {
317!
1068
    BindParser BP;
317✔
1069
    try {
317✔
1070
      BP.parse(getArg("config"));
317✔
1071
    }
317✔
1072
    catch (PDNSException& ae) {
317✔
1073
      SLOG(g_log << Logger::Error << "Error parsing bind configuration: " << ae.reason << endl,
×
1074
           d_slog->error(Logr::Error, ae.reason, "Error parsing bind configuration"));
×
1075
      throw;
×
1076
    }
×
1077

1078
    vector<BindDomainInfo> domains = BP.getDomains();
317✔
1079
    this->alsoNotify = BP.getAlsoNotify();
317✔
1080

1081
    s_binddirectory = BP.getDirectory();
317✔
1082
    //    ZP.setDirectory(d_binddirectory);
1083

1084
    SLOG(g_log << Logger::Warning << d_logprefix << " Parsing " << domains.size() << " domain(s), will report when done" << endl,
317✔
1085
         d_slog->info(Logr::Info, "Parsing " + std::to_string(domains.size()) + " domain(s), will report when done"));
317✔
1086

1087
    set<ZoneName> oldnames;
317✔
1088
    set<ZoneName> newnames;
317✔
1089
    {
317✔
1090
      auto state = s_state.read_lock();
317✔
1091
      for (const BB2DomainInfo& bbd : *state) {
317!
1092
        oldnames.insert(bbd.d_name);
×
1093
      }
×
1094
    }
317✔
1095
    int rejected = 0;
317✔
1096
    int newdomains = 0;
317✔
1097

1098
    struct stat st;
317✔
1099

1100
    for (auto& domain : domains) {
2,803✔
1101
      if (stat(domain.filename.c_str(), &st) == 0) {
2,671✔
1102
        domain.d_dev = st.st_dev;
2,599✔
1103
        domain.d_ino = st.st_ino;
2,599✔
1104
      }
2,599✔
1105
    }
2,671✔
1106

1107
    sort(domains.begin(), domains.end()); // put stuff in inode order
317✔
1108
    for (const auto& domain : domains) {
2,803✔
1109
      if (!(domain.hadFileDirective)) {
2,671!
1110
        SLOG(g_log << Logger::Warning << d_logprefix << " Zone '" << domain.name << "' has no 'file' directive set in " << getArg("config") << endl,
×
1111
             d_slog->info(Logr::Warning, "Zone has no 'file' directive set", "zone", Logging::Loggable(domain.name), "filename", Logging::Loggable(getArg("config"))));
×
1112
        rejected++;
×
1113
        continue;
×
1114
      }
×
1115

1116
      if (domain.type.empty()) {
2,671!
1117
        SLOG(g_log << Logger::Notice << d_logprefix << " Zone '" << domain.name << "' has no type specified, assuming 'native'" << endl,
×
1118
             d_slog->info(Logr::Notice, "Zone has no type specified, assuming 'native'", "zone", Logging::Loggable(domain.name)));
×
1119
      }
×
1120
      if (domain.type != "primary" && domain.type != "secondary" && domain.type != "native" && !domain.type.empty() && domain.type != "master" && domain.type != "slave") {
2,671!
1121
        SLOG(g_log << Logger::Warning << d_logprefix << " Warning! Skipping zone '" << domain.name << "' because type '" << domain.type << "' is invalid" << endl,
×
1122
             d_slog->info(Logr::Warning, "Skipping zone because type is invalid", "zone", Logging::Loggable(domain.name), "type", Logging::Loggable(domain.type)));
×
1123
        rejected++;
×
1124
        continue;
×
1125
      }
×
1126

1127
      BB2DomainInfo bbd;
2,671✔
1128
      bool isNew = false;
2,671✔
1129

1130
      if (safeGetBBDomainInfo(domain.name, &bbd)) {
2,671!
NEW
1131
        if (bbd.d_pending) {
×
NEW
1132
          SLOG(g_log << Logger::Warning << d_logprefix << " Warning! Skipping zone '" << domain.name << "' because it is not commited yet" << endl,
×
NEW
1133
               d_slog->info(Logr::Warning, "Skipping zone because it is not commited yet", "zone", Logging::Loggable(domain.name)));
×
NEW
1134
          rejected++;
×
NEW
1135
          continue;
×
NEW
1136
        }
×
NEW
1137
      }
×
1138
      else {
2,671✔
1139
        isNew = true;
2,671✔
1140
        bbd.d_id = domain_id++;
2,671✔
1141
        bbd.setCheckInterval(getArgAsNum("check-interval"));
2,671✔
1142
        bbd.d_lastnotified = 0;
2,671✔
1143
        bbd.d_loaded = false;
2,671✔
1144
      }
2,671✔
1145

1146
      // overwrite what we knew about the domain
1147
      bbd.d_name = domain.name;
2,671✔
1148
      bool filenameChanged = bbd.d_fileinfo.empty() || (bbd.main_filename() != domain.filename);
2,671!
1149
      bool addressesChanged = (bbd.d_primaries != domain.primaries || bbd.d_also_notify != domain.alsoNotify);
2,671!
1150
      // Preserve existing fileinfo in case we won't reread anything.
1151
      if (filenameChanged) {
2,671!
1152
        bbd.d_fileinfo.clear();
2,671✔
1153
        bbd.d_fileinfo.emplace_back(std::make_pair(domain.filename, 0));
2,671✔
1154
      }
2,671✔
1155
      bbd.d_primaries = domain.primaries;
2,671✔
1156
      bbd.d_also_notify = domain.alsoNotify;
2,671✔
1157

1158
      DomainInfo::DomainKind kind = DomainInfo::Native;
2,671✔
1159
      if (domain.type == "primary" || domain.type == "master") {
2,671!
1160
        kind = DomainInfo::Primary;
2,599✔
1161
      }
2,599✔
1162
      if (domain.type == "secondary" || domain.type == "slave") {
2,671!
1163
        kind = DomainInfo::Secondary;
72✔
1164
      }
72✔
1165

1166
      bool kindChanged = (bbd.d_kind != kind);
2,671✔
1167
      bbd.d_kind = kind;
2,671✔
1168

1169
      newnames.insert(bbd.d_name);
2,671✔
1170
      if (filenameChanged || !bbd.d_loaded || !bbd.current()) {
2,671!
1171
        SLOG(g_log << Logger::Info << d_logprefix << " parsing '" << domain.name << "' from file '" << domain.filename << "'" << endl,
2,671✔
1172
             d_slog->info(Logr::Info, "Parsing zone from file", "zone", Logging::Loggable(domain.name), "file", Logging::Loggable(domain.filename)));
2,671✔
1173

1174
        try {
2,671✔
1175
          parseZoneFile(&bbd);
2,671✔
1176
        }
2,671✔
1177
        catch (PDNSException& ae) {
2,671✔
1178
          ostringstream msg;
×
1179
          msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.reason;
×
1180

1181
          if (status != nullptr)
×
1182
            *status += msg.str();
×
1183
          bbd.d_status = msg.str();
×
1184

1185
          SLOG(g_log << Logger::Warning << d_logprefix << msg.str() << endl,
×
1186
               d_slog->error(Logr::Error, ae.reason, "Error in zone file", "zone", Logging::Loggable(domain.name), "file", Logging::Loggable(domain.filename)));
×
1187
          rejected++;
×
1188
        }
×
1189
        catch (std::system_error& ae) {
2,671✔
1190
          ostringstream msg;
72✔
1191
          bool missingNewSecondary = ae.code().value() == ENOENT && isNew && kind == DomainInfo::Secondary;
72!
1192
          if (missingNewSecondary) {
72!
1193
            msg << " error at " + nowTime() << " no file found for new secondary domain '" << domain.name << "'. Has not been AXFR'd yet";
72✔
1194
          }
72✔
1195
          else {
×
1196
            msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.what();
×
1197
          }
×
1198

1199
          if (status != nullptr)
72!
1200
            *status += msg.str();
×
1201
          bbd.d_status = msg.str();
72✔
1202
          SLOG(
72!
1203
            g_log << Logger::Warning << d_logprefix << msg.str() << endl,
72✔
1204
            if (missingNewSecondary) {
72✔
1205
              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✔
1206
            } else {
72✔
1207
              d_slog->error(Logr::Warning, ae.what(), "Parse error", "domain", Logging::Loggable(domain.name), "file", Logging::Loggable(domain.filename));
72✔
1208
            });
72✔
1209
          rejected++;
72✔
1210
        }
72✔
1211
        catch (std::exception& ae) {
2,671✔
1212
          ostringstream msg;
×
1213
          msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.what();
×
1214

1215
          if (status != nullptr)
×
1216
            *status += msg.str();
×
1217
          bbd.d_status = msg.str();
×
1218

1219
          SLOG(g_log << Logger::Warning << d_logprefix << msg.str() << endl,
×
1220
               d_slog->error(Logr::Warning, ae.what(), "Parse error", "domain", Logging::Loggable(domain.name), "file", Logging::Loggable(domain.filename)));
×
1221
          rejected++;
×
1222
        }
×
1223
        safePutBBDomainInfo(bbd);
2,671✔
1224
      }
2,671✔
1225
      else if (addressesChanged || kindChanged) {
×
1226
        safePutBBDomainInfo(bbd);
×
1227
      }
×
1228
    }
2,671✔
1229
    vector<ZoneName> diff;
317✔
1230

1231
    set_difference(oldnames.begin(), oldnames.end(), newnames.begin(), newnames.end(), back_inserter(diff));
317✔
1232
    unsigned int remdomains = diff.size();
317✔
1233

1234
    for (const ZoneName& name : diff) {
317!
1235
      safeRemoveBBDomainInfo(name);
×
1236
    }
×
1237

1238
    // count number of entirely new domains
1239
    diff.clear();
317✔
1240
    set_difference(newnames.begin(), newnames.end(), oldnames.begin(), oldnames.end(), back_inserter(diff));
317✔
1241
    newdomains = diff.size();
317✔
1242

1243
    ostringstream msg;
317✔
1244
    msg << " Done parsing domains, " << rejected << " rejected, " << newdomains << " new, " << remdomains << " removed";
317✔
1245
    if (status != nullptr)
317!
1246
      *status = msg.str();
×
1247

1248
    SLOG(
317!
1249
      g_log << Logger::Error << d_logprefix << msg.str() << endl,
317✔
1250
      if (rejected == 0) {
317✔
1251
        d_slog->info(Logr::Info, "Done parsing domains", "new", Logging::Loggable(newdomains), "removed", Logging::Loggable(remdomains));
317✔
1252
      } else {
317✔
1253
        d_slog->info(Logr::Error, "Done parsing domains", "new", Logging::Loggable(newdomains), "removed", Logging::Loggable(remdomains), "rejected", Logging::Loggable(rejected));
317✔
1254
      });
317✔
1255
  }
317✔
1256
}
317✔
1257

1258
// NOLINTNEXTLINE(readability-identifier-length)
1259
void Bind2Backend::queueReloadAndStore(domainid_t id)
1260
{
78✔
1261
  BB2DomainInfo bbold;
78✔
1262
  try {
78✔
1263
    if (!safeGetBBDomainInfo(id, &bbold) || bbold.d_pending) {
78!
1264
      return;
×
NEW
1265
    }
×
1266
    bbold.d_checknow = false;
78✔
1267
    BB2DomainInfo bbnew(bbold);
78✔
1268
    /* make sure that nothing will be able to alter the existing records,
1269
       we will load them from the zone file instead */
1270
    bbnew.d_records = LookButDontTouch<recordstorage_t>();
78✔
1271
    parseZoneFile(&bbnew);
78✔
1272
    bbnew.d_wasRejectedLastReload = false;
78✔
1273
    safePutBBDomainInfo(bbnew);
78✔
1274
    SLOG(g_log << Logger::Warning << "Zone '" << bbnew.d_name << "' (" << bbnew.main_filename() << ") reloaded" << endl,
78✔
1275
         d_slog->info(Logr::Info, "Zone reloaded", "zone", Logging::Loggable(bbnew.d_name), "file", Logging::Loggable(bbnew.main_filename())));
78✔
1276
  }
78✔
1277
  catch (PDNSException& ae) {
78✔
1278
    ostringstream msg;
×
1279
    msg << " error at " + nowTime() + " parsing '" << bbold.d_name << "' from file '" << bbold.main_filename() << "': " << ae.reason;
×
1280
    SLOG(g_log << Logger::Warning << "Error parsing '" << bbold.d_name << "' from file '" << bbold.main_filename() << "': " << ae.reason << endl,
×
1281
         d_slog->error(Logr::Error, ae.reason, "Error reloading zone", "zone", Logging::Loggable(bbold.d_name), "file", Logging::Loggable(bbold.main_filename())));
×
1282
    bbold.d_status = msg.str();
×
1283
    bbold.d_lastcheck = time(nullptr);
×
1284
    bbold.d_wasRejectedLastReload = true;
×
1285
    safePutBBDomainInfo(bbold);
×
1286
  }
×
1287
  catch (std::exception& ae) {
78✔
1288
    ostringstream msg;
×
1289
    msg << " error at " + nowTime() + " parsing '" << bbold.d_name << "' from file '" << bbold.main_filename() << "': " << ae.what();
×
1290
    SLOG(g_log << Logger::Warning << "Error parsing '" << bbold.d_name << "' from file '" << bbold.main_filename() << "': " << ae.what() << endl,
×
1291
         d_slog->error(Logr::Error, ae.what(), "Error reloading zone", "zone", Logging::Loggable(bbold.d_name), "file", Logging::Loggable(bbold.main_filename())));
×
1292
    bbold.d_status = msg.str();
×
1293
    bbold.d_lastcheck = time(nullptr);
×
1294
    bbold.d_wasRejectedLastReload = true;
×
1295
    safePutBBDomainInfo(bbold);
×
1296
  }
×
1297
}
78✔
1298

1299
bool Bind2Backend::findBeforeAndAfterUnhashed(std::shared_ptr<const recordstorage_t>& records, const DNSName& qname, DNSName& /* unhashed */, DNSName& before, DNSName& after)
1300
{
2,740✔
1301
  // for(const auto& record: *records)
1302
  //   cerr<<record.qname<<"\t"<<makeHexDump(record.qname.toDNSString())<<endl;
1303

1304
  recordstorage_t::const_iterator iterBefore, iterAfter;
2,740✔
1305

1306
  iterBefore = iterAfter = records->upper_bound(qname.makeLowerCase());
2,740✔
1307

1308
  if (iterBefore != records->begin())
2,740!
1309
    --iterBefore;
2,740✔
1310
  while ((!iterBefore->auth && iterBefore->qtype != QType::NS) || !iterBefore->qtype)
3,269✔
1311
    --iterBefore;
529✔
1312
  before = iterBefore->qname;
2,740✔
1313

1314
  if (iterAfter == records->end()) {
2,740✔
1315
    iterAfter = records->begin();
376✔
1316
  }
376✔
1317
  else {
2,364✔
1318
    while ((!iterAfter->auth && iterAfter->qtype != QType::NS) || !iterAfter->qtype) {
3,573✔
1319
      ++iterAfter;
1,209✔
1320
      if (iterAfter == records->end()) {
1,209!
1321
        iterAfter = records->begin();
×
1322
        break;
×
1323
      }
×
1324
    }
1,209✔
1325
  }
2,364✔
1326
  after = iterAfter->qname;
2,740✔
1327

1328
  return true;
2,740✔
1329
}
2,740✔
1330

1331
// NOLINTNEXTLINE(readability-identifier-length)
1332
bool Bind2Backend::getBeforeAndAfterNamesAbsolute(domainid_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
1333
{
7,109✔
1334
  BB2DomainInfo bbd;
7,109✔
1335
  if (!safeGetBBDomainInfo(id, &bbd))
7,109!
1336
    return false;
×
1337
  // Ignore pending domains unless we are the backend running the transaction
1338
  // in which they are being created. This can't be done in safeGetBBDomainInfo
1339
  // because it needs to be a static method.
1340
  if (bbd.d_pending && bbd.d_id != d_transaction_id) {
7,109!
NEW
1341
    return false;
×
NEW
1342
  }
×
1343

1344
  shared_ptr<const recordstorage_t> records = bbd.d_records.get();
7,109✔
1345
  if (!bbd.d_nsec3zone) {
7,109✔
1346
    return findBeforeAndAfterUnhashed(records, qname, unhashed, before, after);
2,740✔
1347
  }
2,740✔
1348
  else {
4,369✔
1349
    const auto& hashindex = boost::multi_index::get<NSEC3Tag>(*records);
4,369✔
1350

1351
    // for(auto iter = first; iter != hashindex.end(); iter++)
1352
    //  cerr<<iter->nsec3hash<<endl;
1353

1354
    auto first = hashindex.upper_bound("");
4,369✔
1355
    auto iter = hashindex.upper_bound(qname.toStringNoDot());
4,369✔
1356

1357
    if (iter == hashindex.end()) {
4,369✔
1358
      --iter;
360✔
1359
      before = DNSName(iter->nsec3hash);
360✔
1360
      after = DNSName(first->nsec3hash);
360✔
1361
    }
360✔
1362
    else {
4,009✔
1363
      after = DNSName(iter->nsec3hash);
4,009✔
1364
      if (iter != first)
4,009✔
1365
        --iter;
3,920✔
1366
      else
89✔
1367
        iter = --hashindex.end();
89✔
1368
      before = DNSName(iter->nsec3hash);
4,009✔
1369
    }
4,009✔
1370
    unhashed = iter->qname + bbd.d_name.operator const DNSName&();
4,369✔
1371

1372
    return true;
4,369✔
1373
  }
4,369✔
1374
}
7,109✔
1375

1376
void Bind2Backend::lookup(const QType& qtype, const DNSName& qname, domainid_t zoneId, DNSPacket* /* pkt_p */)
1377
{
4,189✔
1378
  d_handle.reset();
4,189✔
1379

1380
  static bool mustlog = ::arg().mustDo("query-logging");
4,189✔
1381

1382
  bool found = false;
4,189✔
1383
  ZoneName domain;
4,189✔
1384
  BB2DomainInfo bbd;
4,189✔
1385

1386
  if (mustlog) {
4,189!
1387
    SLOG(g_log << Logger::Warning << "Lookup for '" << qtype.toString() << "' of '" << qname << "' within zoneID " << zoneId << endl,
×
1388
         d_slog->info(Logr::Warning, "Record lookup", "name", Logging::Loggable(qname), "qtype", Logging::Loggable(qtype), "zone id", Logging::Loggable(zoneId)));
×
1389
  }
×
1390

1391
  if (zoneId != UnknownDomainID) {
4,189✔
1392
    found = safeGetBBDomainInfo(zoneId, &bbd) && qname.isPartOf(bbd.d_name);
3,577!
1393
    // Ignore pending domains unless we are the backend running the transaction
1394
    // in which they are being created. This can't be done in safeGetBBDomainInfo
1395
    // because it needs to be a static method.
1396
    if (found && bbd.d_pending && bbd.d_id != d_transaction_id) {
3,577!
NEW
1397
      found = false;
×
NEW
1398
    }
×
1399
    if (found) {
3,577!
1400
      domain = std::move(bbd.d_name);
3,577✔
1401
    }
3,577✔
1402
  }
3,577✔
1403
  else {
612✔
1404
    domain = ZoneName(qname);
612✔
1405
    do {
615✔
1406
      found = safeGetBBDomainInfo(domain, &bbd);
615✔
1407
      // Ignore pending domains unless we are the backend running the transaction
1408
      // in which they are being created. This can't be done in safeGetBBDomainInfo
1409
      // because it needs to be a static method.
1410
      if (found && bbd.d_pending && bbd.d_id != d_transaction_id) {
615!
NEW
1411
        found = false;
×
NEW
1412
      }
×
1413
    } while (!found && qtype != QType::SOA && domain.chopOff());
615✔
1414
  }
612✔
1415

1416
  if (!found) {
4,189✔
1417
    if (mustlog) {
24!
1418
      SLOG(g_log << Logger::Warning << "Found no authoritative zone for '" << qname << "' and/or id " << zoneId << endl,
×
1419
           d_slog->info(Logr::Warning, "Found no authoritative zone", "name", Logging::Loggable(qname), "zone id", Logging::Loggable(zoneId)));
×
1420
    }
×
1421
    d_handle.d_list = false;
24✔
1422
    return;
24✔
1423
  }
24✔
1424

1425
  if (mustlog) {
4,165!
1426
    SLOG(g_log << Logger::Warning << "Found a zone '" << domain << "' (with id " << bbd.d_id << ") that might contain data " << endl,
×
1427
         d_slog->info(Logr::Warning, "Found authoritative zone", "zone", Logging::Loggable(domain), "zone id", Logging::Loggable(bbd.d_id)));
×
1428
  }
×
1429

1430
  d_handle.id = bbd.d_id;
4,165✔
1431
  d_handle.qname = qname.makeRelative(domain); // strip domain name
4,165✔
1432
  d_handle.qtype = qtype;
4,165✔
1433
  d_handle.domain = std::move(domain);
4,165✔
1434

1435
  if (!bbd.current()) {
4,165✔
1436
    SLOG(g_log << Logger::Warning << "Zone '" << d_handle.domain << "' (" << bbd.main_filename() << ") needs reloading" << endl,
6!
1437
         d_slog->info(Logr::Warning, "Zone needs reloading", "zone", Logging::Loggable(d_handle.domain), "file", Logging::Loggable(bbd.main_filename())));
6✔
1438
    queueReloadAndStore(bbd.d_id);
6✔
1439
    if (!safeGetBBDomainInfo(d_handle.domain, &bbd))
6!
1440
      throw DBException("Zone '" + bbd.d_name.toLogString() + "' (" + bbd.main_filename() + ") gone after reload"); // if we don't throw here, we crash for some reason
×
1441
  }
6✔
1442

1443
  if (!bbd.d_loaded) {
4,165✔
1444
    d_handle.reset();
216✔
1445
    throw DBException("Zone for '" + d_handle.domain.toLogString() + "' in '" + bbd.main_filename() + "' not loaded (file missing, corrupt or primary dead)"); // fsck
216✔
1446
  }
216✔
1447

1448
  d_handle.d_records = bbd.d_records.get();
3,949✔
1449

1450
  if (d_handle.d_records->empty()) {
3,949!
1451
    DLOG(SLOG(g_log << "Query with no results" << endl,
×
1452
              d_slog->info(Logr::Debug, "No results", "name", Logging::Loggable(qname), "qtype", Logging::Loggable(qtype))));
×
1453
  }
×
1454

1455
  d_handle.mustlog = mustlog;
3,949✔
1456

1457
  const auto& hashedidx = boost::multi_index::get<UnorderedNameTag>(*d_handle.d_records);
3,949✔
1458
  auto range = hashedidx.equal_range(d_handle.qname);
3,949✔
1459

1460
  d_handle.d_list = false;
3,949✔
1461
  d_handle.d_iter = range.first;
3,949✔
1462
  d_handle.d_end_iter = range.second;
3,949✔
1463
}
3,949✔
1464

1465
bool Bind2Backend::get(DNSResourceRecord& r)
1466
{
197,976✔
1467
  if (!d_handle.d_records) {
197,976✔
1468
    if (d_handle.mustlog) {
24!
1469
      SLOG(g_log << Logger::Warning << "There were no answers" << endl,
×
1470
           d_slog->info(Logr::Warning, "No answers"));
×
1471
    }
×
1472
    return false;
24✔
1473
  }
24✔
1474

1475
  if (!d_handle.get(r)) {
197,952✔
1476
    if (d_handle.mustlog) {
4,254!
1477
      SLOG(g_log << Logger::Warning << "End of answers" << endl,
×
1478
           d_slog->info(Logr::Warning, "No more answers"));
×
1479
    }
×
1480

1481
    d_handle.reset();
4,254✔
1482

1483
    return false;
4,254✔
1484
  }
4,254✔
1485
  if (d_handle.mustlog) {
193,698!
1486
    SLOG(g_log << Logger::Warning << "Returning: '" << r.qtype.toString() << "' of '" << r.qname << "', content: '" << r.content << "'" << endl,
×
1487
         d_slog->info(Logr::Warning, "Returning record", "name", Logging::Loggable(r.qname), "type", Logging::Loggable(r.qtype), "content", Logging::Loggable(r.content)));
×
1488
  }
×
1489
  return true;
193,698✔
1490
}
197,952✔
1491

1492
void Bind2Backend::lookupEnd()
1493
{
26✔
1494
  d_handle.reset();
26✔
1495
}
26✔
1496

1497
bool Bind2Backend::handle::get(DNSResourceRecord& r)
1498
{
197,952✔
1499
  if (d_list)
197,952✔
1500
    return get_list(r);
188,936✔
1501
  else
9,016✔
1502
    return get_normal(r);
9,016✔
1503
}
197,952✔
1504

1505
void Bind2Backend::handle::reset()
1506
{
9,016✔
1507
  d_records.reset();
9,016✔
1508
  qname.clear();
9,016✔
1509
  mustlog = false;
9,016✔
1510
}
9,016✔
1511

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

1517
  if (d_iter == d_end_iter) {
9,016✔
1518
    return false;
3,923✔
1519
  }
3,923✔
1520

1521
  while (d_iter != d_end_iter && !(qtype.getCode() == QType::ANY || (d_iter)->qtype == qtype.getCode())) {
7,850!
1522
    DLOG(SLOG(g_log << Logger::Warning << "Skipped " << qname << "/" << QType(d_iter->qtype).toString() << ": '" << d_iter->content << "'" << endl,
2,757✔
1523
              d_slog->info(Logr::Debug, "Skipped record", "name", Logging::Loggable(qname), "type", Logging::Loggable(d_iter->qtype), "content", Logging::Loggable(d_iter->content))));
2,757✔
1524
    d_iter++;
2,757✔
1525
  }
2,757✔
1526
  if (d_iter == d_end_iter) {
5,093!
1527
    return false;
×
1528
  }
×
1529
  DLOG(SLOG(g_log << "Bind2Backend get() returning a rr with a " << QType(d_iter->qtype).getCode() << endl,
5,093✔
1530
            d_slog->info(Logr::Debug, "Bind2Backend get() returning a rr", "type", Logging::Loggable(d_iter->qtype))));
5,093✔
1531

1532
  const DNSName& domainName(domain);
5,093✔
1533
  r.qname = qname.empty() ? domainName : (qname + domainName);
5,093!
1534
  r.domain_id = id;
5,093✔
1535
  r.content = (d_iter)->content;
5,093✔
1536
  //  r.domain_id=(d_iter)->domain_id;
1537
  r.qtype = (d_iter)->qtype;
5,093✔
1538
  r.ttl = (d_iter)->ttl;
5,093✔
1539

1540
  //if(!d_iter->auth && r.qtype.getCode() != QType::A && r.qtype.getCode()!=QType::AAAA && r.qtype.getCode() != QType::NS)
1541
  //  cerr<<"Warning! Unauth response for qtype "<< r.qtype.toString() << " for '"<<r.qname<<"'"<<endl;
1542
  r.auth = d_iter->auth;
5,093✔
1543

1544
  d_iter++;
5,093✔
1545

1546
  return true;
5,093✔
1547
}
5,093✔
1548

1549
bool Bind2Backend::list(const ZoneName& /* target */, domainid_t domainId, bool /* include_disabled */)
1550
{
331✔
1551
  BB2DomainInfo bbd;
331✔
1552

1553
  if (!safeGetBBDomainInfo(domainId, &bbd)) {
331!
1554
    return false;
×
1555
  }
×
1556
  // Ignore pending domains unless we are the backend running the transaction
1557
  // in which they are being created. This can't be done in safeGetBBDomainInfo
1558
  // because it needs to be a static method.
1559
  if (bbd.d_pending && bbd.d_id != d_transaction_id) {
331!
NEW
1560
    return false;
×
NEW
1561
  }
×
1562

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

1567
  if (!bbd.d_loaded) {
331!
1568
    throw PDNSException("zone was not loaded, perhaps because of: " + bbd.d_status);
×
1569
  }
×
1570

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

1575
  d_handle.id = domainId;
331✔
1576
  d_handle.domain = bbd.d_name;
331✔
1577
  d_handle.d_list = true;
331✔
1578
  return true;
331✔
1579
}
331✔
1580

1581
bool Bind2Backend::handle::get_list(DNSResourceRecord& r)
1582
{
188,936✔
1583
  if (d_qname_iter != d_qname_end) {
188,936✔
1584
    const DNSName& domainName(domain);
188,605✔
1585
    r.qname = d_qname_iter->qname.empty() ? domainName : (d_qname_iter->qname + domainName);
188,605!
1586
    r.domain_id = id;
188,605✔
1587
    r.content = (d_qname_iter)->content;
188,605✔
1588
    r.qtype = (d_qname_iter)->qtype;
188,605✔
1589
    r.ttl = (d_qname_iter)->ttl;
188,605✔
1590
    r.auth = d_qname_iter->auth;
188,605✔
1591
    d_qname_iter++;
188,605✔
1592
    return true;
188,605✔
1593
  }
188,605✔
1594
  return false;
331✔
1595
}
188,936✔
1596

1597
bool Bind2Backend::autoPrimariesList(std::vector<AutoPrimary>& primaries)
1598
{
×
1599
  if (getArg("autoprimary-config").empty())
×
1600
    return false;
×
1601

1602
  std::string filename(getArg("autoprimaries"));
×
1603
  ifstream c_if(filename, std::ios::in);
×
1604
  if (!c_if) {
×
1605
    SLOG(g_log << Logger::Error << "Unable to open autoprimaries file for read: " << stringerror() << endl,
×
1606
         d_slog->error(Logr::Error, errno, "Unable to open autoprimaries file", "file", Logging::Loggable(filename)));
×
1607
    return false;
×
1608
  }
×
1609

1610
  string line, sip, saccount;
×
1611
  while (getline(c_if, line)) {
×
1612
    std::istringstream ii(line);
×
1613
    ii >> sip;
×
1614
    if (!sip.empty()) {
×
1615
      ii >> saccount;
×
1616
      primaries.emplace_back(sip, "", saccount);
×
1617
    }
×
1618
  }
×
1619

1620
  c_if.close();
×
1621
  return true;
×
1622
}
×
1623

1624
bool Bind2Backend::autoPrimaryBackend(const string& ipAddress, const ZoneName& /* domain */, const vector<DNSResourceRecord>& /* nsset */, string* /* nameserver */, string* account, DNSBackend** backend)
1625
{
×
1626
  // Check whether we have a configfile available.
1627
  if (getArg("autoprimary-config").empty())
×
1628
    return false;
×
1629

1630
  std::string filename(getArg("autoprimaries"));
×
1631
  ifstream c_if(filename.c_str(), std::ios::in); // this was nocreate?
×
1632
  if (!c_if) {
×
1633
    SLOG(g_log << Logger::Error << "Unable to open autoprimaries file for read: " << stringerror() << endl,
×
1634
         d_slog->error(Logr::Error, errno, "Unable to open autoprimaries file", "file", Logging::Loggable(filename)));
×
1635
    return false;
×
1636
  }
×
1637

1638
  // Format:
1639
  // <ip> <accountname>
1640
  string line, sip, saccount;
×
1641
  while (getline(c_if, line)) {
×
1642
    std::istringstream ii(line);
×
1643
    ii >> sip;
×
1644
    if (sip == ipAddress) {
×
1645
      ii >> saccount;
×
1646
      break;
×
1647
    }
×
1648
  }
×
1649
  c_if.close();
×
1650

1651
  if (sip != ipAddress) { // ip not found in authorization list - reject
×
1652
    return false;
×
1653
  }
×
1654

1655
  // ip authorized as autoprimary - accept
1656
  *backend = this;
×
1657
  if (saccount.length() > 0)
×
1658
    *account = saccount.c_str();
×
1659

1660
  return true;
×
1661
}
×
1662

1663
BB2DomainInfo Bind2Backend::createDomainEntry(const ZoneName& domain)
1664
{
6✔
1665
  domainid_t newid = 1;
6✔
1666
  { // Find a free zone id nr.
6✔
1667
    auto state = s_state.read_lock();
6✔
1668
    if (!state->empty()) {
6!
1669
      newid = state->rbegin()->d_id + 1;
6✔
1670
    }
6✔
1671
  }
6✔
1672

1673
  BB2DomainInfo bbd;
6✔
1674
  bbd.d_kind = DomainInfo::Native;
6✔
1675
  bbd.d_id = newid;
6✔
1676
  bbd.d_records = std::make_shared<recordstorage_t>();
6✔
1677
  bbd.d_name = domain;
6✔
1678
  bbd.setCheckInterval(getArgAsNum("check-interval"));
6✔
1679

1680
  return bbd;
6✔
1681
}
6✔
1682

1683
bool Bind2Backend::createSecondaryDomain(const string& ipAddress, const ZoneName& domain, const string& /* nameserver */, const string& account, DomainInfo& info, bool startTransaction)
1684
{
×
NEW
1685
  info.backend = this; // to be able to abortTransaction if the operation fails
×
1686

UNCOV
1687
  std::string domainname = domain.toStringNoDot();
×
1688

1689
  // Reject domain name if it embeds quotes; this may happen if 8bit-dns is
1690
  // used, and bind currently does not allow for character escapes in zone
1691
  // names.
1692
  if (domainname.find_first_of("\"") != std::string::npos) {
×
1693
    SLOG(g_log << Logger::Error << d_logprefix
×
1694
               << " Unable to accept autosecondary zone '" << domain
×
1695
               << "' from autoprimary " << ipAddress
×
1696
               << " due to unauthorized characters in domain name for bind configuration file"
×
1697
               << endl,
×
1698
         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)));
×
1699
    throw PDNSException("Unauthorized characters in domain name for bind configuration file");
×
1700
  }
×
1701

1702
  string filename = getArg("autoprimary-destdir") + '/';
×
1703
  if (domainname.empty()) {
×
1704
    filename.append("rootzone.");
×
1705
  }
×
1706
  else {
×
1707
    // Make sure the zone file name does not contain path separators.
1708
    filename.append(boost::replace_all_copy(domainname, "/", "\\047"));
×
1709
  }
×
1710

NEW
1711
  BB2DomainInfo bbd = createDomainEntry(domain);
×
NEW
1712
  bbd.d_kind = DomainInfo::Secondary;
×
NEW
1713
  bbd.d_primaries.emplace_back(ComboAddress(ipAddress, 53));
×
NEW
1714
  bbd.d_fileinfo.emplace_back(std::make_pair(filename, 0));
×
NEW
1715
  bbd.updateCtime();
×
1716

NEW
1717
  if (startTransaction) {
×
1718
    // Remember this domain is pending until the transaction gets commited.
NEW
1719
    bbd.d_pending = true;
×
NEW
1720
    startDomainCreationTransactionInternal(bbd);
×
NEW
1721
  }
×
NEW
1722
  safePutBBDomainInfo(bbd);
×
1723

1724
  SLOG(g_log << Logger::Warning << d_logprefix
×
1725
             << " Writing bind config zone statement for autosecondary zone '" << domain
×
1726
             << "' from autoprimary " << ipAddress << endl,
×
1727
       d_slog->info(Logr::Warning, "Writing bind config zone statement for autosecondary zone", "zone", Logging::Loggable(domain), "autoprimary address", Logging::Loggable(ipAddress)));
×
1728

1729
  {
×
1730
    auto lock = std::scoped_lock(s_autosecondary_config_lock);
×
1731

1732
    std::string configfile(getArg("autoprimary-config"));
×
1733
    ofstream c_of(configfile.c_str(), std::ios::app);
×
1734
    if (!c_of) {
×
1735
      int err = errno;
×
1736
      auto errorMessage = stringerror();
×
1737
      SLOG(g_log << Logger::Error << "Unable to open autoprimary configfile for append: " << errorMessage << endl,
×
1738
           d_slog->error(Logr::Error, err, "Unable to open autoprimary configuration file for append", "file", Logging::Loggable(configfile)));
×
1739
      throw DBException("Unable to open autoprimary configfile for append: " + errorMessage);
×
1740
    }
×
1741

1742
    c_of << endl;
×
1743
    c_of << "# AutoSecondary zone '" << domainname << "' (added: " << nowTime() << ") (account: " << account << ')' << endl;
×
1744
    c_of << "zone \"" << domainname << "\" {" << endl;
×
1745
    c_of << "\ttype secondary;" << endl;
×
1746
    c_of << "\tfile \"" << filename << "\";" << endl;
×
1747
    c_of << "\tprimaries { " << ipAddress << "; };" << endl;
×
1748
    c_of << "};" << endl;
×
1749
    c_of.close();
×
1750
  }
×
1751

NEW
1752
  info.id = bbd.d_id;
×
NEW
1753
  info.zone = domain;
×
NEW
1754
  info.primaries = bbd.d_primaries;
×
NEW
1755
  info.last_check = bbd.d_lastcheck;
×
NEW
1756
  info.backend = this;
×
NEW
1757
  info.kind = bbd.d_kind;
×
NEW
1758
  info.serial = 0;
×
1759
  return true;
×
1760
}
×
1761

1762
bool Bind2Backend::searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result)
1763
{
24✔
1764
  SimpleMatch sm(pattern, true);
24✔
1765
  static bool mustlog = ::arg().mustDo("query-logging");
24✔
1766
  if (mustlog) {
24!
1767
    SLOG(g_log << Logger::Warning << "Search for pattern '" << pattern << "'" << endl,
×
1768
         d_slog->info(Logr::Debug, "Search for pattern", "pattern", Logging::Loggable(pattern)));
×
1769
  }
×
1770

1771
  {
24✔
1772
    auto state = s_state.read_lock();
24✔
1773

1774
    for (const auto& i : *state) {
24!
1775
      BB2DomainInfo h;
×
1776
      if (!safeGetBBDomainInfo(i.d_id, &h)) {
×
1777
        continue;
×
1778
      }
×
1779
      // Ignore pending domains unless we are the backend running the transaction
1780
      // in which they are being created. This can't be done in safeGetBBDomainInfo
1781
      // because it needs to be a static method.
NEW
1782
      if (h.d_pending && h.d_id != d_transaction_id) {
×
NEW
1783
        continue;
×
NEW
1784
      }
×
1785

1786
      if (!h.d_loaded) {
×
1787
        continue;
×
1788
      }
×
1789

1790
      shared_ptr<const recordstorage_t> rhandle = h.d_records.get();
×
1791

1792
      for (recordstorage_t::const_iterator ri = rhandle->begin(); result.size() < maxResults && ri != rhandle->end(); ri++) {
×
1793
        const DNSName& domainName(i.d_name);
×
1794
        DNSName name = ri->qname.empty() ? domainName : (ri->qname + domainName);
×
1795
        if (sm.match(name) || sm.match(ri->content)) {
×
1796
          DNSResourceRecord r;
×
1797
          r.qname = std::move(name);
×
1798
          r.domain_id = i.d_id;
×
1799
          r.content = ri->content;
×
1800
          r.qtype = ri->qtype;
×
1801
          r.ttl = ri->ttl;
×
1802
          r.auth = ri->auth;
×
1803
          result.push_back(std::move(r));
×
1804
        }
×
1805
      }
×
1806
    }
×
1807
  }
24✔
1808

1809
  return true;
24✔
1810
}
24✔
1811

1812
class Bind2Factory : public BackendFactory
1813
{
1814
public:
1815
  Bind2Factory() :
1816
    BackendFactory("bind") {}
6,222✔
1817

1818
  void declareArguments(const string& suffix = "") override
1819
  {
587✔
1820
    declare(suffix, "ignore-broken-records", "Ignore records that are out-of-bound for the zone.", "no");
587✔
1821
    declare(suffix, "config", "Location of named.conf", "");
587✔
1822
    declare(suffix, "check-interval", "Interval for zonefile changes", "0");
587✔
1823
    declare(suffix, "autoprimary-config", "Location of (part of) named.conf where pdns can write zone-statements to", "");
587✔
1824
    declare(suffix, "autoprimaries", "List of IP-addresses of autoprimaries", "");
587✔
1825
    declare(suffix, "autoprimary-destdir", "Destination directory for newly added secondary zones", ::arg()["config-dir"]);
587✔
1826
    declare(suffix, "dnssec-db", "Filename to store & access our DNSSEC metadatabase, empty for none", "");
587✔
1827
    declare(suffix, "dnssec-db-journal-mode", "SQLite3 journal mode", "WAL");
587✔
1828
    declare(suffix, "hybrid", "Store DNSSEC metadata in other backend", "no");
587✔
1829
  }
587✔
1830

1831
  DNSBackend* make(const string& suffix = "") override
1832
  {
2,586✔
1833
    assertEmptySuffix(suffix);
2,586✔
1834
    return new Bind2Backend(suffix);
2,586✔
1835
  }
2,586✔
1836

1837
  DNSBackend* makeMetadataOnly(const string& suffix = "") override
1838
  {
919✔
1839
    assertEmptySuffix(suffix);
919✔
1840
    return new Bind2Backend(suffix, false);
919✔
1841
  }
919✔
1842

1843
private:
1844
  void assertEmptySuffix(const string& suffix)
1845
  {
3,505✔
1846
    if (!suffix.empty())
3,505!
1847
      throw PDNSException("launch= suffixes are not supported on the bindbackend");
×
1848
  }
3,505✔
1849
};
1850

1851
//! Magic class that is activated when the dynamic library is loaded
1852
class Bind2Loader
1853
{
1854
public:
1855
  Bind2Loader()
1856
  {
6,222✔
1857
    BackendMakers().report(std::make_unique<Bind2Factory>());
6,222✔
1858
    // If this module is not loaded dynamically at runtime, this code runs
1859
    // as part of a global constructor, before the structured logger has a
1860
    // chance to be set up, so fallback to simple logging.
1861
    g_log << Logger::Info << "[bind2backend] This is the bind backend version " << VERSION
6,222✔
1862
#ifndef REPRODUCIBLE
6,222✔
1863
          << " (" __DATE__ " " __TIME__ ")"
6,222✔
1864
#endif
6,222✔
1865
#ifdef HAVE_SQLITE3
6,222✔
1866
          << " (with bind-dnssec-db support)"
6,222✔
1867
#endif
6,222✔
1868
          << " reporting" << endl;
6,222✔
1869
  }
6,222✔
1870
};
1871
static Bind2Loader bind2loader;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc