2 PowerDNS Versatile Database Driven Nameserver
3 Copyright (C) 2002 - 2014 PowerDNS.COM BV
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2 as
7 published by the Free Software Foundation;
9 Additionally, the license of this program contains a special
10 exception which allows to distribute the program in binary form when
11 it is linked against OpenSSL.
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 St, Fifth Floor, Boston, MA 02110-1301 USA
29 #include <sys/types.h>
35 #include <boost/algorithm/string.hpp>
36 #include <boost/foreach.hpp>
37 #include "pdns/dnsseckeeper.hh"
38 #include "pdns/dnssecinfra.hh"
39 #include "pdns/base32.hh"
40 #include "pdns/namespaces.hh"
41 #include "pdns/dns.hh"
42 #include "pdns/dnsbackend.hh"
43 #include "bindbackend2.hh"
44 #include "pdns/dnspacket.hh"
45 #include "pdns/zoneparser-tng.hh"
46 #include "pdns/bindparserclasses.hh"
47 #include "pdns/logger.hh"
48 #include "pdns/arguments.hh"
49 #include "pdns/qtype.hh"
50 #include "pdns/misc.hh"
51 #include "pdns/dynlistener.hh"
52 #include "pdns/lock.hh"
53 #include "pdns/namespaces.hh"
56 All instances of this backend share one s_state, which is indexed by zone name and zone id.
57 The s_state is protected by a read/write lock, and the goal it to only interact with it briefly.
58 When a query comes in, we take a read lock and COPY the best zone to answer from s_state (BB2DomainInfo object)
59 All answers are served from this copy.
61 To interact with s_state, use safeGetBBDomainInfo (search on name or id), safePutBBDomainInfo (to update)
62 or safeRemoveBBDomainInfo. These all lock as they should.
64 Several functions need to traverse s_state to get data for the rest of PowerDNS. When doing so,
65 you need to manually take the s_state_lock (read).
67 Parsing zones happens with parseZone(), which fills a BB2DomainInfo object. This can then be stored with safePutBBDomainInfo.
69 Finally, the BB2DomainInfo contains all records as a LookButDontTouch object. This makes sure you only look, but don't touch, since
70 the records might be in use in other places.
73 Bind2Backend::state_t
Bind2Backend::s_state
;
74 int Bind2Backend::s_first
=1;
75 bool Bind2Backend::s_ignore_broken_records
=false;
77 pthread_rwlock_t
Bind2Backend::s_state_lock
=PTHREAD_RWLOCK_INITIALIZER
;
78 pthread_mutex_t
Bind2Backend::s_supermaster_config_lock
=PTHREAD_MUTEX_INITIALIZER
; // protects writes to config file
79 pthread_mutex_t
Bind2Backend::s_startup_lock
=PTHREAD_MUTEX_INITIALIZER
;
80 string
Bind2Backend::s_binddirectory
;
82 BB2DomainInfo::BB2DomainInfo()
90 void BB2DomainInfo::setCheckInterval(time_t seconds
)
92 d_checkinterval
=seconds
;
95 bool BB2DomainInfo::current()
104 if(time(0) - d_lastcheck
< d_checkinterval
)
107 if(d_filename
.empty())
110 return (getCtime()==d_ctime
);
113 time_t BB2DomainInfo::getCtime()
117 if(d_filename
.empty() || stat(d_filename
.c_str(),&buf
)<0)
123 void BB2DomainInfo::setCtime()
126 if(stat(d_filename
.c_str(),&buf
)<0)
128 d_ctime
=buf
.st_ctime
;
131 bool Bind2Backend::safeGetBBDomainInfo(int id
, BB2DomainInfo
* bbd
)
133 ReadLock
rl(&s_state_lock
);
134 state_t::const_iterator iter
= s_state
.find(id
);
135 if(iter
== s_state
.end())
141 bool Bind2Backend::safeGetBBDomainInfo(const std::string
& name
, BB2DomainInfo
* bbd
)
143 ReadLock
rl(&s_state_lock
);
144 typedef state_t::index
<NameTag
>::type nameindex_t
;
145 nameindex_t
& nameindex
= boost::multi_index::get
<NameTag
>(s_state
);
147 nameindex_t::const_iterator iter
= nameindex
.find(name
);
148 if(iter
== nameindex
.end())
154 bool Bind2Backend::safeRemoveBBDomainInfo(const std::string
& name
)
156 WriteLock
rl(&s_state_lock
);
157 typedef state_t::index
<NameTag
>::type nameindex_t
;
158 nameindex_t
& nameindex
= boost::multi_index::get
<NameTag
>(s_state
);
160 nameindex_t::iterator iter
= nameindex
.find(name
);
161 if(iter
== nameindex
.end())
163 nameindex
.erase(iter
);
167 void Bind2Backend::safePutBBDomainInfo(const BB2DomainInfo
& bbd
)
169 WriteLock
rl(&s_state_lock
);
170 replacing_insert(s_state
, bbd
);
173 void Bind2Backend::setNotified(uint32_t id
, uint32_t serial
)
176 safeGetBBDomainInfo(id
, &bbd
);
177 bbd
.d_lastnotified
= serial
;
178 safePutBBDomainInfo(bbd
);
181 void Bind2Backend::setFresh(uint32_t domain_id
)
184 if(safeGetBBDomainInfo(domain_id
, &bbd
)) {
185 bbd
.d_lastcheck
=time(0);
186 safePutBBDomainInfo(bbd
);
190 bool Bind2Backend::startTransaction(const DNSName
&qname
, int id
)
193 d_transaction_tmpname
.clear();
198 throw DBException("domain_id 0 is invalid for this backend.");
203 if(safeGetBBDomainInfo(id
, &bbd
)) {
204 d_transaction_tmpname
=bbd
.d_filename
+"."+itoa(random());
205 d_of
=new ofstream(d_transaction_tmpname
.c_str());
207 throw DBException("Unable to open temporary zonefile '"+d_transaction_tmpname
+"': "+stringerror());
208 unlink(d_transaction_tmpname
.c_str());
213 *d_of
<<"; Written by PowerDNS, don't edit!"<<endl
;
214 *d_of
<<"; Zone '"+bbd
.d_name
+"' retrieved from master "<<endl
<<"; at "<<nowTime()<<endl
; // insert master info here again
221 bool Bind2Backend::commitTransaction()
223 if(d_transaction_id
< 0)
229 if(safeGetBBDomainInfo(d_transaction_id
, &bbd
)) {
230 if(rename(d_transaction_tmpname
.c_str(), bbd
.d_filename
.c_str())<0)
231 throw DBException("Unable to commit (rename to: '" + bbd
.d_filename
+"') AXFRed zone: "+stringerror());
232 queueReloadAndStore(bbd
.d_id
);
240 bool Bind2Backend::abortTransaction()
242 // -1 = dnssec speciality
243 // 0 = invalid transact
244 // >0 = actual transaction
245 if(d_transaction_id
> 0) {
248 unlink(d_transaction_tmpname
.c_str());
255 bool Bind2Backend::feedRecord(const DNSResourceRecord
&r
, string
*ordername
)
257 string qname
=r
.qname
.toString();
260 safeGetBBDomainInfo(d_transaction_id
, &bbd
);
262 string domain
= bbd
.d_name
;
264 if(!stripDomainSuffix(&qname
,domain
))
265 throw DBException("out-of-zone data '"+qname
+"' during AXFR of zone '"+domain
+"'");
267 string content
=r
.content
;
269 // SOA needs stripping too! XXX FIXME - also, this should not be here I think
270 switch(r
.qtype
.getCode()) {
275 if(!stripDomainSuffix(&content
, domain
))
276 content
=stripDot(content
)+".";
277 *d_of
<<qname
<<"\t"<<r
.ttl
<<"\t"<<r
.qtype
.getName()<<"\t"<<content
<<endl
;
280 *d_of
<<qname
<<"\t"<<r
.ttl
<<"\t"<<r
.qtype
.getName()<<"\t"<<r
.content
<<endl
;
286 void Bind2Backend::getUpdatedMasters(vector
<DomainInfo
> *changedDomains
)
288 vector
<DomainInfo
> consider
;
290 ReadLock
rl(&s_state_lock
);
292 for(state_t::const_iterator i
= s_state
.begin(); i
!= s_state
.end() ; ++i
) {
293 if(!i
->d_masters
.empty() && this->alsoNotify
.empty() && i
->d_also_notify
.empty())
299 di
.last_check
=i
->d_lastcheck
;
300 di
.notified_serial
=i
->d_lastnotified
;
302 di
.kind
=DomainInfo::Master
;
303 consider
.push_back(di
);
308 BOOST_FOREACH(DomainInfo
& di
, consider
) {
311 this->getSOA(di
.zone
, soadata
); // we might not *have* a SOA yet, but this might trigger a load of it
316 if(di
.notified_serial
!= soadata
.serial
) {
318 if(safeGetBBDomainInfo(di
.id
, &bbd
)) {
319 bbd
.d_lastnotified
=soadata
.serial
;
320 safePutBBDomainInfo(bbd
);
322 if(di
.notified_serial
) { // don't do notification storm on startup
323 di
.serial
=soadata
.serial
;
324 changedDomains
->push_back(di
);
330 void Bind2Backend::getAllDomains(vector
<DomainInfo
> *domains
, bool include_disabled
)
334 // prevent deadlock by using getSOA() later on
336 ReadLock
rl(&s_state_lock
);
338 for(state_t::const_iterator i
= s_state
.begin(); i
!= s_state
.end() ; ++i
) {
342 di
.last_check
=i
->d_lastcheck
;
343 di
.kind
=i
->d_masters
.empty() ? DomainInfo::Master
: DomainInfo::Slave
; //TODO: what about Native?
345 domains
->push_back(di
);
349 BOOST_FOREACH(DomainInfo
&di
, *domains
) {
350 this->getSOA(di
.zone
, soadata
);
351 di
.serial
=soadata
.serial
;
355 void Bind2Backend::getUnfreshSlaveInfos(vector
<DomainInfo
> *unfreshDomains
)
357 vector
<DomainInfo
> domains
;
359 ReadLock
rl(&s_state_lock
);
360 for(state_t::const_iterator i
= s_state
.begin(); i
!= s_state
.end() ; ++i
) {
361 if(i
->d_masters
.empty())
366 sd
.masters
=i
->d_masters
;
367 sd
.last_check
=i
->d_lastcheck
;
369 sd
.kind
=DomainInfo::Slave
;
370 domains
.push_back(sd
);
374 BOOST_FOREACH(DomainInfo
&sd
, domains
) {
379 getSOA(sd
.zone
,soadata
); // we might not *have* a SOA yet
382 sd
.serial
=soadata
.serial
;
383 if(sd
.last_check
+soadata
.refresh
< (unsigned int)time(0))
384 unfreshDomains
->push_back(sd
);
388 bool Bind2Backend::getDomainInfo(const string
&domain
, DomainInfo
&di
)
391 if(!safeGetBBDomainInfo(domain
, &bbd
))
396 di
.masters
=bbd
.d_masters
;
397 di
.last_check
=bbd
.d_lastcheck
;
399 di
.kind
=bbd
.d_masters
.empty() ? DomainInfo::Master
: DomainInfo::Slave
;
405 getSOA(bbd
.d_name
,sd
); // we might not *have* a SOA yet
413 void Bind2Backend::alsoNotifies(const string
&domain
, set
<string
> *ips
)
415 // combine global list with local list
416 for(set
<string
>::iterator i
= this->alsoNotify
.begin(); i
!= this->alsoNotify
.end(); i
++) {
419 ReadLock
rl(&s_state_lock
);
420 for(state_t::const_iterator i
= s_state
.begin(); i
!= s_state
.end() ; ++i
) {
421 if(pdns_iequals(i
->d_name
,domain
)) {
422 for(set
<string
>::iterator it
= i
->d_also_notify
.begin(); it
!= i
->d_also_notify
.end(); it
++) {
430 // only parses, does NOT add to s_state!
431 void Bind2Backend::parseZoneFile(BB2DomainInfo
*bbd
)
433 NSEC3PARAMRecordContent ns3pr
;
437 nsec3zone
=dk
.getNSEC3PARAM(bbd
->d_name
, &ns3pr
);
439 nsec3zone
=getNSEC3PARAM(bbd
->d_name
, &ns3pr
);
441 bbd
->d_records
= shared_ptr
<recordstorage_t
>(new recordstorage_t());
443 ZoneParserTNG
zpt(bbd
->d_filename
, bbd
->d_name
, s_binddirectory
);
444 DNSResourceRecord rr
;
447 if(rr
.qtype
.getCode() == QType::NSEC
|| rr
.qtype
.getCode() == QType::NSEC3
)
448 continue; // we synthesise NSECs on demand
451 if(rr
.qtype
.getCode() != QType::NSEC3
&& rr
.qtype
.getCode() != QType::RRSIG
)
452 hashed
=toBase32Hex(hashQNameWithSalt(ns3pr
.d_iterations
, ns3pr
.d_salt
, rr
.qname
));
456 insertRecord(*bbd
, rr
.qname
.toString(), rr
.qtype
, rr
.content
, rr
.ttl
, hashed
);
458 fixupAuth(bbd
->d_records
.getWRITABLE());
459 doEmptyNonTerminals(*bbd
, nsec3zone
, ns3pr
);
462 bbd
->d_checknow
=false;
463 bbd
->d_status
="parsed into memory at "+nowTime();
466 /** THIS IS AN INTERNAL FUNCTION! It does moadnsparser prio impedance matching
467 Much of the complication is due to the efforts to benefit from std::string reference counting copy on write semantics */
468 void Bind2Backend::insertRecord(BB2DomainInfo
& bb2
, const string
&qnameu
, const QType
&qtype
, const string
&content
, int ttl
, const std::string
& hashed
, bool *auth
)
471 shared_ptr
<recordstorage_t
> records
= bb2
.d_records
.getWRITABLE();
472 bdr
.qname
=toLowerCanonic(qnameu
);
474 if(bb2
.d_name
.empty())
476 else if(dottedEndsOn(bdr
.qname
, bb2
.d_name
))
477 bdr
.qname
.resize(max(0, static_cast<int>(bdr
.qname
.length() - (bb2
.d_name
.length() + 1))));
479 string msg
= "Trying to insert non-zone data, name='"+bdr
.qname
+"', qtype="+qtype
.getName()+", zone='"+bb2
.d_name
+"'";
480 if(s_ignore_broken_records
) {
481 L
<<Logger::Warning
<<msg
<< " ignored" << endl
;
485 throw PDNSException(msg
);
488 bdr
.qname
.swap(bdr
.qname
);
490 if(!records
->empty() && bdr
.qname
==boost::prior(records
->end())->qname
)
491 bdr
.qname
=boost::prior(records
->end())->qname
;
493 bdr
.qname
=labelReverse(bdr
.qname
);
494 bdr
.qtype
=qtype
.getCode();
496 bdr
.nsec3hash
= hashed
;
498 if (auth
) // Set auth on empty non-terminals
504 records
->insert(bdr
);
507 string
Bind2Backend::DLReloadNowHandler(const vector
<string
>&parts
, Utility::pid_t ppid
)
511 for(vector
<string
>::const_iterator i
=parts
.begin()+1;i
<parts
.end();++i
) {
513 if(safeGetBBDomainInfo(*i
, &bbd
)) {
515 bb2
.queueReloadAndStore(bbd
.d_id
);
516 ret
<< *i
<< ": "<< (bbd
.d_loaded
? "": "[rejected]") <<"\t"<<bbd
.d_status
<<"\n";
519 ret
<< *i
<< " no such domain\n";
521 if(ret
.str().empty())
522 ret
<<"no domains reloaded";
527 string
Bind2Backend::DLDomStatusHandler(const vector
<string
>&parts
, Utility::pid_t ppid
)
531 if(parts
.size() > 1) {
532 for(vector
<string
>::const_iterator i
=parts
.begin()+1;i
<parts
.end();++i
) {
534 if(safeGetBBDomainInfo(*i
, &bbd
)) {
535 ret
<< *i
<< ": "<< (bbd
.d_loaded
? "": "[rejected]") <<"\t"<<bbd
.d_status
<<"\n";
538 ret
<< *i
<< " no such domain\n";
542 ReadLock
rl(&s_state_lock
);
543 for(state_t::const_iterator i
= s_state
.begin(); i
!= s_state
.end() ; ++i
) {
544 ret
<< i
->d_name
<< ": "<< (i
->d_loaded
? "": "[rejected]") <<"\t"<<i
->d_status
<<"\n";
548 if(ret
.str().empty())
549 ret
<<"no domains passed";
554 string
Bind2Backend::DLListRejectsHandler(const vector
<string
>&parts
, Utility::pid_t ppid
)
557 ReadLock
rl(&s_state_lock
);
558 for(state_t::const_iterator i
= s_state
.begin(); i
!= s_state
.end() ; ++i
) {
560 ret
<<i
->d_name
<<"\t"<<i
->d_status
<<endl
;
565 string
Bind2Backend::DLAddDomainHandler(const vector
<string
>&parts
, Utility::pid_t ppid
)
568 return "ERROR: Domain name and zone filename are required";
570 string domainname
= toLowerCanonic(parts
[1]);
571 const string
&filename
= parts
[2];
573 if(safeGetBBDomainInfo(domainname
, &bbd
))
574 return "Already loaded";
575 Bind2Backend bb2
; // createdomainentry needs access to our configuration
576 bbd
=bb2
.createDomainEntry(domainname
, filename
);
577 bbd
.d_filename
=filename
;
581 bbd
.d_status
="parsing into memory";
583 safePutBBDomainInfo(bbd
);
585 L
<<Logger::Warning
<<"Zone "<<domainname
<< " loaded"<<endl
;
586 return "Loaded zone " + domainname
+ " from " + filename
;
589 Bind2Backend::Bind2Backend(const string
&suffix
, bool loadZones
)
591 d_getAllDomainMetadataQuery_stmt
= NULL
;
592 d_getDomainMetadataQuery_stmt
= NULL
;
593 d_deleteDomainMetadataQuery_stmt
= NULL
;
594 d_insertDomainMetadataQuery_stmt
= NULL
;
595 d_getDomainKeysQuery_stmt
= NULL
;
596 d_deleteDomainKeyQuery_stmt
= NULL
;
597 d_insertDomainKeyQuery_stmt
= NULL
;
598 d_activateDomainKeyQuery_stmt
= NULL
;
599 d_deactivateDomainKeyQuery_stmt
= NULL
;
600 d_getTSIGKeyQuery_stmt
= NULL
;
601 d_setTSIGKeyQuery_stmt
= NULL
;
602 d_deleteTSIGKeyQuery_stmt
= NULL
;
603 d_getTSIGKeysQuery_stmt
= NULL
;
605 setArgPrefix("bind"+suffix
);
606 d_logprefix
="[bind"+suffix
+"backend]";
607 d_hybrid
=mustDo("hybrid");
608 s_ignore_broken_records
=mustDo("ignore-broken-records");
610 if (!loadZones
&& d_hybrid
)
613 Lock
l(&s_startup_lock
);
626 extern DynListener
*dl
;
627 dl
->registerFunc("BIND-RELOAD-NOW", &DLReloadNowHandler
, "bindbackend: reload domains", "<domains>");
628 dl
->registerFunc("BIND-DOMAIN-STATUS", &DLDomStatusHandler
, "bindbackend: list status of all domains", "[domains]");
629 dl
->registerFunc("BIND-LIST-REJECTS", &DLListRejectsHandler
, "bindbackend: list rejected domains");
630 dl
->registerFunc("BIND-ADD-ZONE", &DLAddDomainHandler
, "bindbackend: add zone", "<domain> <filename>");
633 Bind2Backend::~Bind2Backend()
634 { freeStatements(); } // deallocate statements
636 void Bind2Backend::rediscover(string
*status
)
641 void Bind2Backend::reload()
643 WriteLock
rwl(&s_state_lock
);
644 for(state_t::iterator i
= s_state
.begin(); i
!= s_state
.end() ; ++i
) {
645 i
->d_checknow
=true; // being a bit cheeky here, don't index state_t on this (mutable)
649 void Bind2Backend::fixupAuth(shared_ptr
<recordstorage_t
> records
)
651 pair
<recordstorage_t::const_iterator
, recordstorage_t::const_iterator
> range
;
654 recordstorage_t nssets
;
655 BOOST_FOREACH(const Bind2DNSRecord
& bdr
, *records
) {
656 if(bdr
.qtype
==QType::NS
)
660 BOOST_FOREACH(const Bind2DNSRecord
& bdr
, *records
) {
663 if(bdr
.qtype
== QType::DS
) // as are delegation signer records
666 sqname
= labelReverse(bdr
.qname
);
669 if(sqname
.empty()) // this is auth of course!
671 if(bdr
.qtype
== QType::NS
|| nssets
.count(sqname
)) { // NS records which are not apex are unauth by definition
674 } while(chopOff(sqname
));
678 void Bind2Backend::doEmptyNonTerminals(BB2DomainInfo
& bbd
, bool nsec3zone
, NSEC3PARAMRecordContent ns3pr
)
680 shared_ptr
<const recordstorage_t
> records
= bbd
.d_records
.get();
681 bool auth
, doent
=true;
683 map
<string
, bool> nonterm
;
684 string shorter
, hashed
;
686 uint32_t maxent
= ::arg().asNum("max-ent-entries");
688 BOOST_FOREACH(const Bind2DNSRecord
& bdr
, *records
)
689 qnames
.insert(labelReverse(bdr
.qname
));
691 BOOST_FOREACH(const Bind2DNSRecord
& bdr
, *records
) {
692 shorter
=labelReverse(bdr
.qname
);
694 if (!bdr
.auth
&& bdr
.qtype
== QType::NS
)
695 auth
=(!nsec3zone
|| !ns3pr
.d_flags
);
699 while(chopOff(shorter
))
701 if(!qnames
.count(shorter
))
705 L
<<Logger::Error
<<"Zone '"<<bbd
.d_name
<<"' has too many empty non terminals."<<endl
;
710 if (!nonterm
.count(shorter
)) {
711 nonterm
.insert(pair
<string
, bool>(shorter
, auth
));
714 nonterm
[shorter
]=true;
721 DNSResourceRecord rr
;
725 pair
<string
, bool> nt
;
726 BOOST_FOREACH(nt
, nonterm
)
728 rr
.qname
=nt
.first
+"."+bbd
.d_name
+".";
730 hashed
=toBase32Hex(hashQNameWithSalt(ns3pr
.d_iterations
, ns3pr
.d_salt
, rr
.qname
));
731 insertRecord(bbd
, rr
.qname
.toString(), rr
.qtype
, rr
.content
, rr
.ttl
, hashed
, &nt
.second
);
735 void Bind2Backend::loadConfig(string
* status
)
737 static int domain_id
=1;
739 if(!getArg("config").empty()) {
742 BP
.parse(getArg("config"));
744 catch(PDNSException
&ae
) {
745 L
<<Logger::Error
<<"Error parsing bind configuration: "<<ae
.reason
<<endl
;
749 vector
<BindDomainInfo
> domains
=BP
.getDomains();
750 this->alsoNotify
= BP
.getAlsoNotify();
752 s_binddirectory
=BP
.getDirectory();
753 // ZP.setDirectory(d_binddirectory);
755 L
<<Logger::Warning
<<d_logprefix
<<" Parsing "<<domains
.size()<<" domain(s), will report when done"<<endl
;
757 set
<string
> oldnames
, newnames
;
759 ReadLock
rl(&s_state_lock
);
760 BOOST_FOREACH(const BB2DomainInfo
& bbd
, s_state
) {
761 oldnames
.insert(bbd
.d_name
);
769 for(vector
<BindDomainInfo
>::iterator i
=domains
.begin(); i
!=domains
.end(); ++i
)
771 if(stat(i
->filename
.c_str(), &st
) == 0) {
772 i
->d_dev
= st
.st_dev
;
773 i
->d_ino
= st
.st_ino
;
777 sort(domains
.begin(), domains
.end()); // put stuff in inode order
778 for(vector
<BindDomainInfo
>::const_iterator i
=domains
.begin();
782 if(i
->type
!="master" && i
->type
!="slave") {
783 L
<<Logger::Warning
<<d_logprefix
<<" Warning! Skipping '"<<i
->type
<<"' zone '"<<i
->name
<<"'"<<endl
;
789 if(!safeGetBBDomainInfo(i
->name
, &bbd
)) {
790 bbd
.d_id
=domain_id
++;
791 bbd
.setCheckInterval(getArgAsNum("check-interval"));
792 bbd
.d_lastnotified
=0;
796 // overwrite what we knew about the domain
797 bbd
.d_name
=toLowerCanonic(i
->name
);
798 bool filenameChanged
= (bbd
.d_filename
!=i
->filename
);
799 bbd
.d_filename
=i
->filename
;
800 bbd
.d_masters
=i
->masters
;
801 bbd
.d_also_notify
=i
->alsoNotify
;
803 newnames
.insert(bbd
.d_name
);
804 if(filenameChanged
|| !bbd
.d_loaded
|| !bbd
.current()) {
805 L
<<Logger::Info
<<d_logprefix
<<" parsing '"<<i
->name
<<"' from file '"<<i
->filename
<<"'"<<endl
;
810 catch(PDNSException
&ae
) {
812 msg
<<" error at "+nowTime()+" parsing '"<<i
->name
<<"' from file '"<<i
->filename
<<"': "<<ae
.reason
;
816 bbd
.d_status
=msg
.str();
818 L
<<Logger::Warning
<<d_logprefix
<<msg
.str()<<endl
;
821 catch(std::exception
&ae
) {
823 msg
<<" error at "+nowTime()+" parsing '"<<i
->name
<<"' from file '"<<i
->filename
<<"': "<<ae
.what();
827 bbd
.d_status
=msg
.str();
828 L
<<Logger::Warning
<<d_logprefix
<<msg
.str()<<endl
;
831 safePutBBDomainInfo(bbd
);
837 set_difference(oldnames
.begin(), oldnames
.end(), newnames
.begin(), newnames
.end(), back_inserter(diff
));
838 unsigned int remdomains
=diff
.size();
840 BOOST_FOREACH(const std::string
& name
, diff
) {
841 safeRemoveBBDomainInfo(name
);
844 // count number of entirely new domains
846 set_difference(newnames
.begin(), newnames
.end(), oldnames
.begin(), oldnames
.end(), back_inserter(diff
));
847 newdomains
=diff
.size();
850 msg
<<" Done parsing domains, "<<rejected
<<" rejected, "<<newdomains
<<" new, "<<remdomains
<<" removed";
854 L
<<Logger::Error
<<d_logprefix
<<msg
.str()<<endl
;
858 void Bind2Backend::queueReloadAndStore(unsigned int id
)
862 if(!safeGetBBDomainInfo(id
, &bbold
))
864 parseZoneFile(&bbold
);
865 bbold
.d_checknow
=false;
866 safePutBBDomainInfo(bbold
);
867 L
<<Logger::Warning
<<"Zone '"<<bbold
.d_name
<<"' ("<<bbold
.d_filename
<<") reloaded"<<endl
;
869 catch(PDNSException
&ae
) {
871 msg
<<" error at "+nowTime()+" parsing '"<<bbold
.d_name
<<"' from file '"<<bbold
.d_filename
<<"': "<<ae
.reason
;
872 bbold
.d_status
=msg
.str();
873 safePutBBDomainInfo(bbold
);
875 catch(std::exception
&ae
) {
877 msg
<<" error at "+nowTime()+" parsing '"<<bbold
.d_name
<<"' from file '"<<bbold
.d_filename
<<"': "<<ae
.what();
878 bbold
.d_status
=msg
.str();
879 safePutBBDomainInfo(bbold
);
883 bool Bind2Backend::findBeforeAndAfterUnhashed(BB2DomainInfo
& bbd
, const DNSName
& qname
, DNSName
& unhashed
, DNSName
& before
, DNSName
& after
)
885 string domain
=qname
.toString();
886 shared_ptr
<const recordstorage_t
> records
= bbd
.d_records
.get();
887 recordstorage_t::const_iterator iter
= records
->upper_bound(domain
);
890 //cout<<"starting before for: '"<<domain<<"'"<<endl;
891 iter
= records
->upper_bound(domain
);
893 while(iter
== records
->end() || (iter
->qname
) > domain
|| (!(iter
->auth
) && (!(iter
->qtype
== QType::NS
))) || (!(iter
->qtype
)))
902 //cerr<<"Now after"<<endl;
903 iter
= records
->upper_bound(domain
);
905 if(iter
== records
->end()) {
906 //cerr<<"\tFound the end, begin storage: '"<<bbd.d_records->begin()->qname<<"', '"<<bbd.d_name<<"'"<<endl;
907 after
.clear(); // this does the right thing (i.e. point to apex, which is sure to have auth records)
909 //cerr<<"\tFound: '"<<(iter->qname)<<"' (nsec3hash='"<<(iter->nsec3hash)<<"')"<<endl;
910 // this iteration is theoretically unnecessary - glue always sorts right behind a delegation
911 // so we will never get here. But let's do it anyway.
912 while((!(iter
->auth
) && (!(iter
->qtype
== QType::NS
))) || (!(iter
->qtype
)))
915 if(iter
== records
->end())
921 after
= (iter
)->qname
;
924 //cerr<<"Before: '"<<before<<"', after: '"<<after<<"'\n";
928 bool Bind2Backend::getBeforeAndAfterNamesAbsolute(uint32_t id
, const std::string
& qname
, std::string
& unhashed
, std::string
& before
, std::string
& after
)
931 safeGetBBDomainInfo(id
, &bbd
);
933 NSEC3PARAMRecordContent ns3pr
;
934 string auth
=bbd
.d_name
;
939 nsec3zone
=dk
.getNSEC3PARAM(auth
, &ns3pr
);
941 nsec3zone
=getNSEC3PARAM(auth
, &ns3pr
);
944 //cerr<<"in bind2backend::getBeforeAndAfterAbsolute: no nsec3 for "<<auth<<endl;
945 return findBeforeAndAfterUnhashed(bbd
, qname
, unhashed
, before
, after
);
949 string lqname
= toLower(qname
);
950 // cerr<<"\nin bind2backend::getBeforeAndAfterAbsolute: nsec3 HASH for "<<auth<<", asked for: "<<lqname<< " (auth: "<<auth<<".)"<<endl;
951 typedef recordstorage_t::index
<HashedTag
>::type records_by_hashindex_t
;
952 records_by_hashindex_t
& hashindex
=boost::multi_index::get
<HashedTag
>(*bbd
.d_records
.getWRITABLE()); // needlessly dangerous
954 // BOOST_FOREACH(const Bind2DNSRecord& bdr, hashindex) {
955 // cerr<<"Hash: "<<bdr.nsec3hash<<"\t"<< (lqname < bdr.nsec3hash) <<endl;
958 records_by_hashindex_t::const_iterator iter
;
961 if (before
.empty()) {
962 iter
= hashindex
.upper_bound(lqname
);
964 if(iter
!= hashindex
.begin() && (iter
== hashindex
.end() || iter
->nsec3hash
> lqname
))
969 if(iter
== hashindex
.begin() && (iter
->nsec3hash
> lqname
))
971 iter
= hashindex
.end();
975 while(iter
== hashindex
.end() || (!iter
->auth
&& !(iter
->qtype
== QType::NS
&& !pdns_iequals(iter
->qname
, auth
) && !ns3pr
.d_flags
)) || iter
->nsec3hash
.empty())
978 if(iter
== hashindex
.begin()) {
980 iter
= hashindex
.end();
991 before
= iter
->nsec3hash
;
992 unhashed
= dotConcat(labelReverse(iter
->qname
), auth
);
993 // cerr<<"before: "<<(iter->nsec3hash)<<"/"<<(iter->qname)<<endl;
999 iter
= hashindex
.upper_bound(lqname
);
1000 if(iter
== hashindex
.end())
1002 iter
= hashindex
.begin();
1006 while((!iter
->auth
&& !(iter
->qtype
== QType::NS
&& !pdns_iequals(iter
->qname
, auth
) && !ns3pr
.d_flags
)) || iter
->nsec3hash
.empty())
1009 if(iter
== hashindex
.end()) {
1011 iter
= hashindex
.begin();
1022 after
= iter
->nsec3hash
;
1023 // cerr<<"after: "<<(iter->nsec3hash)<<"/"<<(iter->qname)<<endl;
1024 //cerr<<"Before: '"<<before<<"', after: '"<<after<<"'\n";
1029 void Bind2Backend::lookup(const QType
&qtype
, const DNSName
&qname
, DNSPacket
*pkt_p
, int zoneId
)
1032 string domain
=qname
.toString();
1034 static bool mustlog
=::arg().mustDo("query-logging");
1036 L
<<Logger::Warning
<<"Lookup for '"<<qtype
.getName()<<"' of '"<<domain
<<"'"<<endl
;
1040 found
= safeGetBBDomainInfo(domain
, &bbd
);
1041 } while ((!found
|| (zoneId
!= (int)bbd
.d_id
&& zoneId
!= -1)) && chopOff(domain
));
1045 L
<<Logger::Warning
<<"Found no authoritative zone for "<<qname
<<endl
;
1046 d_handle
.d_list
=false;
1051 L
<<Logger::Warning
<<"Found a zone '"<<domain
<<"' (with id " << bbd
.d_id
<<") that might contain data "<<endl
;
1053 d_handle
.id
=bbd
.d_id
;
1055 DLOG(L
<<"Bind2Backend constructing handle for search for "<<qtype
.getName()<<" for "<<
1059 d_handle
.qname
=qname
;
1060 else if(strcasecmp(qname
.c_str(),domain
.c_str()))
1061 d_handle
.qname
=qname
.substr(0,qname
.size()-domain
.length()-1); // strip domain name
1063 d_handle
.qtype
=qtype
;
1064 d_handle
.domain
=qname
.substr(qname
.size()-domain
.length());
1068 throw DBException("Zone for '"+bbd
.d_name
+"' in '"+bbd
.d_filename
+"' temporarily not available (file missing, or master dead)"); // fsck
1071 if(!bbd
.current()) {
1072 L
<<Logger::Warning
<<"Zone '"<<bbd
.d_name
<<"' ("<<bbd
.d_filename
<<") needs reloading"<<endl
;
1073 queueReloadAndStore(bbd
.d_id
);
1074 if (!safeGetBBDomainInfo(domain
, &bbd
))
1075 throw DBException("Zone '"+bbd
.d_name
+"' ("+bbd
.d_filename
+") gone after reload"); // if we don't throw here, we crash for some reason
1078 d_handle
.d_records
= bbd
.d_records
.get();
1080 if(d_handle
.d_records
->empty())
1081 DLOG(L
<<"Query with no results"<<endl
);
1083 pair
<recordstorage_t::const_iterator
, recordstorage_t::const_iterator
> range
;
1085 string lname
=labelReverse(toLower(d_handle
.qname
));
1086 //cout<<"starting equal range for: '"<<d_handle.qname<<"', search is for: '"<<lname<<"'"<<endl;
1088 range
= d_handle
.d_records
->equal_range(lname
);
1089 //cout<<"End equal range"<<endl;
1090 d_handle
.mustlog
= mustlog
;
1092 if(range
.first
==range
.second
) {
1093 // cerr<<"Found nothing!"<<endl;
1094 d_handle
.d_list
=false;
1095 d_handle
.d_iter
= d_handle
.d_end_iter
= range
.first
;
1099 // cerr<<"Found something!"<<endl;
1100 d_handle
.d_iter
=range
.first
;
1101 d_handle
.d_end_iter
=range
.second
;
1104 d_handle
.d_list
=false;
1107 Bind2Backend::handle::handle()
1112 bool Bind2Backend::get(DNSResourceRecord
&r
)
1114 if(!d_handle
.d_records
) {
1115 if(d_handle
.mustlog
)
1116 L
<<Logger::Warning
<<"There were no answers"<<endl
;
1120 if(!d_handle
.get(r
)) {
1121 if(d_handle
.mustlog
)
1122 L
<<Logger::Warning
<<"End of answers"<<endl
;
1128 if(d_handle
.mustlog
)
1129 L
<<Logger::Warning
<<"Returning: '"<<r
.qtype
.getName()<<"' of '"<<r
.qname
.toString()<<"', content: '"<<r
.content
<<"'"<<endl
;
1133 bool Bind2Backend::handle::get(DNSResourceRecord
&r
)
1138 return get_normal(r
);
1141 void Bind2Backend::handle::reset()
1149 bool Bind2Backend::handle::get_normal(DNSResourceRecord
&r
)
1151 DLOG(L
<< "Bind2Backend get() was called for "<<qtype
.getName() << " record for '"<<
1152 qname
<<"' - "<<d_records
->size()<<" available in total!"<<endl
);
1154 if(d_iter
==d_end_iter
) {
1158 while(d_iter
!=d_end_iter
&& !(qtype
.getCode()==QType::ANY
|| (d_iter
)->qtype
==qtype
.getCode())) {
1159 DLOG(L
<<Logger::Warning
<<"Skipped "<<qname
<<"/"<<QType(d_iter
->qtype
).getName()<<": '"<<d_iter
->content
<<"'"<<endl
);
1162 if(d_iter
==d_end_iter
) {
1165 DLOG(L
<< "Bind2Backend get() returning a rr with a "<<QType(d_iter
->qtype
).getCode()<<endl
);
1167 r
.qname
=qname
.empty() ? domain
: (qname
+"."+domain
);
1169 r
.content
=(d_iter
)->content
;
1170 // r.domain_id=(d_iter)->domain_id;
1171 r
.qtype
=(d_iter
)->qtype
;
1172 r
.ttl
=(d_iter
)->ttl
;
1174 //if(!d_iter->auth && r.qtype.getCode() != QType::A && r.qtype.getCode()!=QType::AAAA && r.qtype.getCode() != QType::NS)
1175 // cerr<<"Warning! Unauth response for qtype "<< r.qtype.getName() << " for '"<<r.qname<<"'"<<endl;
1176 r
.auth
= d_iter
->auth
;
1183 bool Bind2Backend::list(const string
&target
, int id
, bool include_disabled
)
1187 if(!safeGetBBDomainInfo(id
, &bbd
))
1191 DLOG(L
<<"Bind2Backend constructing handle for list of "<<id
<<endl
);
1193 d_handle
.d_records
=bbd
.d_records
.get(); // give it a copy, which will stay around
1194 d_handle
.d_qname_iter
= d_handle
.d_records
->begin();
1195 d_handle
.d_qname_end
=d_handle
.d_records
->end(); // iter now points to a vector of pointers to vector<BBResourceRecords>
1198 d_handle
.d_list
=true;
1202 bool Bind2Backend::handle::get_list(DNSResourceRecord
&r
)
1204 if(d_qname_iter
!=d_qname_end
) {
1205 r
.qname
=d_qname_iter
->qname
.empty() ? domain
: (labelReverse(d_qname_iter
->qname
)+"."+domain
);
1207 r
.content
=(d_qname_iter
)->content
;
1208 r
.qtype
=(d_qname_iter
)->qtype
;
1209 r
.ttl
=(d_qname_iter
)->ttl
;
1210 r
.auth
= d_qname_iter
->auth
;
1217 bool Bind2Backend::isMaster(const string
&name
, const string
&ip
)
1220 if(!safeGetBBDomainInfo(name
, &bbd
))
1223 for(vector
<string
>::const_iterator iter
= bbd
.d_masters
.begin(); iter
!= bbd
.d_masters
.end(); ++iter
)
1230 bool Bind2Backend::superMasterBackend(const string
&ip
, const string
&domain
, const vector
<DNSResourceRecord
>&nsset
, string
*nameserver
, string
*account
, DNSBackend
**db
)
1232 // Check whether we have a configfile available.
1233 if (getArg("supermaster-config").empty())
1236 ifstream
c_if(getArg("supermasters").c_str(), std::ios::in
); // this was nocreate?
1238 L
<< Logger::Error
<< "Unable to open supermasters file for read: " << stringerror() << endl
;
1243 // <ip> <accountname>
1244 string line
, sip
, saccount
;
1245 while (getline(c_if
, line
)) {
1246 std::istringstream
ii(line
);
1255 if (sip
!= ip
) // ip not found in authorization list - reject
1258 // ip authorized as supermaster - accept
1260 if (saccount
.length() > 0)
1261 *account
= saccount
.c_str();
1266 BB2DomainInfo
Bind2Backend::createDomainEntry(const string
&domain
, const string
&filename
)
1269 { // Find a free zone id nr.
1270 ReadLock
rl(&s_state_lock
);
1271 if (!s_state
.empty()) {
1272 newid
= s_state
.rbegin()->d_id
+1;
1278 bbd
.d_records
= shared_ptr
<recordstorage_t
>(new recordstorage_t
);
1279 bbd
.d_name
= domain
;
1280 bbd
.setCheckInterval(getArgAsNum("check-interval"));
1281 bbd
.d_filename
= filename
;
1285 bool Bind2Backend::createSlaveDomain(const string
&ip
, const string
&domain
, const string
&nameserver
, const string
&account
)
1287 string filename
= getArg("supermaster-destdir")+'/'+domain
;
1289 L
<< Logger::Warning
<< d_logprefix
1290 << " Writing bind config zone statement for superslave zone '" << domain
1291 << "' from supermaster " << ip
<< endl
;
1294 Lock
l2(&s_supermaster_config_lock
);
1296 ofstream
c_of(getArg("supermaster-config").c_str(), std::ios::app
);
1298 L
<< Logger::Error
<< "Unable to open supermaster configfile for append: " << stringerror() << endl
;
1299 throw DBException("Unable to open supermaster configfile for append: "+stringerror());
1303 c_of
<< "# Superslave zone " << domain
<< " (added: " << nowTime() << ") (account: " << account
<< ')' << endl
;
1304 c_of
<< "zone \"" << domain
<< "\" {" << endl
;
1305 c_of
<< "\ttype slave;" << endl
;
1306 c_of
<< "\tfile \"" << filename
<< "\";" << endl
;
1307 c_of
<< "\tmasters { " << ip
<< "; };" << endl
;
1308 c_of
<< "};" << endl
;
1312 BB2DomainInfo bbd
= createDomainEntry(toLowerCanonic(domain
), filename
);
1313 bbd
.d_masters
.push_back(ip
);
1314 safePutBBDomainInfo(bbd
);
1318 class Bind2Factory
: public BackendFactory
1321 Bind2Factory() : BackendFactory("bind") {}
1323 void declareArguments(const string
&suffix
="")
1325 declare(suffix
,"ignore-broken-records","Ignore records that are out-of-bound for the zone.","no");
1326 declare(suffix
,"config","Location of named.conf","");
1327 declare(suffix
,"check-interval","Interval for zonefile changes","0");
1328 declare(suffix
,"supermaster-config","Location of (part of) named.conf where pdns can write zone-statements to","");
1329 declare(suffix
,"supermasters","List of IP-addresses of supermasters","");
1330 declare(suffix
,"supermaster-destdir","Destination directory for newly added slave zones",::arg()["config-dir"]);
1331 declare(suffix
,"dnssec-db","Filename to store & access our DNSSEC metadatabase, empty for none", "");
1332 declare(suffix
,"hybrid","Store DNSSEC metadata in other backend","no");
1335 DNSBackend
*make(const string
&suffix
="")
1337 return new Bind2Backend(suffix
);
1340 DNSBackend
*makeMetadataOnly(const string
&suffix
="")
1342 return new Bind2Backend(suffix
, false);
1346 //! Magic class that is activated when the dynamic library is loaded
1352 BackendMakers().report(new Bind2Factory
);
1353 L
<< Logger::Info
<< "[bind2backend] This is the bind backend version " << VERSION
1354 #ifndef REPRODUCIBLE
1355 << " (" __DATE__
" " __TIME__
")"
1357 << " reporting" << endl
;
1360 static Bind2Loader bind2loader
;