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

PowerDNS / pdns / 26160315807

20 May 2026 11:43AM UTC coverage: 43.993% (-15.6%) from 59.559%
26160315807

push

github

web-flow
Merge pull request #17444 from miodvallat/49x.sa_2026_06

auth 4.9: backport SA 2026-06

7913 of 26976 branches covered (29.33%)

Branch coverage included in aggregate %.

11 of 296 new or added lines in 8 files covered. (3.72%)

9259 existing lines in 100 files now uncovered.

29713 of 58552 relevant lines covered (50.75%)

1088874.24 hits per line

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

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

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

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

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

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

110
  if (d_filename.empty())
×
111
    return true;
×
112

113
  return (getCtime() == d_ctime);
×
114
}
×
115

116
time_t BB2DomainInfo::getCtime()
117
{
×
118
  struct stat buf;
×
119

120
  if (d_filename.empty() || stat(d_filename.c_str(), &buf) < 0)
×
121
    return 0;
×
122
  d_lastcheck = time(nullptr);
×
123
  return buf.st_ctime;
×
124
}
×
125

126
void BB2DomainInfo::setCtime()
UNCOV
127
{
×
UNCOV
128
  struct stat buf;
×
UNCOV
129
  if (stat(d_filename.c_str(), &buf) < 0)
×
130
    return;
×
UNCOV
131
  d_ctime = buf.st_ctime;
×
UNCOV
132
}
×
133

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

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

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

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

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

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

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

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

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

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

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

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

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

UNCOV
241
    return true;
×
UNCOV
242
  }
×
243
  return false;
×
UNCOV
244
}
×
245

246
bool Bind2Backend::commitTransaction()
UNCOV
247
{
×
UNCOV
248
  if (d_transaction_id < 0)
×
UNCOV
249
    return false;
×
UNCOV
250
  d_of.reset();
×
251

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

UNCOV
259
  d_transaction_id = 0;
×
260

UNCOV
261
  return true;
×
UNCOV
262
}
×
263

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

275
  return true;
×
276
}
×
277

278
// Perform adequate escaping of characters which have special meaning in
279
// Bind zone files.
280
// Note that the input is supposed to be a DNSName::toString() - or any of
281
// its variants - so we assume \ and . have been correctly escaped by
282
// DNSName::appendEscapedLabel already.
283
static const std::string bindEscape(const std::string& name)
NEW
284
{
×
NEW
285
  std::string ret;
×
NEW
286
  std::array<char, 5> ebuf{};
×
287

NEW
288
  for (char letter : name) {
×
NEW
289
    switch (letter) {
×
NEW
290
    case '$':
×
NEW
291
    case '@':
×
NEW
292
    case '"':
×
NEW
293
    case ';':
×
NEW
294
    case '(':
×
NEW
295
    case ')':
×
NEW
296
      snprintf(ebuf.data(), ebuf.size(), "\\%03u", static_cast<unsigned char>(letter));
×
NEW
297
      ret += ebuf.data();
×
NEW
298
      break;
×
NEW
299
    default:
×
NEW
300
      ret += letter;
×
NEW
301
      break;
×
NEW
302
    }
×
NEW
303
  }
×
NEW
304
  return ret;
×
NEW
305
}
×
306

307
bool Bind2Backend::feedRecord(const DNSResourceRecord& rr, const DNSName& /* ordername */, bool /* ordernameIsNSEC3 */)
UNCOV
308
{
×
UNCOV
309
  if (d_transaction_id < 1) {
×
310
    throw DBException("Bind2Backend::feedRecord() called outside of transaction");
×
311
  }
×
312

UNCOV
313
  string qname;
×
UNCOV
314
  if (d_transaction_qname.empty()) {
×
NEW
315
    qname = bindEscape(rr.qname.toString());
×
316
  }
×
UNCOV
317
  else if (rr.qname.isPartOf(d_transaction_qname)) {
×
UNCOV
318
    if (rr.qname == d_transaction_qname) {
×
UNCOV
319
      qname = "@";
×
UNCOV
320
    }
×
UNCOV
321
    else {
×
UNCOV
322
      DNSName relName = rr.qname.makeRelative(d_transaction_qname);
×
NEW
323
      qname = bindEscape(relName.toStringNoDot());
×
UNCOV
324
    }
×
UNCOV
325
  }
×
326
  else {
×
327
    throw DBException("out-of-zone data '" + rr.qname.toLogString() + "' during AXFR of zone '" + d_transaction_qname.toLogString() + "'");
×
328
  }
×
329

UNCOV
330
  shared_ptr<DNSRecordContent> drc(DNSRecordContent::make(rr.qtype.getCode(), QClass::IN, rr.content));
×
UNCOV
331
  string content = drc->getZoneRepresentation();
×
332

333
  // SOA needs stripping too! XXX FIXME - also, this should not be here I think
UNCOV
334
  switch (rr.qtype.getCode()) {
×
UNCOV
335
  case QType::MX:
×
UNCOV
336
  case QType::SRV:
×
UNCOV
337
  case QType::CNAME:
×
UNCOV
338
  case QType::DNAME:
×
UNCOV
339
  case QType::NS:
×
UNCOV
340
    stripDomainSuffix(&content, d_transaction_qname);
×
UNCOV
341
    [[fallthrough]];
×
UNCOV
342
  default:
×
UNCOV
343
    if (d_of && *d_of) {
×
UNCOV
344
      *d_of << qname << "\t" << rr.ttl << "\t" << rr.qtype.toString() << "\t" << content << endl;
×
UNCOV
345
    }
×
UNCOV
346
  }
×
UNCOV
347
  return true;
×
UNCOV
348
}
×
349

350
void Bind2Backend::getUpdatedPrimaries(vector<DomainInfo>& changedDomains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
351
{
×
352
  vector<DomainInfo> consider;
×
353
  {
×
354
    auto state = s_state.read_lock();
×
355

356
    for (const auto& i : *state) {
×
357
      if (i.d_kind != DomainInfo::Primary && this->alsoNotify.empty() && i.d_also_notify.empty())
×
358
        continue;
×
359

360
      DomainInfo di;
×
361
      di.id = i.d_id;
×
362
      di.zone = i.d_name;
×
363
      di.last_check = i.d_lastcheck;
×
364
      di.notified_serial = i.d_lastnotified;
×
365
      di.backend = this;
×
366
      di.kind = DomainInfo::Primary;
×
367
      consider.push_back(std::move(di));
×
368
    }
×
369
  }
×
370

371
  SOAData soadata;
×
372
  for (DomainInfo& di : consider) {
×
373
    soadata.serial = 0;
×
374
    try {
×
375
      this->getSOA(di.zone, soadata); // we might not *have* a SOA yet, but this might trigger a load of it
×
376
    }
×
377
    catch (...) {
×
378
      continue;
×
379
    }
×
380
    if (di.notified_serial != soadata.serial) {
×
381
      BB2DomainInfo bbd;
×
382
      if (safeGetBBDomainInfo(di.id, &bbd)) {
×
383
        bbd.d_lastnotified = soadata.serial;
×
384
        safePutBBDomainInfo(bbd);
×
385
      }
×
386
      if (di.notified_serial) { // don't do notification storm on startup
×
387
        di.serial = soadata.serial;
×
388
        changedDomains.push_back(std::move(di));
×
389
      }
×
390
    }
×
391
  }
×
392
}
×
393

394
void Bind2Backend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool /* include_disabled */)
395
{
66✔
396
  SOAData soadata;
66✔
397

398
  // prevent deadlock by using getSOA() later on
399
  {
66✔
400
    auto state = s_state.read_lock();
66✔
401
    domains->reserve(state->size());
66✔
402

403
    for (const auto& i : *state) {
66!
UNCOV
404
      DomainInfo di;
×
UNCOV
405
      di.id = i.d_id;
×
UNCOV
406
      di.zone = i.d_name;
×
UNCOV
407
      di.last_check = i.d_lastcheck;
×
UNCOV
408
      di.kind = i.d_kind;
×
UNCOV
409
      di.primaries = i.d_primaries;
×
UNCOV
410
      di.backend = this;
×
UNCOV
411
      domains->push_back(std::move(di));
×
UNCOV
412
    };
×
413
  }
66✔
414

415
  if (getSerial) {
66✔
416
    for (DomainInfo& di : *domains) {
1,340✔
417
      // do not corrupt di if domain supplied by another backend.
418
      if (di.backend != this)
1,340!
419
        continue;
1,340✔
420
      try {
×
421
        this->getSOA(di.zone, soadata);
×
422
      }
×
423
      catch (...) {
×
424
        continue;
×
425
      }
×
426
      di.serial = soadata.serial;
×
427
    }
×
428
  }
41✔
429
}
66✔
430

