2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
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.
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.
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.
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.
29 #include <sys/types.h>
35 #include <boost/algorithm/string.hpp>
36 #include <system_error>
37 #include <unordered_map>
38 #include <unordered_set>
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/namespaces.hh"
59 All instances of this backend share one s_state, which is indexed by zone name and zone id.
60 The s_state is protected by a read/write lock, and the goal it to only interact with it briefly.
61 When a query comes in, we take a read lock and COPY the best zone to answer from s_state (BB2DomainInfo object)
62 All answers are served from this copy.
64 To interact with s_state, use safeGetBBDomainInfo (search on name or id), safePutBBDomainInfo (to update)
65 or safeRemoveBBDomainInfo. These all lock as they should.
67 Several functions need to traverse s_state to get data for the rest of PowerDNS. When doing so,
68 you need to manually take the s_state_lock (read).
70 Parsing zones happens with parseZone(), which fills a BB2DomainInfo object. This can then be stored with safePutBBDomainInfo.
72 Finally, the BB2DomainInfo contains all records as a LookButDontTouch object. This makes sure you only look, but don't touch, since
73 the records might be in use in other places.
76 Bind2Backend::state_t
Bind2Backend::s_state
;
77 int Bind2Backend::s_first
=1;
78 bool Bind2Backend::s_ignore_broken_records
=false;
80 ReadWriteLock
Bind2Backend::s_state_lock
;
81 std::mutex
Bind2Backend::s_supermaster_config_lock
; // protects writes to config file
82 std::mutex
Bind2Backend::s_startup_lock
;
83 string
Bind2Backend::s_binddirectory
;
86 std::mutex LookButDontTouch
<T
>::s_lock
;
88 BB2DomainInfo::BB2DomainInfo()
96 void BB2DomainInfo::setCheckInterval(time_t seconds
)
98 d_checkinterval
=seconds
;
101 bool BB2DomainInfo::current()
110 if(time(0) - d_lastcheck
< d_checkinterval
)
113 if(d_filename
.empty())
116 return (getCtime()==d_ctime
);
119 time_t BB2DomainInfo::getCtime()
123 if(d_filename
.empty() || stat(d_filename
.c_str(),&buf
)<0)
129 void BB2DomainInfo::setCtime()
132 if(stat(d_filename
.c_str(),&buf
)<0)
134 d_ctime
=buf
.st_ctime
;
137 bool Bind2Backend::safeGetBBDomainInfo(int id
, BB2DomainInfo
* bbd
)
139 ReadLock
rl(&s_state_lock
);
140 state_t::const_iterator iter
= s_state
.find(id
);
141 if(iter
== s_state
.end())
147 bool Bind2Backend::safeGetBBDomainInfo(const DNSName
& name
, BB2DomainInfo
* bbd
)
149 ReadLock
rl(&s_state_lock
);
150 typedef state_t::index
<NameTag
>::type nameindex_t
;
151 nameindex_t
& nameindex
= boost::multi_index::get
<NameTag
>(s_state
);
153 nameindex_t::const_iterator iter
= nameindex
.find(name
);
154 if(iter
== nameindex
.end())
160 bool Bind2Backend::safeRemoveBBDomainInfo(const DNSName
& name
)
162 WriteLock
rl(&s_state_lock
);
163 typedef state_t::index
<NameTag
>::type nameindex_t
;
164 nameindex_t
& nameindex
= boost::multi_index::get
<NameTag
>(s_state
);
166 nameindex_t::iterator iter
= nameindex
.find(name
);
167 if(iter
== nameindex
.end())
169 nameindex
.erase(iter
);
173 void Bind2Backend::safePutBBDomainInfo(const BB2DomainInfo
& bbd
)
175 WriteLock
rl(&s_state_lock
);
176 replacing_insert(s_state
, bbd
);
179 void Bind2Backend::setNotified(uint32_t id
, uint32_t serial
)
182 if (!safeGetBBDomainInfo(id
, &bbd
))
184 bbd
.d_lastnotified
= serial
;
185 safePutBBDomainInfo(bbd
);
188 void Bind2Backend::setFresh(uint32_t domain_id
)
191 if(safeGetBBDomainInfo(domain_id
, &bbd
)) {
192 bbd
.d_lastcheck
=time(0);
193 safePutBBDomainInfo(bbd
);
197 bool Bind2Backend::startTransaction(const DNSName
&qname
, int id
)
200 d_transaction_tmpname
.clear();
205 throw DBException("domain_id 0 is invalid for this backend.");
210 if(safeGetBBDomainInfo(id
, &bbd
)) {
211 d_transaction_tmpname
= bbd
.d_filename
+ "XXXXXX";
212 int fd
= mkstemp(&d_transaction_tmpname
.at(0));
214 throw DBException("Unable to create a unique temporary zonefile '"+d_transaction_tmpname
+"': "+stringerror());
218 d_of
= std::unique_ptr
<ofstream
>(new ofstream(d_transaction_tmpname
.c_str()));
220 unlink(d_transaction_tmpname
.c_str());
224 throw DBException("Unable to open temporary zonefile '"+d_transaction_tmpname
+"': "+stringerror());
229 *d_of
<<"; Written by PowerDNS, don't edit!"<<endl
;
230 *d_of
<<"; Zone '"<<bbd
.d_name
<<"' retrieved from master "<<endl
<<"; at "<<nowTime()<<endl
; // insert master info here again
237 bool Bind2Backend::commitTransaction()
239 if(d_transaction_id
< 0)
244 if(safeGetBBDomainInfo(d_transaction_id
, &bbd
)) {
245 if(rename(d_transaction_tmpname
.c_str(), bbd
.d_filename
.c_str())<0)
246 throw DBException("Unable to commit (rename to: '" + bbd
.d_filename
+"') AXFRed zone: "+stringerror());
247 queueReloadAndStore(bbd
.d_id
);
255 bool Bind2Backend::abortTransaction()
257 // -1 = dnssec speciality
258 // 0 = invalid transact
259 // >0 = actual transaction
260 if(d_transaction_id
> 0) {
261 unlink(d_transaction_tmpname
.c_str());
269 bool Bind2Backend::feedRecord(const DNSResourceRecord
&rr
, const DNSName
&ordername
, bool ordernameIsNSEC3
)
272 if (!safeGetBBDomainInfo(d_transaction_id
, &bbd
))
276 string name
= bbd
.d_name
.toString();
277 if (bbd
.d_name
.empty()) {
278 qname
= rr
.qname
.toString();
280 else if (rr
.qname
.isPartOf(bbd
.d_name
)) {
281 if (rr
.qname
== bbd
.d_name
) {
285 DNSName relName
= rr
.qname
.makeRelative(bbd
.d_name
);
286 qname
= relName
.toStringNoDot();
290 throw DBException("out-of-zone data '"+rr
.qname
.toLogString()+"' during AXFR of zone '"+bbd
.d_name
.toLogString()+"'");
293 shared_ptr
<DNSRecordContent
> drc(DNSRecordContent::mastermake(rr
.qtype
.getCode(), 1, rr
.content
));
294 string content
= drc
->getZoneRepresentation();
296 // SOA needs stripping too! XXX FIXME - also, this should not be here I think
297 switch(rr
.qtype
.getCode()) {
303 stripDomainSuffix(&content
, name
);
307 *d_of
<<qname
<<"\t"<<rr
.ttl
<<"\t"<<rr
.qtype
.getName()<<"\t"<<content
<<endl
;
313 void Bind2Backend::getUpdatedMasters(vector
<DomainInfo
> *changedDomains
)
315 vector
<DomainInfo
> consider
;
317 ReadLock
rl(&s_state_lock
);
319 for(state_t::const_iterator i
= s_state
.begin(); i
!= s_state
.end() ; ++i
) {
320 if(i
->d_kind
!= DomainInfo::Master
&& this->alsoNotify
.empty() && i
->d_also_notify
.empty())
326 di
.last_check
=i
->d_lastcheck
;
327 di
.notified_serial
=i
->d_lastnotified
;
329 di
.kind
=DomainInfo::Master
;
330 consider
.push_back(std::move(di
));
335 for(DomainInfo
& di
: consider
) {
338 this->getSOA(di
.zone
, soadata
); // we might not *have* a SOA yet, but this might trigger a load of it
343 if(di
.notified_serial
!= soadata
.serial
) {
345 if(safeGetBBDomainInfo(di
.id
, &bbd
)) {
346 bbd
.d_lastnotified
=soadata
.serial
;
347 safePutBBDomainInfo(bbd
);
349 if(di
.notified_serial
) { // don't do notification storm on startup
350 di
.serial
=soadata
.serial
;
351 changedDomains
->push_back(std::move(di
));
357 void Bind2Backend::getAllDomains(vector
<DomainInfo
> *domains
, bool include_disabled
)
361 // prevent deadlock by using getSOA() later on
363 ReadLock
rl(&s_state_lock
);
364 domains
->reserve(s_state
.size());
366 for(state_t::const_iterator i
= s_state
.begin(); i
!= s_state
.end() ; ++i
) {
370 di
.last_check
=i
->d_lastcheck
;
372 di
.masters
=i
->d_masters
;
374 domains
->push_back(std::move(di
));
378 for(DomainInfo
&di
: *domains
) {
379 // do not corrupt di if domain supplied by another backend.
380 if (di
.backend
!= this)
383 this->getSOA(di
.zone
, soadata
);
387 di
.serial
=soadata
.serial
;
391 void Bind2Backend::getUnfreshSlaveInfos(vector
<DomainInfo
> *unfreshDomains
)
393 vector
<DomainInfo
> domains
;
395 ReadLock
rl(&s_state_lock
);
396 domains
.reserve(s_state
.size());
397 for(state_t::const_iterator i
= s_state
.begin(); i
!= s_state
.end() ; ++i
) {
398 if(i
->d_kind
!= DomainInfo::Slave
)
403 sd
.masters
=i
->d_masters
;
404 sd
.last_check
=i
->d_lastcheck
;
406 sd
.kind
=DomainInfo::Slave
;
407 domains
.push_back(std::move(sd
));
410 unfreshDomains
->reserve(domains
.size());
412 for(DomainInfo
&sd
: domains
) {
417 getSOA(sd
.zone
,soadata
); // we might not *have* a SOA yet
420 sd
.serial
=soadata
.serial
;
421 if(sd
.last_check
+soadata
.refresh
< (unsigned int)time(0))
422 unfreshDomains
->push_back(std::move(sd
));
426 bool Bind2Backend::getDomainInfo(const DNSName
& domain
, DomainInfo
&di
, bool getSerial
)
429 if(!safeGetBBDomainInfo(domain
, &bbd
))
434 di
.masters
=bbd
.d_masters
;
435 di
.last_check
=bbd
.d_lastcheck
;
444 getSOA(bbd
.d_name
,sd
); // we might not *have* a SOA yet
453 void Bind2Backend::alsoNotifies(const DNSName
& domain
, set
<string
> *ips
)
455 // combine global list with local list
456 for(set
<string
>::iterator i
= this->alsoNotify
.begin(); i
!= this->alsoNotify
.end(); i
++) {
459 // check metadata too if available
461 if (getDomainMetadata(domain
, "ALSO-NOTIFY", meta
)) {
462 for(const auto& str
: meta
) {
466 ReadLock
rl(&s_state_lock
);
467 for(state_t::const_iterator i
= s_state
.begin(); i
!= s_state
.end() ; ++i
) {
468 if(i
->d_name
== domain
) {
469 for(set
<string
>::iterator it
= i
->d_also_notify
.begin(); it
!= i
->d_also_notify
.end(); it
++) {
477 // only parses, does NOT add to s_state!
478 void Bind2Backend::parseZoneFile(BB2DomainInfo
*bbd
)
480 NSEC3PARAMRecordContent ns3pr
;
484 nsec3zone
=dk
.getNSEC3PARAM(bbd
->d_name
, &ns3pr
);
486 nsec3zone
=getNSEC3PARAM(bbd
->d_name
, &ns3pr
);
488 auto records
= std::make_shared
<recordstorage_t
>();
489 ZoneParserTNG
zpt(bbd
->d_filename
, bbd
->d_name
, s_binddirectory
);
490 zpt
.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
491 DNSResourceRecord rr
;
494 if(rr
.qtype
.getCode() == QType::NSEC
|| rr
.qtype
.getCode() == QType::NSEC3
|| rr
.qtype
.getCode() == QType::NSEC3PARAM
)
495 continue; // we synthesise NSECs on demand
497 insertRecord(records
, bbd
->d_name
, rr
.qname
, rr
.qtype
, rr
.content
, rr
.ttl
, "");
499 fixupOrderAndAuth(records
, bbd
->d_name
, nsec3zone
, ns3pr
);
500 doEmptyNonTerminals(records
, bbd
->d_name
, nsec3zone
, ns3pr
);
503 bbd
->d_checknow
=false;
504 bbd
->d_status
="parsed into memory at "+nowTime();
505 bbd
->d_records
= LookButDontTouch
<recordstorage_t
>(records
);
508 /** THIS IS AN INTERNAL FUNCTION! It does moadnsparser prio impedance matching
509 Much of the complication is due to the efforts to benefit from std::string reference counting copy on write semantics */
510 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
)
517 else if(bdr
.qname
.isPartOf(zoneName
))
518 bdr
.qname
.makeUsRelative(zoneName
);
520 string msg
= "Trying to insert non-zone data, name='"+bdr
.qname
.toLogString()+"', qtype="+qtype
.getName()+", zone='"+zoneName
.toLogString()+"'";
521 if(s_ignore_broken_records
) {
522 g_log
<<Logger::Warning
<<msg
<< " ignored" << endl
;
526 throw PDNSException(msg
);
529 // bdr.qname.swap(bdr.qname);
531 if(!records
->empty() && bdr
.qname
==boost::prior(records
->end())->qname
)
532 bdr
.qname
=boost::prior(records
->end())->qname
;
535 bdr
.qtype
=qtype
.getCode();
537 bdr
.nsec3hash
= hashed
;
539 if (auth
) // Set auth on empty non-terminals
545 records
->insert(std::move(bdr
));
548 string
Bind2Backend::DLReloadNowHandler(const vector
<string
>&parts
, Utility::pid_t ppid
)
552 for(vector
<string
>::const_iterator i
=parts
.begin()+1;i
<parts
.end();++i
) {
555 if(safeGetBBDomainInfo(zone
, &bbd
)) {
557 bb2
.queueReloadAndStore(bbd
.d_id
);
558 if (!safeGetBBDomainInfo(zone
, &bbd
)) // Read the *new* domain status
559 ret
<< *i
<< ": [missing]\n";
561 ret
<< *i
<< ": "<< (bbd
.d_wasRejectedLastReload
? "[rejected]": "") <<"\t"<<bbd
.d_status
<<"\n";
564 ret
<< *i
<< " no such domain\n";
566 if(ret
.str().empty())
567 ret
<<"no domains reloaded";
572 string
Bind2Backend::DLDomStatusHandler(const vector
<string
>&parts
, Utility::pid_t ppid
)
576 if(parts
.size() > 1) {
577 for(vector
<string
>::const_iterator i
=parts
.begin()+1;i
<parts
.end();++i
) {
579 if(safeGetBBDomainInfo(DNSName(*i
), &bbd
)) {
580 ret
<< *i
<< ": "<< (bbd
.d_loaded
? "": "[rejected]") <<"\t"<<bbd
.d_status
<<"\n";
583 ret
<< *i
<< " no such domain\n";
588 ReadLock
rl(&s_state_lock
);
589 for(state_t::const_iterator i
= s_state
.begin(); i
!= s_state
.end() ; ++i
) {
590 ret
<< i
->d_name
<< ": "<< (i
->d_loaded
? "": "[rejected]") <<"\t"<<i
->d_status
<<"\n";
594 if(ret
.str().empty())
595 ret
<<"no domains passed";
600 static void printDomainExtendedStatus(ostringstream
& ret
, const BB2DomainInfo
& info
)
602 ret
<< info
.d_name
<< ": " << std::endl
;
603 ret
<< "\t Status: " << info
.d_status
<< std::endl
;
604 ret
<< "\t Internal ID: " << info
.d_id
<< std::endl
;
605 ret
<< "\t On-disk file: " << info
.d_filename
<< " (" << info
.d_ctime
<< ")" << std::endl
;
607 switch (info
.d_kind
) {
608 case DomainInfo::Master
:
611 case DomainInfo::Slave
:
618 ret
<< "\t Masters: " << std::endl
;
619 for (const auto& master
: info
.d_masters
) {
620 ret
<< "\t\t - " << master
.toStringWithPort() << std::endl
;
622 ret
<< "\t Also Notify: " << std::endl
;
623 for (const auto& also
: info
.d_also_notify
) {
624 ret
<< "\t\t - " << also
<< std::endl
;
626 ret
<< "\t Number of records: " << info
.d_records
.getEntriesCount() << std::endl
;
627 ret
<< "\t Loaded: " << info
.d_loaded
<< std::endl
;
628 ret
<< "\t Check now: " << info
.d_checknow
<< std::endl
;
629 ret
<< "\t Check interval: " << info
.getCheckInterval() << std::endl
;
630 ret
<< "\t Last check: " << info
.d_lastcheck
<< std::endl
;
631 ret
<< "\t Last notified: " << info
.d_lastnotified
<< std::endl
;
634 string
Bind2Backend::DLDomExtendedStatusHandler(const vector
<string
>&parts
, Utility::pid_t ppid
)
638 if (parts
.size() > 1) {
639 for (vector
<string
>::const_iterator i
=parts
.begin()+1;i
<parts
.end();++i
) {
641 if (safeGetBBDomainInfo(DNSName(*i
), &bbd
)) {
642 printDomainExtendedStatus(ret
, bbd
);
645 ret
<< *i
<< " no such domain" << std::endl
;
650 ReadLock
rl(&s_state_lock
);
651 for (const auto& state
: s_state
) {
652 printDomainExtendedStatus(ret
, state
);
656 if (ret
.str().empty()) {
657 ret
<< "no domains passed" << std::endl
;
663 string
Bind2Backend::DLListRejectsHandler(const vector
<string
>&parts
, Utility::pid_t ppid
)
666 ReadLock
rl(&s_state_lock
);
667 for(state_t::const_iterator i
= s_state
.begin(); i
!= s_state
.end() ; ++i
) {
669 ret
<<i
->d_name
<<"\t"<<i
->d_status
<<endl
;
674 string
Bind2Backend::DLAddDomainHandler(const vector
<string
>&parts
, Utility::pid_t ppid
)
677 return "ERROR: Domain name and zone filename are required";
679 DNSName
domainname(parts
[1]);
680 const string
&filename
= parts
[2];
682 if(safeGetBBDomainInfo(domainname
, &bbd
))
683 return "Already loaded";
685 if (!boost::starts_with(filename
, "/") && ::arg()["chroot"].empty())
686 return "Unable to load zone " + domainname
.toLogString() + " from " + filename
+ " as the filename is not absolute.";
689 if (stat(filename
.c_str(), &buf
) != 0)
690 return "Unable to load zone " + domainname
.toLogString() + " from " + filename
+ ": " + strerror(errno
);
692 Bind2Backend bb2
; // createdomainentry needs access to our configuration
693 bbd
=bb2
.createDomainEntry(domainname
, filename
);
694 bbd
.d_filename
=filename
;
698 bbd
.d_status
="parsing into memory";
701 safePutBBDomainInfo(bbd
);
703 g_log
<<Logger::Warning
<<"Zone "<<domainname
<< " loaded"<<endl
;
704 return "Loaded zone " + domainname
.toLogString() + " from " + filename
;
707 Bind2Backend::Bind2Backend(const string
&suffix
, bool loadZones
)
709 d_getAllDomainMetadataQuery_stmt
= NULL
;
710 d_getDomainMetadataQuery_stmt
= NULL
;
711 d_deleteDomainMetadataQuery_stmt
= NULL
;
712 d_insertDomainMetadataQuery_stmt
= NULL
;
713 d_getDomainKeysQuery_stmt
= NULL
;
714 d_deleteDomainKeyQuery_stmt
= NULL
;
715 d_insertDomainKeyQuery_stmt
= NULL
;
716 d_GetLastInsertedKeyIdQuery_stmt
= NULL
;
717 d_activateDomainKeyQuery_stmt
= NULL
;
718 d_deactivateDomainKeyQuery_stmt
= NULL
;
719 d_getTSIGKeyQuery_stmt
= NULL
;
720 d_setTSIGKeyQuery_stmt
= NULL
;
721 d_deleteTSIGKeyQuery_stmt
= NULL
;
722 d_getTSIGKeysQuery_stmt
= NULL
;
724 setArgPrefix("bind"+suffix
);
725 d_logprefix
="[bind"+suffix
+"backend]";
726 d_hybrid
=mustDo("hybrid");
728 s_ignore_broken_records
=mustDo("ignore-broken-records");
730 if (!loadZones
&& d_hybrid
)
733 std::lock_guard
<std::mutex
> l(s_startup_lock
);
745 extern DynListener
*dl
;
746 dl
->registerFunc("BIND-RELOAD-NOW", &DLReloadNowHandler
, "bindbackend: reload domains", "<domains>");
747 dl
->registerFunc("BIND-DOMAIN-STATUS", &DLDomStatusHandler
, "bindbackend: list status of all domains", "[domains]");
748 dl
->registerFunc("BIND-DOMAIN-EXTENDED-STATUS", &DLDomExtendedStatusHandler
, "bindbackend: list the extended status of all domains", "[domains]");
749 dl
->registerFunc("BIND-LIST-REJECTS", &DLListRejectsHandler
, "bindbackend: list rejected domains");
750 dl
->registerFunc("BIND-ADD-ZONE", &DLAddDomainHandler
, "bindbackend: add zone", "<domain> <filename>");
753 Bind2Backend::~Bind2Backend()
754 { freeStatements(); } // deallocate statements
756 void Bind2Backend::rediscover(string
*status
)
761 void Bind2Backend::reload()
763 WriteLock
rwl(&s_state_lock
);
764 for(state_t::iterator i
= s_state
.begin(); i
!= s_state
.end() ; ++i
) {
765 i
->d_checknow
=true; // being a bit cheeky here, don't index state_t on this (mutable)
769 void Bind2Backend::fixupOrderAndAuth(std::shared_ptr
<recordstorage_t
>& records
, const DNSName
& zoneName
, bool nsec3zone
, NSEC3PARAMRecordContent ns3pr
)
773 set
<DNSName
> nssets
, dssets
;
775 for(const auto& bdr
: *records
) {
776 if(!bdr
.qname
.isRoot() && bdr
.qtype
== QType::NS
)
777 nssets
.insert(bdr
.qname
);
778 else if(bdr
.qtype
== QType::DS
)
779 dssets
.insert(bdr
.qname
);
782 for(auto iter
= records
->begin(); iter
!= records
->end(); iter
++) {
784 shorter
= iter
->qname
;
786 if (!iter
->qname
.isRoot() && shorter
.chopOff() && !iter
->qname
.isRoot()) {
788 if(nssets
.count(shorter
)) {
792 } while(shorter
.chopOff() && !iter
->qname
.isRoot());
795 iter
->auth
= (!skip
&& (iter
->qtype
== QType::DS
|| iter
->qtype
== QType::RRSIG
|| !nssets
.count(iter
->qname
)));
797 if(!skip
&& nsec3zone
&& iter
->qtype
!= QType::RRSIG
&& (iter
->auth
|| (iter
->qtype
== QType::NS
&& !ns3pr
.d_flags
) || dssets
.count(iter
->qname
))) {
798 Bind2DNSRecord bdr
= *iter
;
799 bdr
.nsec3hash
= toBase32Hex(hashQNameWithSalt(ns3pr
, bdr
.qname
+zoneName
));
800 records
->replace(iter
, bdr
);
803 // cerr<<iter->qname<<"\t"<<QType(iter->qtype).getName()<<"\t"<<iter->nsec3hash<<"\t"<<iter->auth<<endl;
807 void Bind2Backend::doEmptyNonTerminals(std::shared_ptr
<recordstorage_t
>& records
, const DNSName
& zoneName
, bool nsec3zone
, NSEC3PARAMRecordContent ns3pr
)
811 std::unordered_set
<DNSName
> qnames
;
812 std::unordered_map
<DNSName
, bool> nonterm
;
814 uint32_t maxent
= ::arg().asNum("max-ent-entries");
816 for(const auto& bdr
: *records
)
817 qnames
.insert(bdr
.qname
);
819 for(const auto& bdr
: *records
) {
821 if (!bdr
.auth
&& bdr
.qtype
== QType::NS
)
822 auth
= (!nsec3zone
|| !ns3pr
.d_flags
);
827 while(shorter
.chopOff())
829 if(!qnames
.count(shorter
))
833 g_log
<<Logger::Error
<<"Zone '"<<zoneName
<<"' has too many empty non terminals."<<endl
;
837 if (!nonterm
.count(shorter
)) {
838 nonterm
.emplace(shorter
, auth
);
841 nonterm
[shorter
] = true;
846 DNSResourceRecord rr
;
850 for(auto& nt
: nonterm
)
853 rr
.qname
= nt
.first
+ zoneName
;
854 if(nsec3zone
&& nt
.second
)
855 hashed
= toBase32Hex(hashQNameWithSalt(ns3pr
, rr
.qname
));
856 insertRecord(records
, zoneName
, rr
.qname
, rr
.qtype
, rr
.content
, rr
.ttl
, hashed
, &nt
.second
);
858 // cerr<<rr.qname<<"\t"<<rr.qtype.getName()<<"\t"<<hashed<<"\t"<<nt.second<<endl;
862 void Bind2Backend::loadConfig(string
* status
)
864 static int domain_id
=1;
866 if(!getArg("config").empty()) {
869 BP
.parse(getArg("config"));
871 catch(PDNSException
&ae
) {
872 g_log
<<Logger::Error
<<"Error parsing bind configuration: "<<ae
.reason
<<endl
;
876 vector
<BindDomainInfo
> domains
=BP
.getDomains();
877 this->alsoNotify
= BP
.getAlsoNotify();
879 s_binddirectory
=BP
.getDirectory();
880 // ZP.setDirectory(d_binddirectory);
882 g_log
<<Logger::Warning
<<d_logprefix
<<" Parsing "<<domains
.size()<<" domain(s), will report when done"<<endl
;
884 set
<DNSName
> oldnames
, newnames
;
886 ReadLock
rl(&s_state_lock
);
887 for(const BB2DomainInfo
& bbd
: s_state
) {
888 oldnames
.insert(bbd
.d_name
);
896 for(vector
<BindDomainInfo
>::iterator i
=domains
.begin(); i
!=domains
.end(); ++i
)
898 if(stat(i
->filename
.c_str(), &st
) == 0) {
899 i
->d_dev
= st
.st_dev
;
900 i
->d_ino
= st
.st_ino
;
904 sort(domains
.begin(), domains
.end()); // put stuff in inode order
905 for(vector
<BindDomainInfo
>::const_iterator i
=domains
.begin();
909 if (!(i
->hadFileDirective
)) {
910 g_log
<<Logger::Warning
<<d_logprefix
<<" Zone '"<<i
->name
<<"' has no 'file' directive set in "<<getArg("config")<<endl
;
916 g_log
<<Logger::Notice
<<d_logprefix
<<" Zone '"<<i
->name
<<"' has no type specified, assuming 'native'"<<endl
;
917 if(i
->type
!="master" && i
->type
!="slave" && i
->type
!= "native" && i
->type
!= "") {
918 g_log
<<Logger::Warning
<<d_logprefix
<<" Warning! Skipping zone '"<<i
->name
<<"' because type '"<<i
->type
<<"' is invalid"<<endl
;
926 if(!safeGetBBDomainInfo(i
->name
, &bbd
)) {
928 bbd
.d_id
=domain_id
++;
929 bbd
.setCheckInterval(getArgAsNum("check-interval"));
930 bbd
.d_lastnotified
=0;
934 // overwrite what we knew about the domain
936 bool filenameChanged
= (bbd
.d_filename
!=i
->filename
);
937 bbd
.d_filename
=i
->filename
;
938 bbd
.d_masters
=i
->masters
;
939 bbd
.d_also_notify
=i
->alsoNotify
;
941 bbd
.d_kind
= DomainInfo::Native
;
942 if (i
->type
== "master")
943 bbd
.d_kind
= DomainInfo::Master
;
944 if (i
->type
== "slave")
945 bbd
.d_kind
= DomainInfo::Slave
;
947 newnames
.insert(bbd
.d_name
);
948 if(filenameChanged
|| !bbd
.d_loaded
|| !bbd
.current()) {
949 g_log
<<Logger::Info
<<d_logprefix
<<" parsing '"<<i
->name
<<"' from file '"<<i
->filename
<<"'"<<endl
;
954 catch(PDNSException
&ae
) {
956 msg
<<" error at "+nowTime()+" parsing '"<<i
->name
<<"' from file '"<<i
->filename
<<"': "<<ae
.reason
;
960 bbd
.d_status
=msg
.str();
962 g_log
<<Logger::Warning
<<d_logprefix
<<msg
.str()<<endl
;
965 catch(std::system_error
&ae
) {
967 if (ae
.code().value() == ENOENT
&& isNew
&& i
->type
== "slave")
968 msg
<<" error at "+nowTime()<<" no file found for new slave domain '"<<i
->name
<<"'. Has not been AXFR'd yet";
970 msg
<<" error at "+nowTime()+" parsing '"<<i
->name
<<"' from file '"<<i
->filename
<<"': "<<ae
.what();
974 bbd
.d_status
=msg
.str();
975 g_log
<<Logger::Warning
<<d_logprefix
<<msg
.str()<<endl
;
978 catch(std::exception
&ae
) {
980 msg
<<" error at "+nowTime()+" parsing '"<<i
->name
<<"' from file '"<<i
->filename
<<"': "<<ae
.what();
984 bbd
.d_status
=msg
.str();
986 g_log
<<Logger::Warning
<<d_logprefix
<<msg
.str()<<endl
;
989 safePutBBDomainInfo(bbd
);
993 vector
<DNSName
> diff
;
995 set_difference(oldnames
.begin(), oldnames
.end(), newnames
.begin(), newnames
.end(), back_inserter(diff
));
996 unsigned int remdomains
=diff
.size();
998 for(const DNSName
& name
: diff
) {
999 safeRemoveBBDomainInfo(name
);
1002 // count number of entirely new domains
1004 set_difference(newnames
.begin(), newnames
.end(), oldnames
.begin(), oldnames
.end(), back_inserter(diff
));
1005 newdomains
=diff
.size();
1008 msg
<<" Done parsing domains, "<<rejected
<<" rejected, "<<newdomains
<<" new, "<<remdomains
<<" removed";
1012 g_log
<<Logger::Error
<<d_logprefix
<<msg
.str()<<endl
;
1016 void Bind2Backend::queueReloadAndStore(unsigned int id
)
1018 BB2DomainInfo bbold
;
1020 if(!safeGetBBDomainInfo(id
, &bbold
))
1022 BB2DomainInfo
bbnew(bbold
);
1023 /* make sure that nothing will be able to alter the existing records,
1024 we will load them from the zone file instead */
1025 bbnew
.d_records
= LookButDontTouch
<recordstorage_t
>();
1026 parseZoneFile(&bbnew
);
1027 bbnew
.d_checknow
=false;
1028 bbnew
.d_wasRejectedLastReload
=false;
1029 safePutBBDomainInfo(bbnew
);
1030 g_log
<<Logger::Warning
<<"Zone '"<<bbnew
.d_name
<<"' ("<<bbnew
.d_filename
<<") reloaded"<<endl
;
1032 catch(PDNSException
&ae
) {
1034 msg
<<" error at "+nowTime()+" parsing '"<<bbold
.d_name
<<"' from file '"<<bbold
.d_filename
<<"': "<<ae
.reason
;
1035 g_log
<<Logger::Warning
<<" error parsing '"<<bbold
.d_name
<<"' from file '"<<bbold
.d_filename
<<"': "<<ae
.reason
<<endl
;
1036 bbold
.d_status
=msg
.str();
1037 bbold
.d_wasRejectedLastReload
=true;
1038 safePutBBDomainInfo(bbold
);
1040 catch(std::exception
&ae
) {
1042 msg
<<" error at "+nowTime()+" parsing '"<<bbold
.d_name
<<"' from file '"<<bbold
.d_filename
<<"': "<<ae
.what();
1043 g_log
<<Logger::Warning
<<" error parsing '"<<bbold
.d_name
<<"' from file '"<<bbold
.d_filename
<<"': "<<ae
.what()<<endl
;
1044 bbold
.d_status
=msg
.str();
1045 bbold
.d_wasRejectedLastReload
=true;
1046 safePutBBDomainInfo(bbold
);
1050 bool Bind2Backend::findBeforeAndAfterUnhashed(std::shared_ptr
<const recordstorage_t
>& records
, const DNSName
& qname
, DNSName
& unhashed
, DNSName
& before
, DNSName
& after
)
1052 // for(const auto& record: *records)
1053 // cerr<<record.qname<<"\t"<<makeHexDump(record.qname.toDNSString())<<endl;
1055 recordstorage_t::const_iterator iterBefore
, iterAfter
;
1057 iterBefore
= iterAfter
= records
->upper_bound(qname
.makeLowerCase());
1059 if(iterBefore
!= records
->begin())
1061 while((!iterBefore
->auth
&& iterBefore
->qtype
!= QType::NS
) || !iterBefore
->qtype
)
1063 before
=iterBefore
->qname
;
1065 if(iterAfter
== records
->end()) {
1066 iterAfter
= records
->begin();
1068 while((!iterAfter
->auth
&& iterAfter
->qtype
!= QType::NS
) || !iterAfter
->qtype
) {
1070 if(iterAfter
== records
->end()) {
1071 iterAfter
= records
->begin();
1076 after
= iterAfter
->qname
;
1081 bool Bind2Backend::getBeforeAndAfterNamesAbsolute(uint32_t id
, const DNSName
& qname
, DNSName
& unhashed
, DNSName
& before
, DNSName
& after
)
1084 if (!safeGetBBDomainInfo(id
, &bbd
))
1087 NSEC3PARAMRecordContent ns3pr
;
1092 nsec3zone
=dk
.getNSEC3PARAM(bbd
.d_name
, &ns3pr
);
1094 nsec3zone
=getNSEC3PARAM(bbd
.d_name
, &ns3pr
);
1096 shared_ptr
<const recordstorage_t
> records
= bbd
.d_records
.get();
1098 return findBeforeAndAfterUnhashed(records
, qname
, unhashed
, before
, after
);
1101 auto& hashindex
=boost::multi_index::get
<NSEC3Tag
>(*records
);
1103 // for(auto iter = first; iter != hashindex.end(); iter++)
1104 // cerr<<iter->nsec3hash<<endl;
1106 auto first
= hashindex
.upper_bound("");
1107 auto iter
= hashindex
.upper_bound(qname
.toStringNoDot());
1109 if (iter
== hashindex
.end()) {
1111 before
= DNSName(iter
->nsec3hash
);
1112 after
= DNSName(first
->nsec3hash
);
1114 after
= DNSName(iter
->nsec3hash
);
1118 iter
= --hashindex
.end();
1119 before
= DNSName(iter
->nsec3hash
);
1121 unhashed
= iter
->qname
+bbd
.d_name
;
1127 void Bind2Backend::lookup(const QType
&qtype
, const DNSName
&qname
, int zoneId
, DNSPacket
*pkt_p
)
1131 static bool mustlog
=::arg().mustDo("query-logging");
1138 g_log
<<Logger::Warning
<<"Lookup for '"<<qtype
.getName()<<"' of '"<<qname
<<"' within zoneID "<<zoneId
<<endl
;
1141 if ((found
= (safeGetBBDomainInfo(zoneId
, &bbd
) && qname
.isPartOf(bbd
.d_name
)))) {
1142 domain
= std::move(bbd
.d_name
);
1147 found
= safeGetBBDomainInfo(domain
, &bbd
);
1148 } while (!found
&& qtype
!= QType::SOA
&& domain
.chopOff());
1153 g_log
<<Logger::Warning
<<"Found no authoritative zone for '"<<qname
<<"' and/or id "<<bbd
.d_id
<<endl
;
1154 d_handle
.d_list
=false;
1159 g_log
<<Logger::Warning
<<"Found a zone '"<<domain
<<"' (with id " << bbd
.d_id
<<") that might contain data "<<endl
;
1161 d_handle
.id
=bbd
.d_id
;
1162 d_handle
.qname
=qname
.makeRelative(domain
); // strip domain name
1163 d_handle
.qtype
=qtype
;
1164 d_handle
.domain
=std::move(domain
);
1168 throw DBException("Zone for '"+d_handle
.domain
.toLogString()+"' in '"+bbd
.d_filename
+"' temporarily not available (file missing, or master dead)"); // fsck
1171 if(!bbd
.current()) {
1172 g_log
<<Logger::Warning
<<"Zone '"<<d_handle
.domain
<<"' ("<<bbd
.d_filename
<<") needs reloading"<<endl
;
1173 queueReloadAndStore(bbd
.d_id
);
1174 if (!safeGetBBDomainInfo(d_handle
.domain
, &bbd
))
1175 throw DBException("Zone '"+bbd
.d_name
.toLogString()+"' ("+bbd
.d_filename
+") gone after reload"); // if we don't throw here, we crash for some reason
1178 d_handle
.d_records
= bbd
.d_records
.get();
1180 if(d_handle
.d_records
->empty())
1181 DLOG(g_log
<<"Query with no results"<<endl
);
1183 d_handle
.mustlog
= mustlog
;
1185 auto& hashedidx
= boost::multi_index::get
<UnorderedNameTag
>(*d_handle
.d_records
);
1186 auto range
= hashedidx
.equal_range(d_handle
.qname
);
1188 if(range
.first
==range
.second
) {
1189 d_handle
.d_list
=false;
1190 d_handle
.d_iter
= d_handle
.d_end_iter
= range
.first
;
1194 d_handle
.d_iter
=range
.first
;
1195 d_handle
.d_end_iter
=range
.second
;
1198 d_handle
.d_list
=false;
1201 Bind2Backend::handle::handle()
1206 bool Bind2Backend::get(DNSResourceRecord
&r
)
1208 if(!d_handle
.d_records
) {
1209 if(d_handle
.mustlog
)
1210 g_log
<<Logger::Warning
<<"There were no answers"<<endl
;
1214 if(!d_handle
.get(r
)) {
1215 if(d_handle
.mustlog
)
1216 g_log
<<Logger::Warning
<<"End of answers"<<endl
;
1222 if(d_handle
.mustlog
)
1223 g_log
<<Logger::Warning
<<"Returning: '"<<r
.qtype
.getName()<<"' of '"<<r
.qname
<<"', content: '"<<r
.content
<<"'"<<endl
;
1227 bool Bind2Backend::handle::get(DNSResourceRecord
&r
)
1232 return get_normal(r
);
1235 void Bind2Backend::handle::reset()
1243 bool Bind2Backend::handle::get_normal(DNSResourceRecord
&r
)
1245 DLOG(g_log
<< "Bind2Backend get() was called for "<<qtype
.getName() << " record for '"<<
1246 qname
<<"' - "<<d_records
->size()<<" available in total!"<<endl
);
1248 if(d_iter
==d_end_iter
) {
1252 while(d_iter
!=d_end_iter
&& !(qtype
.getCode()==QType::ANY
|| (d_iter
)->qtype
==qtype
.getCode())) {
1253 DLOG(g_log
<<Logger::Warning
<<"Skipped "<<qname
<<"/"<<QType(d_iter
->qtype
).getName()<<": '"<<d_iter
->content
<<"'"<<endl
);
1256 if(d_iter
==d_end_iter
) {
1259 DLOG(g_log
<< "Bind2Backend get() returning a rr with a "<<QType(d_iter
->qtype
).getCode()<<endl
);
1261 r
.qname
=qname
.empty() ? domain
: (qname
+domain
);
1263 r
.content
=(d_iter
)->content
;
1264 // r.domain_id=(d_iter)->domain_id;
1265 r
.qtype
=(d_iter
)->qtype
;
1266 r
.ttl
=(d_iter
)->ttl
;
1268 //if(!d_iter->auth && r.qtype.getCode() != QType::A && r.qtype.getCode()!=QType::AAAA && r.qtype.getCode() != QType::NS)
1269 // cerr<<"Warning! Unauth response for qtype "<< r.qtype.getName() << " for '"<<r.qname<<"'"<<endl;
1270 r
.auth
= d_iter
->auth
;
1277 bool Bind2Backend::list(const DNSName
& target
, int id
, bool include_disabled
)
1281 if(!safeGetBBDomainInfo(id
, &bbd
))
1285 DLOG(g_log
<<"Bind2Backend constructing handle for list of "<<id
<<endl
);
1287 d_handle
.d_records
=bbd
.d_records
.get(); // give it a copy, which will stay around
1288 d_handle
.d_qname_iter
= d_handle
.d_records
->begin();
1289 d_handle
.d_qname_end
=d_handle
.d_records
->end(); // iter now points to a vector of pointers to vector<BBResourceRecords>
1292 d_handle
.domain
=bbd
.d_name
;
1293 d_handle
.d_list
=true;
1297 bool Bind2Backend::handle::get_list(DNSResourceRecord
&r
)
1299 if(d_qname_iter
!=d_qname_end
) {
1300 r
.qname
=d_qname_iter
->qname
.empty() ? domain
: (d_qname_iter
->qname
+domain
);
1302 r
.content
=(d_qname_iter
)->content
;
1303 r
.qtype
=(d_qname_iter
)->qtype
;
1304 r
.ttl
=(d_qname_iter
)->ttl
;
1305 r
.auth
= d_qname_iter
->auth
;
1312 bool Bind2Backend::superMasterBackend(const string
&ip
, const DNSName
& domain
, const vector
<DNSResourceRecord
>&nsset
, string
*nameserver
, string
*account
, DNSBackend
**db
)
1314 // Check whether we have a configfile available.
1315 if (getArg("supermaster-config").empty())
1318 ifstream
c_if(getArg("supermasters").c_str(), std::ios::in
); // this was nocreate?
1320 g_log
<< Logger::Error
<< "Unable to open supermasters file for read: " << stringerror() << endl
;
1325 // <ip> <accountname>
1326 string line
, sip
, saccount
;
1327 while (getline(c_if
, line
)) {
1328 std::istringstream
ii(line
);
1337 if (sip
!= ip
) // ip not found in authorization list - reject
1340 // ip authorized as supermaster - accept
1342 if (saccount
.length() > 0)
1343 *account
= saccount
.c_str();
1348 BB2DomainInfo
Bind2Backend::createDomainEntry(const DNSName
& domain
, const string
&filename
)
1351 { // Find a free zone id nr.
1352 ReadLock
rl(&s_state_lock
);
1353 if (!s_state
.empty()) {
1354 newid
= s_state
.rbegin()->d_id
+1;
1359 bbd
.d_kind
= DomainInfo::Native
;
1361 bbd
.d_records
= std::make_shared
<recordstorage_t
>();
1362 bbd
.d_name
= domain
;
1363 bbd
.setCheckInterval(getArgAsNum("check-interval"));
1364 bbd
.d_filename
= filename
;
1369 bool Bind2Backend::createSlaveDomain(const string
&ip
, const DNSName
& domain
, const string
&nameserver
, const string
&account
)
1371 string filename
= getArg("supermaster-destdir")+'/'+domain
.toStringNoDot();
1373 g_log
<< Logger::Warning
<< d_logprefix
1374 << " Writing bind config zone statement for superslave zone '" << domain
1375 << "' from supermaster " << ip
<< endl
;
1378 std::lock_guard
<std::mutex
> l2(s_supermaster_config_lock
);
1380 ofstream
c_of(getArg("supermaster-config").c_str(), std::ios::app
);
1382 g_log
<< Logger::Error
<< "Unable to open supermaster configfile for append: " << stringerror() << endl
;
1383 throw DBException("Unable to open supermaster configfile for append: "+stringerror());
1387 c_of
<< "# Superslave zone '" << domain
.toString() << "' (added: " << nowTime() << ") (account: " << account
<< ')' << endl
;
1388 c_of
<< "zone \"" << domain
.toStringNoDot() << "\" {" << endl
;
1389 c_of
<< "\ttype slave;" << endl
;
1390 c_of
<< "\tfile \"" << filename
<< "\";" << endl
;
1391 c_of
<< "\tmasters { " << ip
<< "; };" << endl
;
1392 c_of
<< "};" << endl
;
1396 BB2DomainInfo bbd
= createDomainEntry(domain
, filename
);
1397 bbd
.d_kind
= DomainInfo::Slave
;
1398 bbd
.d_masters
.push_back(ComboAddress(ip
, 53));
1400 safePutBBDomainInfo(bbd
);
1404 bool Bind2Backend::searchRecords(const string
&pattern
, int maxResults
, vector
<DNSResourceRecord
>& result
)
1406 SimpleMatch
sm(pattern
,true);
1407 static bool mustlog
=::arg().mustDo("query-logging");
1409 g_log
<<Logger::Warning
<<"Search for pattern '"<<pattern
<<"'"<<endl
;
1412 ReadLock
rl(&s_state_lock
);
1414 for(state_t::const_iterator i
= s_state
.begin(); i
!= s_state
.end() ; ++i
) {
1416 if (!safeGetBBDomainInfo(i
->d_id
, &h
)) {
1420 shared_ptr
<const recordstorage_t
> rhandle
= h
.d_records
.get();
1422 for(recordstorage_t::const_iterator ri
= rhandle
->begin(); result
.size() < static_cast<vector
<DNSResourceRecord
>::size_type
>(maxResults
) && ri
!= rhandle
->end(); ri
++) {
1423 DNSName name
= ri
->qname
.empty() ? i
->d_name
: (ri
->qname
+i
->d_name
);
1424 if (sm
.match(name
) || sm
.match(ri
->content
)) {
1425 DNSResourceRecord r
;
1427 r
.domain_id
=i
->d_id
;
1428 r
.content
=ri
->content
;
1432 result
.push_back(std::move(r
));
1441 class Bind2Factory
: public BackendFactory
1444 Bind2Factory() : BackendFactory("bind") {}
1446 void declareArguments(const string
&suffix
="")
1448 declare(suffix
,"ignore-broken-records","Ignore records that are out-of-bound for the zone.","no");
1449 declare(suffix
,"config","Location of named.conf","");
1450 declare(suffix
,"check-interval","Interval for zonefile changes","0");
1451 declare(suffix
,"supermaster-config","Location of (part of) named.conf where pdns can write zone-statements to","");
1452 declare(suffix
,"supermasters","List of IP-addresses of supermasters","");
1453 declare(suffix
,"supermaster-destdir","Destination directory for newly added slave zones",::arg()["config-dir"]);
1454 declare(suffix
,"dnssec-db","Filename to store & access our DNSSEC metadatabase, empty for none", "");
1455 declare(suffix
,"dnssec-db-journal-mode","SQLite3 journal mode", "WAL");
1456 declare(suffix
,"hybrid","Store DNSSEC metadata in other backend","no");
1459 DNSBackend
*make(const string
&suffix
="")
1461 assertEmptySuffix(suffix
);
1462 return new Bind2Backend(suffix
);
1465 DNSBackend
*makeMetadataOnly(const string
&suffix
="")
1467 assertEmptySuffix(suffix
);
1468 return new Bind2Backend(suffix
, false);
1471 void assertEmptySuffix(const string
&suffix
)
1474 throw PDNSException("launch= suffixes are not supported on the bindbackend");
1478 //! Magic class that is activated when the dynamic library is loaded
1484 BackendMakers().report(new Bind2Factory
);
1485 g_log
<< Logger::Info
<< "[bind2backend] This is the bind backend version " << VERSION
1486 #ifndef REPRODUCIBLE
1487 << " (" __DATE__
" " __TIME__
")"
1490 << " (with bind-dnssec-db support)"
1492 << " reporting" << endl
;
1495 static Bind2Loader bind2loader
;