431
void Bind2Backend::getUnfreshSecondaryInfos(vector<DomainInfo>* unfreshDomains)
UNCOV
432
{
×
UNCOV
433
  vector<DomainInfo> domains;
×
UNCOV
434
  {
×
UNCOV
435
    auto state = s_state.read_lock();
×
UNCOV
436
    domains.reserve(state->size());
×
UNCOV
437
    for (const auto& i : *state) {
×
UNCOV
438
      if (i.d_kind != DomainInfo::Secondary)
×
439
        continue;
×
UNCOV
440
      DomainInfo sd;
×
UNCOV
441
      sd.id = i.d_id;
×
UNCOV
442
      sd.zone = i.d_name;
×
UNCOV
443
      sd.primaries = i.d_primaries;
×
UNCOV
444
      sd.last_check = i.d_lastcheck;
×
UNCOV
445
      sd.backend = this;
×
UNCOV
446
      sd.kind = DomainInfo::Secondary;
×
UNCOV
447
      domains.push_back(std::move(sd));
×
UNCOV
448
    }
×
UNCOV
449
  }
×
UNCOV
450
  unfreshDomains->reserve(domains.size());
×
451

UNCOV
452
  for (DomainInfo& sd : domains) {
×
UNCOV
453
    SOAData soadata;
×
UNCOV
454
    soadata.refresh = 0;
×
UNCOV
455
    soadata.serial = 0;
×
UNCOV
456
    try {
×
UNCOV
457
      getSOA(sd.zone, soadata); // we might not *have* a SOA yet
×
UNCOV
458
    }
×
UNCOV
459
    catch (...) {
×
UNCOV
460
    }
×
UNCOV
461
    sd.serial = soadata.serial;
×
462
    // coverity[store_truncates_time_t]
UNCOV
463
    if (sd.last_check + soadata.refresh < (unsigned int)time(nullptr))
×
UNCOV
464
      unfreshDomains->push_back(std::move(sd));
×
UNCOV
465
  }
×
UNCOV
466
}
×
467

468
bool Bind2Backend::getDomainInfo(const DNSName& domain, DomainInfo& di, bool getSerial)
469
{
579✔
470
  BB2DomainInfo bbd;
579✔
471
  if (!safeGetBBDomainInfo(domain, &bbd))
579!
472
    return false;
579✔
473

UNCOV
474
  di.id = bbd.d_id;
×
UNCOV
475
  di.zone = domain;
×
UNCOV
476
  di.primaries = bbd.d_primaries;
×
UNCOV
477
  di.last_check = bbd.d_lastcheck;
×
UNCOV
478
  di.backend = this;
×
UNCOV
479
  di.kind = bbd.d_kind;
×
UNCOV
480
  di.serial = 0;
×
UNCOV
481
  if (getSerial) {
×
UNCOV
482
    try {
×
UNCOV
483
      SOAData sd;
×
UNCOV
484
      sd.serial = 0;
×
485

UNCOV
486
      getSOA(bbd.d_name, sd); // we might not *have* a SOA yet
×
UNCOV
487
      di.serial = sd.serial;
×
UNCOV
488
    }
×
UNCOV
489
    catch (...) {
×
UNCOV
490
    }
×
UNCOV
491
  }
×
492

UNCOV
493
  return true;
×
UNCOV
494
}
×
495

496
void Bind2Backend::alsoNotifies(const DNSName& domain, set<string>* ips)
497
{
4✔
498
  // combine global list with local list
499
  for (const auto& i : this->alsoNotify) {
4!
500
    (*ips).insert(i);
×
501
  }
×
502
  // check metadata too if available
503
  vector<string> meta;
4✔
504
  if (getDomainMetadata(domain, "ALSO-NOTIFY", meta)) {
4!
505
    for (const auto& str : meta) {
×
506
      (*ips).insert(str);
×
507
    }
×
508
  }
×
509
  auto state = s_state.read_lock();
4✔
510
  for (const auto& i : *state) {
4!
511
    if (i.d_name == domain) {
×
512
      for (const auto& it : i.d_also_notify) {
×
513
        (*ips).insert(it);
×
514
      }
×
515
      return;
×
516
    }
×
517
  }
×
518
}
4✔
519

520
// only parses, does NOT add to s_state!
521
void Bind2Backend::parseZoneFile(BB2DomainInfo* bbd)
UNCOV
522
{
×
UNCOV
523
  NSEC3PARAMRecordContent ns3pr;
×
UNCOV
524
  bool nsec3zone = false;
×
UNCOV
525
  if (d_hybrid) {
×
526
    DNSSECKeeper dk;
×
527
    nsec3zone = dk.getNSEC3PARAM(bbd->d_name, &ns3pr);
×
528
  }
×
UNCOV
529
  else
×
UNCOV
530
    nsec3zone = getNSEC3PARAMuncached(bbd->d_name, &ns3pr);
×
531

UNCOV
532
  auto records = std::make_shared<recordstorage_t>();
×
UNCOV
533
  ZoneParserTNG zpt(bbd->d_filename, bbd->d_name, s_binddirectory, d_upgradeContent);
×
UNCOV
534
  zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
×
UNCOV
535
  zpt.setMaxIncludes(::arg().asNum("max-include-depth"));
×
UNCOV
536
  DNSResourceRecord rr;
×
UNCOV
537
  string hashed;
×
UNCOV
538
  while (zpt.get(rr)) {
×
UNCOV
539
    if (rr.qtype.getCode() == QType::NSEC || rr.qtype.getCode() == QType::NSEC3 || rr.qtype.getCode() == QType::NSEC3PARAM)
×
540
      continue; // we synthesise NSECs on demand
×
541

UNCOV
542
    insertRecord(records, bbd->d_name, rr.qname, rr.qtype, rr.content, rr.ttl, "");
×
UNCOV
543
  }
×
UNCOV
544
  fixupOrderAndAuth(records, bbd->d_name, nsec3zone, ns3pr);
×
UNCOV
545
  doEmptyNonTerminals(records, bbd->d_name, nsec3zone, ns3pr);
×
UNCOV
546
  bbd->setCtime();
×
UNCOV
547
  bbd->d_loaded = true;
×
UNCOV
548
  bbd->d_checknow = false;
×
UNCOV
549
  bbd->d_status = "parsed into memory at " + nowTime();
×
UNCOV
550
  bbd->d_records = LookButDontTouch<recordstorage_t>(std::move(records));
×
UNCOV
551
  bbd->d_nsec3zone = nsec3zone;
×
UNCOV
552
  bbd->d_nsec3param = std::move(ns3pr);
×
UNCOV
553
}
×
554

555
/** THIS IS AN INTERNAL FUNCTION! It does moadnsparser prio impedance matching
556
    Much of the complication is due to the efforts to benefit from std::string reference counting copy on write semantics */
557
void Bind2Backend::insertRecord(std::shared_ptr<recordstorage_t>& records, const DNSName& zoneName, const DNSName& qname, const QType& qtype, const string& content, int ttl, const std::string& hashed, bool* auth)
UNCOV
558
{
×
UNCOV
559
  Bind2DNSRecord bdr;
×
UNCOV
560
  bdr.qname = qname;
×
561

UNCOV
562
  if (zoneName.empty())
×
563
    ;
×
UNCOV
564
  else if (bdr.qname.isPartOf(zoneName))
×
UNCOV
565
    bdr.qname.makeUsRelative(zoneName);
×
UNCOV
566
  else {
×
UNCOV
567
    string msg = "Trying to insert non-zone data, name='" + bdr.qname.toLogString() + "', qtype=" + qtype.toString() + ", zone='" + zoneName.toLogString() + "'";
×
UNCOV
568
    if (s_ignore_broken_records) {
×
UNCOV
569
      g_log << Logger::Warning << msg << " ignored" << endl;
×
UNCOV
570
      return;
×
UNCOV
571
    }
×
572
    else
×
573
      throw PDNSException(msg);
×
UNCOV
574
  }
×
575

576
  //  bdr.qname.swap(bdr.qname);
577

UNCOV
578
  if (!records->empty() && bdr.qname == boost::prior(records->end())->qname)
×
UNCOV
579
    bdr.qname = boost::prior(records->end())->qname;
×
580

UNCOV
581
  bdr.qname = bdr.qname;
×
UNCOV
582
  bdr.qtype = qtype.getCode();
×
UNCOV
583
  bdr.content = content;
×
UNCOV
584
  bdr.nsec3hash = hashed;
×
585

UNCOV
586
  if (auth != nullptr) // Set auth on empty non-terminals
×
UNCOV
587
    bdr.auth = *auth;
×
UNCOV
588
  else
×
UNCOV
589
    bdr.auth = true;
×
590

UNCOV
591
  bdr.ttl = ttl;
×
UNCOV
592
  records->insert(std::move(bdr));
×
UNCOV
593
}
×
594

595
string Bind2Backend::DLReloadNowHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
596
{
×
597
  ostringstream ret;
×
598

599
  for (auto i = parts.begin() + 1; i < parts.end(); ++i) {
×
600
    BB2DomainInfo bbd;
×
601
    DNSName zone(*i);
×
602
    if (safeGetBBDomainInfo(zone, &bbd)) {
×
603
      Bind2Backend bb2;
×
604
      bb2.queueReloadAndStore(bbd.d_id);
×
605
      if (!safeGetBBDomainInfo(zone, &bbd)) // Read the *new* domain status
×
606
        ret << *i << ": [missing]\n";
×
607
      else
×
608
        ret << *i << ": " << (bbd.d_wasRejectedLastReload ? "[rejected]" : "") << "\t" << bbd.d_status << "\n";
×
609
      purgeAuthCaches(zone.toString() + "$");
×
610
      DNSSECKeeper::clearMetaCache(zone);
×
611
    }
×
612
    else
×
613
      ret << *i << " no such domain\n";
×
614
  }
×
615
  if (ret.str().empty())
×
616
    ret << "no domains reloaded";
×
617
  return ret.str();
×
618
}
×
619

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

UNCOV
624
  if (parts.size() > 1) {
×
625
    for (auto i = parts.begin() + 1; i < parts.end(); ++i) {
×
626
      BB2DomainInfo bbd;
×
627
      if (safeGetBBDomainInfo(DNSName(*i), &bbd)) {
×
628
        ret << *i << ": " << (bbd.d_loaded ? "" : "[rejected]") << "\t" << bbd.d_status << "\n";
×
629
      }
×
630
      else {
×
631
        ret << *i << " no such domain\n";
×
632
      }
×
633
    }
×
634
  }
×
UNCOV
635
  else {
×
UNCOV
636
    auto state = s_state.read_lock();
×
UNCOV
637
    for (const auto& i : *state) {
×
UNCOV
638
      ret << i.d_name << ": " << (i.d_loaded ? "" : "[rejected]") << "\t" << i.d_status << "\n";
×
UNCOV
639
    }
×
UNCOV
640
  }
×
641

UNCOV
642
  if (ret.str().empty())
×
643
    ret << "no domains passed";
×
644

UNCOV
645
  return ret.str();
×
UNCOV
646
}
×
647

648
static void printDomainExtendedStatus(ostringstream& ret, const BB2DomainInfo& info)
649
{
×
650
  ret << info.d_name << ": " << std::endl;
×
651
  ret << "\t Status: " << info.d_status << std::endl;
×
652
  ret << "\t Internal ID: " << info.d_id << std::endl;
×
653
  ret << "\t On-disk file: " << info.d_filename << " (" << info.d_ctime << ")" << std::endl;
×
654
  ret << "\t Kind: ";
×
655
  switch (info.d_kind) {
×
656
  case DomainInfo::Primary:
×
657
    ret << "Primary";
×
658
    break;
×
659
  case DomainInfo::Secondary:
×
660
    ret << "Secondary";
×
661
    break;
×
662
  default:
×
663
    ret << "Native";
×
664
  }
×
665
  ret << std::endl;
×
666
  ret << "\t Primaries: " << std::endl;
×
667
  for (const auto& primary : info.d_primaries) {
×
668
    ret << "\t\t - " << primary.toStringWithPort() << std::endl;
×
669
  }
×
670
  ret << "\t Also Notify: " << std::endl;
×
671
  for (const auto& also : info.d_also_notify) {
×
672
    ret << "\t\t - " << also << std::endl;
×
673
  }
×
674
  ret << "\t Number of records: " << info.d_records.getEntriesCount() << std::endl;
×
675
  ret << "\t Loaded: " << info.d_loaded << std::endl;
×
676
  ret << "\t Check now: " << info.d_checknow << std::endl;
×
677
  ret << "\t Check interval: " << info.getCheckInterval() << std::endl;
×
678
  ret << "\t Last check: " << info.d_lastcheck << std::endl;
×
679
  ret << "\t Last notified: " << info.d_lastnotified << std::endl;
×
680
}
×
681

682
string Bind2Backend::DLDomExtendedStatusHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
683
{
×
684
  ostringstream ret;
×
685

686
  if (parts.size() > 1) {
×
687
    for (auto i = parts.begin() + 1; i < parts.end(); ++i) {
×
688
      BB2DomainInfo bbd;
×
689
      if (safeGetBBDomainInfo(DNSName(*i), &bbd)) {
×
690
        printDomainExtendedStatus(ret, bbd);
×
691
      }
×
692
      else {
×
693
        ret << *i << " no such domain" << std::endl;
×
694
      }
×
695
    }
×
696
  }
×
697
  else {
×
698
    auto rstate = s_state.read_lock();
×
699
    for (const auto& state : *rstate) {
×
700
      printDomainExtendedStatus(ret, state);
×
701
    }
×
702
  }
×
703

704
  if (ret.str().empty()) {
×
705
    ret << "no domains passed" << std::endl;
×
706
  }
×
707

708
  return ret.str();
×
709
}
×
710

711
string Bind2Backend::DLListRejectsHandler(const vector<string>& /* parts */, Utility::pid_t /* ppid */)
712
{
×
713
  ostringstream ret;
×
714
  auto rstate = s_state.read_lock();
×
715
  for (const auto& i : *rstate) {
×
716
    if (!i.d_loaded)
×
717
      ret << i.d_name << "\t" << i.d_status << endl;
×
718
  }
×
719
  return ret.str();
×
720
}
×
721

722
string Bind2Backend::DLAddDomainHandler(const vector<string>& parts, Utility::pid_t /* ppid */)
UNCOV
723
{
×
UNCOV
724
  if (parts.size() < 3)
×
725
    return "ERROR: Domain name and zone filename are required";
×
726

UNCOV
727
  DNSName domainname(parts[1]);
×
UNCOV
728
  const string& filename = parts[2];
×
UNCOV
729
  BB2DomainInfo bbd;
×
UNCOV
730
  if (safeGetBBDomainInfo(domainname, &bbd))
×
UNCOV
731
    return "Already loaded";
×
732

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

UNCOV
736
  struct stat buf;
×
UNCOV
737
  if (stat(filename.c_str(), &buf) != 0)
×
738
    return "Unable to load zone " + domainname.toLogString() + " from " + filename + ": " + strerror(errno);
×
739

UNCOV
740
  Bind2Backend bb2; // createdomainentry needs access to our configuration
×
UNCOV
741
  bbd = bb2.createDomainEntry(domainname, filename);
×
UNCOV
742
  bbd.d_filename = filename;
×
UNCOV
743
  bbd.d_checknow = true;
×
UNCOV
744
  bbd.d_loaded = true;
×
UNCOV
745
  bbd.d_lastcheck = 0;
×
UNCOV
746
  bbd.d_status = "parsing into memory";
×
UNCOV
747
  bbd.setCtime();
×
748

UNCOV
749
  safePutBBDomainInfo(bbd);
×
750

UNCOV
751
  g_zoneCache.add(domainname, bbd.d_id); // make new zone visible
×
752

UNCOV
753
  g_log << Logger::Warning << "Zone " << domainname << " loaded" << endl;
×
UNCOV
754
  return "Loaded zone " + domainname.toLogString() + " from " + filename;
×
UNCOV
755
}
×
756

757
Bind2Backend::Bind2Backend(const string& suffix, bool loadZones)
758
{
1,861✔
759
  d_getAllDomainMetadataQuery_stmt = nullptr;
1,861✔
760
  d_getDomainMetadataQuery_stmt = nullptr;
1,861✔
761
  d_deleteDomainMetadataQuery_stmt = nullptr;
1,861✔
762
  d_insertDomainMetadataQuery_stmt = nullptr;
1,861✔
763
  d_getDomainKeysQuery_stmt = nullptr;
1,861✔
764
  d_deleteDomainKeyQuery_stmt = nullptr;
1,861✔
765
  d_insertDomainKeyQuery_stmt = nullptr;
1,861✔
766
  d_GetLastInsertedKeyIdQuery_stmt = nullptr;
1,861✔
767
  d_activateDomainKeyQuery_stmt = nullptr;
1,861✔
768
  d_deactivateDomainKeyQuery_stmt = nullptr;
1,861✔
769
  d_getTSIGKeyQuery_stmt = nullptr;
1,861✔
770
  d_setTSIGKeyQuery_stmt = nullptr;
1,861✔
771
  d_deleteTSIGKeyQuery_stmt = nullptr;
1,861✔
772
  d_getTSIGKeysQuery_stmt = nullptr;
1,861✔
773

774
  setArgPrefix("bind" + suffix);
1,861✔
775
  d_logprefix = "[bind" + suffix + "backend]";
1,861✔
776
  d_hybrid = mustDo("hybrid");
1,861✔
777
  if (d_hybrid && g_zoneCache.isEnabled()) {
1,861!
778
    throw PDNSException("bind-hybrid and the zone cache currently interoperate badly. Please disable the zone cache or stop using bind-hybrid");
×
779
  }
×
780

781
  d_transaction_id = 0;
1,861✔
782
  s_ignore_broken_records = mustDo("ignore-broken-records");
1,861✔
783
  d_upgradeContent = ::arg().mustDo("upgrade-unknown-types");
1,861✔
784

785
  if (!loadZones && d_hybrid)
1,861!
786
    return;
×
787

788
  std::lock_guard<std::mutex> l(s_startup_lock);
1,861✔
789

790
  setupDNSSEC();
1,861✔
791
  if (s_first == 0) {
1,861✔
792
    return;
1,499✔
793
  }
1,499✔
794

795
  if (loadZones) {
362✔
796
    loadConfig();
131✔
797
    s_first = 0;
131✔
798
  }
131✔
799

800
  DynListener::registerFunc("BIND-RELOAD-NOW", &DLReloadNowHandler, "bindbackend: reload domains", "<domains>");
362✔
801
  DynListener::registerFunc("BIND-DOMAIN-STATUS", &DLDomStatusHandler, "bindbackend: list status of all domains", "[domains]");
362✔
802
  DynListener::registerFunc("BIND-DOMAIN-EXTENDED-STATUS", &DLDomExtendedStatusHandler, "bindbackend: list the extended status of all domains", "[domains]");
362✔
803
  DynListener::registerFunc("BIND-LIST-REJECTS", &DLListRejectsHandler, "bindbackend: list rejected domains");
362✔
804
  DynListener::registerFunc("BIND-ADD-ZONE", &DLAddDomainHandler, "bindbackend: add zone", "<domain> <filename>");
362✔
805
}
362✔
806

807
Bind2Backend::~Bind2Backend()
808
{
1,845✔
809
  freeStatements();
1,845✔
810
} // deallocate statements
1,845✔
811

812
void Bind2Backend::rediscover(string* status)
813
{
×
814
  loadConfig(status);
×
815
}
×
816

817
void Bind2Backend::reload()
818
{
×
819
  auto state = s_state.write_lock();
×
820
  for (const auto& i : *state) {
×
821
    i.d_checknow = true; // being a bit cheeky here, don't index state_t on this (mutable)
×
822
  }
×
823
}
×
824

825
void Bind2Backend::fixupOrderAndAuth(std::shared_ptr<recordstorage_t>& records, const DNSName& zoneName, bool nsec3zone, const NSEC3PARAMRecordContent& ns3pr)
UNCOV
826
{
×
UNCOV
827
  bool skip;
×
UNCOV
828
  DNSName shorter;
×
UNCOV
829
  set<DNSName> nssets, dssets;
×
830

UNCOV
831
  for (const auto& bdr : *records) {
×
UNCOV
832
    if (!bdr.qname.isRoot() && bdr.qtype == QType::NS)
×
UNCOV
833
      nssets.insert(bdr.qname);
×
UNCOV
834
    else if (bdr.qtype == QType::DS)
×
UNCOV
835
      dssets.insert(bdr.qname);
×
UNCOV
836
  }
×
837

UNCOV
838
  for (auto iter = records->begin(); iter != records->end(); iter++) {
×
UNCOV
839
    skip = false;
×
UNCOV
840
    shorter = iter->qname;
×
841

UNCOV
842
    if (!iter->qname.isRoot() && shorter.chopOff() && !iter->qname.isRoot()) {
×
UNCOV
843
      do {
×
UNCOV
844
        if (nssets.count(shorter) != 0u) {
×
UNCOV
845
          skip = true;
×
UNCOV
846
          break;
×
UNCOV
847
        }
×
UNCOV
848
      } while (shorter.chopOff() && !iter->qname.isRoot());
×
UNCOV
849
    }
×
850

UNCOV
851
    iter->auth = (!skip && (iter->qtype == QType::DS || iter->qtype == QType::RRSIG || (nssets.count(iter->qname) == 0u)));
×
852

UNCOV
853
    if (!skip && nsec3zone && iter->qtype != QType::RRSIG && (iter->auth || (iter->qtype == QType::NS && (ns3pr.d_flags == 0u)) || (dssets.count(iter->qname) != 0u))) {
×
UNCOV
854
      Bind2DNSRecord bdr = *iter;
×
UNCOV
855
      bdr.nsec3hash = toBase32Hex(hashQNameWithSalt(ns3pr, bdr.qname + zoneName));
×
UNCOV
856
      records->replace(iter, bdr);
×
UNCOV
857
    }
×
858

859
    // cerr<<iter->qname<<"\t"<<QType(iter->qtype).toString()<<"\t"<<iter->nsec3hash<<"\t"<<iter->auth<<endl;
UNCOV
860
  }
×
UNCOV
861
}
×
862

863
void Bind2Backend::doEmptyNonTerminals(std::shared_ptr<recordstorage_t>& records, const DNSName& zoneName, bool nsec3zone, const NSEC3PARAMRecordContent& ns3pr)
UNCOV
864
{
×
UNCOV
865
  bool auth = false;
×
UNCOV
866
  DNSName shorter;
×
UNCOV
867
  std::unordered_set<DNSName> qnames;
×
UNCOV
868
  std::unordered_map<DNSName, bool> nonterm;
×
869

UNCOV
870
  uint32_t maxent = ::arg().asNum("max-ent-entries");
×
871

UNCOV
872
  for (const auto& bdr : *records)
×
UNCOV
873
    qnames.insert(bdr.qname);
×
874

UNCOV
875
  for (const auto& bdr : *records) {
×
876

UNCOV
877
    if (!bdr.auth && bdr.qtype == QType::NS)
×
UNCOV
878
      auth = (!nsec3zone || (ns3pr.d_flags == 0u));
×
UNCOV
879
    else
×
UNCOV
880
      auth = bdr.auth;
×
881

UNCOV
882
    shorter = bdr.qname;
×
UNCOV
883
    while (shorter.chopOff()) {
×
UNCOV
884
      if (qnames.count(shorter) == 0u) {
×
UNCOV
885
        if (!(maxent)) {
×
886
          g_log << Logger::Error << "Zone '" << zoneName << "' has too many empty non terminals." << endl;
×
887
          return;
×
888
        }
×
889

UNCOV
890
        if (nonterm.count(shorter) == 0u) {
×
UNCOV
891
          nonterm.emplace(shorter, auth);
×
UNCOV
892
          --maxent;
×
UNCOV
893
        }
×
UNCOV
894
        else if (auth)
×
UNCOV
895
          nonterm[shorter] = true;
×
UNCOV
896
      }
×
UNCOV
897
    }
×
UNCOV
898
  }
×
899

UNCOV
900
  DNSResourceRecord rr;
×
UNCOV
901
  rr.qtype = "#0";
×
UNCOV
902
  rr.content = "";
×
UNCOV
903
  rr.ttl = 0;
×
UNCOV
904
  for (auto& nt : nonterm) {
×
UNCOV
905
    string hashed;
×
UNCOV
906
    rr.qname = nt.first + zoneName;
×
UNCOV
907
    if (nsec3zone && nt.second)
×
UNCOV
908
      hashed = toBase32Hex(hashQNameWithSalt(ns3pr, rr.qname));
×
UNCOV
909
    insertRecord(records, zoneName, rr.qname, rr.qtype, rr.content, rr.ttl, hashed, &nt.second);
×
910

911
    // cerr<<rr.qname<<"\t"<<rr.qtype.toString()<<"\t"<<hashed<<"\t"<<nt.second<<endl;
UNCOV
912
  }
×
UNCOV
913
}
×
914

915
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
916
{
131✔
917
  static int domain_id = 1;
131✔
918

919
  if (!getArg("config").empty()) {
131!
920
    BindParser BP;
131✔
921
    try {
131✔
922
      BP.parse(getArg("config"));
131✔
923
    }
131✔
924
    catch (PDNSException& ae) {
131✔
925
      g_log << Logger::Error << "Error parsing bind configuration: " << ae.reason << endl;
×
926
      throw;
×
927
    }
×
928

929
    vector<BindDomainInfo> domains = BP.getDomains();
131✔
930
    this->alsoNotify = BP.getAlsoNotify();
131✔
931

932
    s_binddirectory = BP.getDirectory();
131✔
933
    //    ZP.setDirectory(d_binddirectory);
934

935
    g_log << Logger::Warning << d_logprefix << " Parsing " << domains.size() << " domain(s), will report when done" << endl;
131✔
936

937
    set<DNSName> oldnames, newnames;
131✔
938
    {
131✔
939
      auto state = s_state.read_lock();
131✔
940
      for (const BB2DomainInfo& bbd : *state) {
131!
941
        oldnames.insert(bbd.d_name);
×
942
      }
×
943
    }
131✔
944
    int rejected = 0;
131✔
945
    int newdomains = 0;
131✔
946

947
    struct stat st;
131✔
948

949
    for (auto& domain : domains) {
131!
UNCOV
950
      if (stat(domain.filename.c_str(), &st) == 0) {
×
UNCOV
951
        domain.d_dev = st.st_dev;
×
UNCOV
952
        domain.d_ino = st.st_ino;
×
UNCOV
953
      }
×
UNCOV
954
    }
×
955

956
    sort(domains.begin(), domains.end()); // put stuff in inode order
131✔
957
    for (const auto& domain : domains) {
131!
UNCOV
958
      if (!(domain.hadFileDirective)) {
×
959
        g_log << Logger::Warning << d_logprefix << " Zone '" << domain.name << "' has no 'file' directive set in " << getArg("config") << endl;
×
960
        rejected++;
×
961
        continue;
×
962
      }
×
963

UNCOV
964
      if (domain.type.empty()) {
×
965
        g_log << Logger::Notice << d_logprefix << " Zone '" << domain.name << "' has no type specified, assuming 'native'" << endl;
×
966
      }
×
UNCOV
967
      if (domain.type != "primary" && domain.type != "secondary" && domain.type != "native" && !domain.type.empty() && domain.type != "master" && domain.type != "slave") {
×
968
        g_log << Logger::Warning << d_logprefix << " Warning! Skipping zone '" << domain.name << "' because type '" << domain.type << "' is invalid" << endl;
×
969
        rejected++;
×
970
        continue;
×
971
      }
×
972

UNCOV
973
      BB2DomainInfo bbd;
×
UNCOV
974
      bool isNew = false;
×
975

UNCOV
976
      if (!safeGetBBDomainInfo(domain.name, &bbd)) {
×
UNCOV
977
        isNew = true;
×
UNCOV
978
        bbd.d_id = domain_id++;
×
UNCOV
979
        bbd.setCheckInterval(getArgAsNum("check-interval"));
×
UNCOV
980
        bbd.d_lastnotified = 0;
×
UNCOV
981
        bbd.d_loaded = false;
×
UNCOV
982
      }
×
983

984
      // overwrite what we knew about the domain
UNCOV
985
      bbd.d_name = domain.name;
×
UNCOV
986
      bool filenameChanged = (bbd.d_filename != domain.filename);
×
UNCOV
987
      bool addressesChanged = (bbd.d_primaries != domain.primaries || bbd.d_also_notify != domain.alsoNotify);
×
UNCOV
988
      bbd.d_filename = domain.filename;
×
UNCOV
989
      bbd.d_primaries = domain.primaries;
×
UNCOV
990
      bbd.d_also_notify = domain.alsoNotify;
×
991

UNCOV
992
      DomainInfo::DomainKind kind = DomainInfo::Native;
×
UNCOV
993
      if (domain.type == "primary" || domain.type == "master") {
×
UNCOV
994
        kind = DomainInfo::Primary;
×
UNCOV
995
      }
×
UNCOV
996
      if (domain.type == "secondary" || domain.type == "slave") {
×
UNCOV
997
        kind = DomainInfo::Secondary;
×
UNCOV
998
      }
×
999

UNCOV
1000
      bool kindChanged = (bbd.d_kind != kind);
×
UNCOV
1001
      bbd.d_kind = kind;
×
1002

UNCOV
1003
      newnames.insert(bbd.d_name);
×
UNCOV
1004
      if (filenameChanged || !bbd.d_loaded || !bbd.current()) {
×
UNCOV
1005
        g_log << Logger::Info << d_logprefix << " parsing '" << domain.name << "' from file '" << domain.filename << "'" << endl;
×
1006

UNCOV
1007
        try {
×
UNCOV
1008
          parseZoneFile(&bbd);
×
UNCOV
1009
        }
×
UNCOV
1010
        catch (PDNSException& ae) {
×
1011
          ostringstream msg;
×
1012
          msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.reason;
×
1013

1014
          if (status != nullptr)
×
1015
            *status += msg.str();
×
1016
          bbd.d_status = msg.str();
×
1017

1018
          g_log << Logger::Warning << d_logprefix << msg.str() << endl;
×
1019
          rejected++;
×
1020
        }
×
UNCOV
1021
        catch (std::system_error& ae) {
×
UNCOV
1022
          ostringstream msg;
×
UNCOV
1023
          if (ae.code().value() == ENOENT && isNew && domain.type == "slave")
×
1024
            msg << " error at " + nowTime() << " no file found for new secondary domain '" << domain.name << "'. Has not been AXFR'd yet";
×
UNCOV
1025
          else
×
UNCOV
1026
            msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.what();
×
1027

UNCOV
1028
          if (status != nullptr)
×
1029
            *status += msg.str();
×
UNCOV
1030
          bbd.d_status = msg.str();
×
UNCOV
1031
          g_log << Logger::Warning << d_logprefix << msg.str() << endl;
×
UNCOV
1032
          rejected++;
×
UNCOV
1033
        }
×
UNCOV
1034
        catch (std::exception& ae) {
×
1035
          ostringstream msg;
×
1036
          msg << " error at " + nowTime() + " parsing '" << domain.name << "' from file '" << domain.filename << "': " << ae.what();
×
1037

1038
          if (status != nullptr)
×
1039
            *status += msg.str();
×
1040
          bbd.d_status = msg.str();
×
1041

1042
          g_log << Logger::Warning << d_logprefix << msg.str() << endl;
×
1043
          rejected++;
×
1044
        }
×
UNCOV
1045
        safePutBBDomainInfo(bbd);
×
UNCOV
1046
      }
×
1047
      else if (addressesChanged || kindChanged) {
×
1048
        safePutBBDomainInfo(bbd);
×
1049
      }
×
UNCOV
1050
    }
×
1051
    vector<DNSName> diff;
131✔
1052

1053
    set_difference(oldnames.begin(), oldnames.end(), newnames.begin(), newnames.end(), back_inserter(diff));
131✔
1054
    unsigned int remdomains = diff.size();
131✔
1055

1056
    for (const DNSName& name : diff) {
131!
1057
      safeRemoveBBDomainInfo(name);
×
1058
    }
×
1059

1060
    // count number of entirely new domains
1061
    diff.clear();
131✔
1062
    set_difference(newnames.begin(), newnames.end(), oldnames.begin(), oldnames.end(), back_inserter(diff));
131✔
1063
    newdomains = diff.size();
131✔
1064

1065
    ostringstream msg;
131✔
1066
    msg << " Done parsing domains, " << rejected << " rejected, " << newdomains << " new, " << remdomains << " removed";
131✔
1067
    if (status != nullptr)
131!
1068
      *status = msg.str();
×
1069

1070
    g_log << Logger::Error << d_logprefix << msg.str() << endl;
131✔
1071
  }
131✔
1072
}
131✔
1073

1074
void Bind2Backend::queueReloadAndStore(unsigned int id)
UNCOV
1075
{
×
UNCOV
1076
  BB2DomainInfo bbold;
×
UNCOV
1077
  try {
×
UNCOV
1078
    if (!safeGetBBDomainInfo(id, &bbold))
×
1079
      return;
×
UNCOV
1080
    bbold.d_checknow = false;
×
UNCOV
1081
    BB2DomainInfo bbnew(bbold);
×
1082
    /* make sure that nothing will be able to alter the existing records,
1083
       we will load them from the zone file instead */
UNCOV
1084
    bbnew.d_records = LookButDontTouch<recordstorage_t>();
×
UNCOV
1085
    parseZoneFile(&bbnew);
×
UNCOV
1086
    bbnew.d_wasRejectedLastReload = false;
×
UNCOV
1087
    safePutBBDomainInfo(bbnew);
×
UNCOV
1088
    g_log << Logger::Warning << "Zone '" << bbnew.d_name << "' (" << bbnew.d_filename << ") reloaded" << endl;
×
UNCOV
1089
  }
×
UNCOV
1090
  catch (PDNSException& ae) {
×
1091
    ostringstream msg;
×
1092
    msg << " error at " + nowTime() + " parsing '" << bbold.d_name << "' from file '" << bbold.d_filename << "': " << ae.reason;
×
1093
    g_log << Logger::Warning << "Error parsing '" << bbold.d_name << "' from file '" << bbold.d_filename << "': " << ae.reason << endl;
×
1094
    bbold.d_status = msg.str();
×
1095
    bbold.d_lastcheck = time(nullptr);
×
1096
    bbold.d_wasRejectedLastReload = true;
×
1097
    safePutBBDomainInfo(bbold);
×
1098
  }
×
UNCOV
1099
  catch (std::exception& ae) {
×
1100
    ostringstream msg;
×
1101
    msg << " error at " + nowTime() + " parsing '" << bbold.d_name << "' from file '" << bbold.d_filename << "': " << ae.what();
×
1102
    g_log << Logger::Warning << "Error parsing '" << bbold.d_name << "' from file '" << bbold.d_filename << "': " << ae.what() << endl;
×
1103
    bbold.d_status = msg.str();
×
1104
    bbold.d_lastcheck = time(nullptr);
×
1105
    bbold.d_wasRejectedLastReload = true;
×
1106
    safePutBBDomainInfo(bbold);
×
1107
  }
×
UNCOV
1108
}
×
1109

1110
bool Bind2Backend::findBeforeAndAfterUnhashed(std::shared_ptr<const recordstorage_t>& records, const DNSName& qname, DNSName& /* unhashed */, DNSName& before, DNSName& after)
UNCOV
1111
{
×
1112
  // for(const auto& record: *records)
1113
  //   cerr<<record.qname<<"\t"<<makeHexDump(record.qname.toDNSString())<<endl;
1114

UNCOV
1115
  recordstorage_t::const_iterator iterBefore, iterAfter;
×
1116

UNCOV
1117
  iterBefore = iterAfter = records->upper_bound(qname.makeLowerCase());
×
1118

UNCOV
1119
  if (iterBefore != records->begin())
×
UNCOV
1120
    --iterBefore;
×
UNCOV
1121
  while ((!iterBefore->auth && iterBefore->qtype != QType::NS) || !iterBefore->qtype)
×
UNCOV
1122
    --iterBefore;
×
UNCOV
1123
  before = iterBefore->qname;
×
1124

UNCOV
1125
  if (iterAfter == records->end()) {
×
UNCOV
1126
    iterAfter = records->begin();
×
UNCOV
1127
  }
×
UNCOV
1128
  else {
×
UNCOV
1129
    while ((!iterAfter->auth && iterAfter->qtype != QType::NS) || !iterAfter->qtype) {
×
UNCOV
1130
      ++iterAfter;
×
UNCOV
1131
      if (iterAfter == records->end()) {
×
1132
        iterAfter = records->begin();
×
1133
        break;
×
1134
      }
×
UNCOV
1135
    }
×
UNCOV
1136
  }
×
UNCOV
1137
  after = iterAfter->qname;
×
1138

UNCOV
1139
  return true;
×
UNCOV
1140
}
×
1141

1142
bool Bind2Backend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
UNCOV
1143
{
×
UNCOV
1144
  BB2DomainInfo bbd;
×
UNCOV
1145
  if (!safeGetBBDomainInfo(id, &bbd))
×
1146
    return false;
×
1147

UNCOV
1148
  shared_ptr<const recordstorage_t> records = bbd.d_records.get();
×
UNCOV
1149
  if (!bbd.d_nsec3zone) {
×
UNCOV
1150
    return findBeforeAndAfterUnhashed(records, qname, unhashed, before, after);
×
UNCOV
1151
  }
×
UNCOV
1152
  else {
×
UNCOV
1153
    const auto& hashindex = boost::multi_index::get<NSEC3Tag>(*records);
×
1154

1155
    // for(auto iter = first; iter != hashindex.end(); iter++)
1156
    //  cerr<<iter->nsec3hash<<endl;
1157

UNCOV
1158
    auto first = hashindex.upper_bound("");
×
UNCOV
1159
    auto iter = hashindex.upper_bound(qname.toStringNoDot());
×
1160

UNCOV
1161
    if (iter == hashindex.end()) {
×
UNCOV
1162
      --iter;
×
UNCOV
1163
      before = DNSName(iter->nsec3hash);
×
UNCOV
1164
      after = DNSName(first->nsec3hash);
×
UNCOV
1165
    }
×
UNCOV
1166
    else {
×
UNCOV
1167
      after = DNSName(iter->nsec3hash);
×
UNCOV
1168
      if (iter != first)
×
UNCOV
1169
        --iter;
×
UNCOV
1170
      else
×
UNCOV
1171
        iter = --hashindex.end();
×
UNCOV
1172
      before = DNSName(iter->nsec3hash);
×
UNCOV
1173
    }
×
UNCOV
1174
    unhashed = iter->qname + bbd.d_name;
×
1175

UNCOV
1176
    return true;
×
UNCOV
1177
  }
×
UNCOV
1178
}
×
1179

1180
void Bind2Backend::lookup(const QType& qtype, const DNSName& qname, int zoneId, DNSPacket* /* pkt_p */)
1181
{
20✔
1182
  d_handle.reset();
20✔
1183

1184
  static bool mustlog = ::arg().mustDo("query-logging");
20✔
1185

1186
  bool found = false;
20✔
1187
  DNSName domain;
20✔
1188
  BB2DomainInfo bbd;
20✔
1189

1190
  if (mustlog)
20!
1191
    g_log << Logger::Warning << "Lookup for '" << qtype.toString() << "' of '" << qname << "' within zoneID " << zoneId << endl;
×
1192

1193
  if (zoneId >= 0) {
20!
UNCOV
1194
    if ((found = (safeGetBBDomainInfo(zoneId, &bbd) && qname.isPartOf(bbd.d_name)))) {
×
UNCOV
1195
      domain = std::move(bbd.d_name);
×
UNCOV
1196
    }
×
UNCOV
1197
  }
×
1198
  else {
20✔
1199
    domain = qname;
20✔
1200
    do {
20✔
1201
      found = safeGetBBDomainInfo(domain, &bbd);
20✔
1202
    } while (!found && qtype != QType::SOA && domain.chopOff());
20!
1203
  }
20✔
1204

1205
  if (!found) {
20!
1206
    if (mustlog)
20!
1207
      g_log << Logger::Warning << "Found no authoritative zone for '" << qname << "' and/or id " << zoneId << endl;
×
1208
    d_handle.d_list = false;
20✔
1209
    return;
20✔
1210
  }
20✔
1211

UNCOV
1212
  if (mustlog)
×
1213
    g_log << Logger::Warning << "Found a zone '" << domain << "' (with id " << bbd.d_id << ") that might contain data " << endl;
×
1214

UNCOV
1215
  d_handle.id = bbd.d_id;
×
UNCOV
1216
  d_handle.qname = qname.makeRelative(domain); // strip domain name
×
UNCOV
1217
  d_handle.qtype = qtype;
×
UNCOV
1218
  d_handle.domain = std::move(domain);
×
1219

UNCOV
1220
  if (!bbd.current()) {
×
UNCOV
1221
    g_log << Logger::Warning << "Zone '" << d_handle.domain << "' (" << bbd.d_filename << ") needs reloading" << endl;
×
UNCOV
1222
    queueReloadAndStore(bbd.d_id);
×
UNCOV
1223
    if (!safeGetBBDomainInfo(d_handle.domain, &bbd))
×
1224
      throw DBException("Zone '" + bbd.d_name.toLogString() + "' (" + bbd.d_filename + ") gone after reload"); // if we don't throw here, we crash for some reason
×
UNCOV
1225
  }
×
1226

UNCOV
1227
  if (!bbd.d_loaded) {
×
UNCOV
1228
    d_handle.reset();
×
UNCOV
1229
    throw DBException("Zone for '" + d_handle.domain.toLogString() + "' in '" + bbd.d_filename + "' not loaded (file missing, corrupt or primary dead)"); // fsck
×
UNCOV
1230
  }
×
1231

UNCOV
1232
  d_handle.d_records = bbd.d_records.get();
×
1233

UNCOV
1234
  if (d_handle.d_records->empty())
×
1235
    DLOG(g_log << "Query with no results" << endl);
×
1236

UNCOV
1237
  d_handle.mustlog = mustlog;
×
1238

UNCOV
1239
  const auto& hashedidx = boost::multi_index::get<UnorderedNameTag>(*d_handle.d_records);
×
UNCOV
1240
  auto range = hashedidx.equal_range(d_handle.qname);
×
1241

UNCOV
1242
  d_handle.d_list = false;
×
UNCOV
1243
  d_handle.d_iter = range.first;
×
UNCOV
1244
  d_handle.d_end_iter = range.second;
×
UNCOV
1245
}
×
1246

1247
Bind2Backend::handle::handle()
1248
{
1,861✔
1249
  mustlog = false;
1,861✔
1250
}
1,861✔
1251

1252
bool Bind2Backend::get(DNSResourceRecord& r)
1253
{
20✔
1254
  if (!d_handle.d_records) {
20!
1255
    if (d_handle.mustlog)
20!
1256
      g_log << Logger::Warning << "There were no answers" << endl;
×
1257
    return false;
20✔
1258
  }
20✔
1259

UNCOV
1260
  if (!d_handle.get(r)) {
×
UNCOV
1261
    if (d_handle.mustlog)
×
1262
      g_log << Logger::Warning << "End of answers" << endl;
×
1263

UNCOV
1264
    d_handle.reset();
×
1265

UNCOV
1266
    return false;
×
UNCOV
1267
  }
×
UNCOV
1268
  if (d_handle.mustlog)
×
1269
    g_log << Logger::Warning << "Returning: '" << r.qtype.toString() << "' of '" << r.qname << "', content: '" << r.content << "'" << endl;
×
UNCOV
1270
  return true;
×
UNCOV
1271
}
×
1272

1273
bool Bind2Backend::handle::get(DNSResourceRecord& r)
UNCOV
1274
{
×
UNCOV
1275
  if (d_list)
×
UNCOV
1276
    return get_list(r);
×
UNCOV
1277
  else
×
UNCOV
1278
    return get_normal(r);
×
UNCOV
1279
}
×
1280

1281
void Bind2Backend::handle::reset()
1282
{
20✔
1283
  d_records.reset();
20✔
1284
  qname.clear();
20✔
1285
  mustlog = false;
20✔
1286
}
20✔
1287

1288
//#define DLOG(x) x
1289
bool Bind2Backend::handle::get_normal(DNSResourceRecord& r)
UNCOV
1290
{
×
UNCOV
1291
  DLOG(g_log << "Bind2Backend get() was called for " << qtype.toString() << " record for '" << qname << "' - " << d_records->size() << " available in total!" << endl);
×
1292

UNCOV
1293
  if (d_iter == d_end_iter) {
×
UNCOV
1294
    return false;
×
UNCOV
1295
  }
×
1296

UNCOV
1297
  while (d_iter != d_end_iter && !(qtype.getCode() == QType::ANY || (d_iter)->qtype == qtype.getCode())) {
×
UNCOV
1298
    DLOG(g_log << Logger::Warning << "Skipped " << qname << "/" << QType(d_iter->qtype).toString() << ": '" << d_iter->content << "'" << endl);
×
UNCOV
1299
    d_iter++;
×
UNCOV
1300
  }
×
UNCOV
1301
  if (d_iter == d_end_iter) {
×
1302
    return false;
×
1303
  }
×
UNCOV
1304
  DLOG(g_log << "Bind2Backend get() returning a rr with a " << QType(d_iter->qtype).getCode() << endl);
×
1305

UNCOV
1306
  r.qname = qname.empty() ? domain : (qname + domain);
×
UNCOV
1307
  r.domain_id = id;
×
UNCOV
1308
  r.content = (d_iter)->content;
×
1309
  //  r.domain_id=(d_iter)->domain_id;
UNCOV
1310
  r.qtype = (d_iter)->qtype;
×
UNCOV
1311
  r.ttl = (d_iter)->ttl;
×
1312

1313
  //if(!d_iter->auth && r.qtype.getCode() != QType::A && r.qtype.getCode()!=QType::AAAA && r.qtype.getCode() != QType::NS)
1314
  //  cerr<<"Warning! Unauth response for qtype "<< r.qtype.toString() << " for '"<<r.qname<<"'"<<endl;
UNCOV
1315
  r.auth = d_iter->auth;
×
1316

UNCOV
1317
  d_iter++;
×
1318

UNCOV
1319
  return true;
×
UNCOV
1320
}
×
1321

1322
bool Bind2Backend::list(const DNSName& /* target */, int id, bool /* include_disabled */)
UNCOV
1323
{
×
UNCOV
1324
  BB2DomainInfo bbd;
×
1325

UNCOV
1326
  if (!safeGetBBDomainInfo(id, &bbd))
×
1327
    return false;
×
1328

UNCOV
1329
  d_handle.reset();
×
UNCOV
1330
  DLOG(g_log << "Bind2Backend constructing handle for list of " << id << endl);
×
1331

UNCOV
1332
  if (!bbd.d_loaded) {
×
1333
    throw PDNSException("zone was not loaded, perhaps because of: " + bbd.d_status);
×
1334
  }
×
1335

UNCOV
1336
  d_handle.d_records = bbd.d_records.get(); // give it a copy, which will stay around
×
UNCOV
1337
  d_handle.d_qname_iter = d_handle.d_records->begin();
×
UNCOV
1338
  d_handle.d_qname_end = d_handle.d_records->end(); // iter now points to a vector of pointers to vector<BBResourceRecords>
×
1339

UNCOV
1340
  d_handle.id = id;
×
UNCOV
1341
  d_handle.domain = bbd.d_name;
×
UNCOV
1342
  d_handle.d_list = true;
×
UNCOV
1343
  return true;
×
UNCOV
1344
}
×
1345

1346
bool Bind2Backend::handle::get_list(DNSResourceRecord& r)
UNCOV
1347
{
×
UNCOV
1348
  if (d_qname_iter != d_qname_end) {
×
UNCOV
1349
    r.qname = d_qname_iter->qname.empty() ? domain : (d_qname_iter->qname + domain);
×
UNCOV
1350
    r.domain_id = id;
×
UNCOV
1351
    r.content = (d_qname_iter)->content;
×
UNCOV
1352
    r.qtype = (d_qname_iter)->qtype;
×
UNCOV
1353
    r.ttl = (d_qname_iter)->ttl;
×
UNCOV
1354
    r.auth = d_qname_iter->auth;
×
UNCOV
1355
    d_qname_iter++;
×
UNCOV
1356
    return true;
×
UNCOV
1357
  }
×
UNCOV
1358
  return false;
×
UNCOV
1359
}
×
1360

1361
bool Bind2Backend::autoPrimariesList(std::vector<AutoPrimary>& primaries)
1362
{
×
1363
  if (getArg("autoprimary-config").empty())
×
1364
    return false;
×
1365

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

1372
  string line, sip, saccount;
×
1373
  while (getline(c_if, line)) {
×
1374
    std::istringstream ii(line);
×
1375
    ii >> sip;
×
1376
    if (!sip.empty()) {
×
1377
      ii >> saccount;
×
1378
      primaries.emplace_back(sip, "", saccount);
×
1379
    }
×
1380
  }
×
1381

1382
  c_if.close();
×
1383
  return true;
×
1384
}
×
1385

1386
bool Bind2Backend::autoPrimaryBackend(const string& ip, const DNSName& /* domain */, const vector<DNSResourceRecord>& /* nsset */, string* /* nameserver */, string* account, DNSBackend** db)
1387
{
×
1388
  // Check whether we have a configfile available.
1389
  if (getArg("autoprimary-config").empty())
×
1390
    return false;
×
1391

1392
  ifstream c_if(getArg("autoprimaries").c_str(), std::ios::in); // this was nocreate?
×
1393
  if (!c_if) {
×
1394
    g_log << Logger::Error << "Unable to open autoprimaries file for read: " << stringerror() << endl;
×
1395
    return false;
×
1396
  }
×
1397

1398
  // Format:
1399
  // <ip> <accountname>
1400
  string line, sip, saccount;
×
1401
  while (getline(c_if, line)) {
×
1402
    std::istringstream ii(line);
×
1403
    ii >> sip;
×
1404
    if (sip == ip) {
×
1405
      ii >> saccount;
×
1406
      break;
×
1407
    }
×
1408
  }
×
1409
  c_if.close();
×
1410

1411
  if (sip != ip) // ip not found in authorization list - reject
×
1412
    return false;
×
1413

1414
  // ip authorized as autoprimary - accept
1415
  *db = this;
×
1416
  if (saccount.length() > 0)
×
1417
    *account = saccount.c_str();
×
1418

1419
  return true;
×
1420
}
×
1421

1422
BB2DomainInfo Bind2Backend::createDomainEntry(const DNSName& domain, const string& filename)
UNCOV
1423
{
×
UNCOV
1424
  int newid = 1;
×
UNCOV
1425
  { // Find a free zone id nr.
×
UNCOV
1426
    auto state = s_state.read_lock();
×
UNCOV
1427
    if (!state->empty()) {
×
1428
      // older (1.53) versions of boost have an expression for s_state.rbegin()
1429
      // that is ambiguous in C++17. So construct it explicitly
UNCOV
1430
      newid = boost::make_reverse_iterator(state->end())->d_id + 1;
×
UNCOV
1431
    }
×
UNCOV
1432
  }
×
1433

UNCOV
1434
  BB2DomainInfo bbd;
×
UNCOV
1435
  bbd.d_kind = DomainInfo::Native;
×
UNCOV
1436
  bbd.d_id = newid;
×
UNCOV
1437
  bbd.d_records = std::make_shared<recordstorage_t>();
×
UNCOV
1438
  bbd.d_name = domain;
×
UNCOV
1439
  bbd.setCheckInterval(getArgAsNum("check-interval"));
×
UNCOV
1440
  bbd.d_filename = filename;
×
1441

UNCOV
1442
  return bbd;
×
UNCOV
1443
}
×
1444

1445
bool Bind2Backend::createSecondaryDomain(const string& ipAddress, const DNSName& domain, const string& /* nameserver */, const string& account)
1446
{
×
1447
  std::string domainname = domain.toStringNoDot();
×
1448

1449
  // Reject domain name if it embeds quotes; this may happen if 8bit-dns is
1450
  // used, and bind currently does not allow for character escapes in zone
1451
  // names.
1452
  if (domainname.find_first_of("\"") != std::string::npos) {
×
1453
    SLOG(g_log << Logger::Error << d_logprefix
×
1454
               << " Unable to accept autosecondary zone '" << domain
×
1455
               << "' from autoprimary " << ipAddress
×
1456
               << " due to unauthorized characters in domain name for bind configuration file"
×
1457
               << endl,
×
1458
         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)));
×
1459
    throw PDNSException("Unauthorized characters in domain name for bind configuration file");
×
1460
  }
×
1461

1462
  string filename = getArg("autoprimary-destdir") + '/';
×
1463
  if (domainname.empty()) {
×
1464
    filename.append("rootzone.");
×
1465
  }
×
1466
  else {
×
1467
    // Make sure the zone file name does not contain path separators.
1468
    filename.append(boost::replace_all_copy(domainname, "/", "\\047"));
×
1469
  }
×
1470

1471
  g_log << Logger::Warning << d_logprefix
×
1472
        << " Writing bind config zone statement for superslave zone '" << domain
×
1473
        << "' from autoprimary " << ipAddress << endl;
×
1474

1475
  {
×
1476
    std::lock_guard<std::mutex> l2(s_autosecondary_config_lock);
×
1477

1478
    ofstream c_of(getArg("autoprimary-config").c_str(), std::ios::app);
×
1479
    if (!c_of) {
×
1480
      g_log << Logger::Error << "Unable to open autoprimary configfile for append: " << stringerror() << endl;
×
1481
      throw DBException("Unable to open autoprimary configfile for append: " + stringerror());
×
1482
    }
×
1483

1484
    c_of << endl;
×
1485
    c_of << "# AutoSecondary zone '" << domainname << "' (added: " << nowTime() << ") (account: " << account << ')' << endl;
×
1486
    c_of << "zone \"" << domainname << "\" {" << endl;
×
1487
    c_of << "\ttype secondary;" << endl;
×
1488
    c_of << "\tfile \"" << filename << "\";" << endl;
×
1489
    c_of << "\tprimaries { " << ipAddress << "; };" << endl;
×
1490
    c_of << "};" << endl;
×
1491
    c_of.close();
×
1492
  }
×
1493

1494
  BB2DomainInfo bbd = createDomainEntry(domain, filename);
×
1495
  bbd.d_kind = DomainInfo::Secondary;
×
1496
  bbd.d_primaries.push_back(ComboAddress(ipAddress, 53));
×
1497
  bbd.setCtime();
×
1498
  safePutBBDomainInfo(bbd);
×
1499

1500
  return true;
×
1501
}
×
1502

1503
bool Bind2Backend::searchRecords(const string& pattern, size_t maxResults, vector<DNSResourceRecord>& result)
1504
{
18✔
1505
  SimpleMatch sm(pattern, true);
18✔
1506
  static bool mustlog = ::arg().mustDo("query-logging");
18✔
1507
  if (mustlog)
18!
1508
    g_log << Logger::Warning << "Search for pattern '" << pattern << "'" << endl;
×
1509

1510
  {
18✔
1511
    auto state = s_state.read_lock();
18✔
1512

1513
    for (const auto& i : *state) {
18!
1514
      BB2DomainInfo h;
×
1515
      if (!safeGetBBDomainInfo(i.d_id, &h)) {
×
1516
        continue;
×
1517
      }
×
1518

1519
      if (!h.d_loaded) {
×
1520
        continue;
×
1521
      }
×
1522

1523
      shared_ptr<const recordstorage_t> rhandle = h.d_records.get();
×
1524

1525
      for (recordstorage_t::const_iterator ri = rhandle->begin(); result.size() < maxResults && ri != rhandle->end(); ri++) {
×
1526
        DNSName name = ri->qname.empty() ? i.d_name : (ri->qname + i.d_name);
×
1527
        if (sm.match(name) || sm.match(ri->content)) {
×
1528
          DNSResourceRecord r;
×
1529
          r.qname = std::move(name);
×
1530
          r.domain_id = i.d_id;
×
1531
          r.content = ri->content;
×
1532
          r.qtype = ri->qtype;
×
1533
          r.ttl = ri->ttl;
×
1534
          r.auth = ri->auth;
×
1535
          result.push_back(std::move(r));
×
1536
        }
×
1537
      }
×
1538
    }
×
1539
  }
18✔
1540

1541
  return true;
18✔
1542
}
18✔
1543

1544
class Bind2Factory : public BackendFactory
1545
{
1546
public:
1547
  Bind2Factory() :
1548
    BackendFactory("bind") {}
235✔
1549

1550
  void declareArguments(const string& suffix = "") override
1551
  {
235✔
1552
    declare(suffix, "ignore-broken-records", "Ignore records that are out-of-bound for the zone.", "no");
235✔
1553
    declare(suffix, "config", "Location of named.conf", "");
235✔
1554
    declare(suffix, "check-interval", "Interval for zonefile changes", "0");
235✔
1555
    declare(suffix, "autoprimary-config", "Location of (part of) named.conf where pdns can write zone-statements to", "");
235✔
1556
    declare(suffix, "autoprimaries", "List of IP-addresses of autoprimaries", "");
235✔
1557
    declare(suffix, "autoprimary-destdir", "Destination directory for newly added secondary zones", ::arg()["config-dir"]);
235✔
1558
    declare(suffix, "dnssec-db", "Filename to store & access our DNSSEC metadatabase, empty for none", "");
235✔
1559
    declare(suffix, "dnssec-db-journal-mode", "SQLite3 journal mode", "WAL");
235✔
1560
    declare(suffix, "hybrid", "Store DNSSEC metadata in other backend", "no");
235✔
1561
  }
235✔
1562

1563
  DNSBackend* make(const string& suffix = "") override
1564
  {
1,630✔
1565
    assertEmptySuffix(suffix);
1,630✔
1566
    return new Bind2Backend(suffix);
1,630✔
1567
  }
1,630✔
1568

1569
  DNSBackend* makeMetadataOnly(const string& suffix = "") override
1570
  {
231✔
1571
    assertEmptySuffix(suffix);
231✔
1572
    return new Bind2Backend(suffix, false);
231✔
1573
  }
231✔
1574

1575
private:
1576
  void assertEmptySuffix(const string& suffix)
1577
  {
1,861✔
1578
    if (!suffix.empty())
1,861!
1579
      throw PDNSException("launch= suffixes are not supported on the bindbackend");
×
1580
  }
1,861✔
1581
};
1582

1583
//! Magic class that is activated when the dynamic library is loaded
1584
class Bind2Loader
1585
{
1586
public:
1587
  Bind2Loader()
1588
  {
235✔
1589
    BackendMakers().report(std::make_unique<Bind2Factory>());
235✔
1590
    g_log << Logger::Info << "[bind2backend] This is the bind backend version " << VERSION
235✔
1591
#ifndef REPRODUCIBLE
235✔
1592
          << " (" __DATE__ " " __TIME__ ")"
235✔
1593
#endif
235✔
1594
#ifdef HAVE_SQLITE3
235✔
1595
          << " (with bind-dnssec-db support)"
235✔
1596
#endif
235✔
1597
          << " reporting" << endl;
235✔
1598
  }
235✔
1599
};
1600
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