2
PowerDNS Versatile Database Driven Nameserver
3
Copyright (C) 2002 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
This program is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
GNU General Public License for more details.
14
You should have received a copy of the GNU General Public License
15
along with this program; if not, write to the Free Software
16
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
#include <sys/types.h>
29
#include "dnsbackend.hh"
30
#include "bindbackend.hh"
31
#include "dnspacket.hh"
33
#include "zoneparser.hh"
34
#include "bindparser.hh"
36
#include "arguments.hh"
40
#include "dynlistener.hh"
45
cmap_t BindBackend::d_qnames;
46
map<int,vector<vector<BBResourceRecord>* > > BindBackend::d_zone_id_map;
47
set<string> BindBackend::s_contents;
48
HuffmanCodec BindBackend::s_hc;
50
map<unsigned int,BBDomainInfo> BindBackend::d_bbds;
52
int BindBackend::s_first=1;
53
pthread_mutex_t BindBackend::s_startup_lock=PTHREAD_MUTEX_INITIALIZER;
55
BBDomainInfo::BBDomainInfo()
60
d_rwlock=new pthread_rwlock_t;
61
d_status="Seen in bind configuration";
63
//cout<<"Generated a new bbdomaininfo: "<<(void*)d_rwlock<<"/"<<getpid()<<endl;
64
pthread_rwlock_init(d_rwlock,0);
67
void BBDomainInfo::setCheckInterval(time_t seconds)
69
d_checkinterval=seconds;
72
bool BBDomainInfo::current()
77
if(!d_checknow && !d_checkinterval || (time(0)-d_lastcheck<d_checkinterval) || d_filename.empty())
80
return (getCtime()==d_ctime);
83
time_t BBDomainInfo::getCtime()
87
if(d_filename.empty() || stat(d_filename.c_str(),&buf)<0)
93
void BBDomainInfo::setCtime()
96
if(stat(d_filename.c_str(),&buf)<0)
101
void BindBackend::setNotified(uint32_t id, uint32_t serial)
103
d_bbds[id].d_lastnotified=serial;
106
void BindBackend::setFresh(uint32_t domain_id)
108
d_bbds[domain_id].d_last_check=time(0);
111
bool BindBackend::startTransaction(const string &qname, int id)
113
BBDomainInfo &bbd=d_bbds[d_transaction_id=id];
114
d_transaction_tmpname=bbd.d_filename+"."+itoa(random());
115
d_of=new ofstream(d_transaction_tmpname.c_str());
117
throw DBException("Unable to open temporary zonefile '"+d_transaction_tmpname+"': "+stringerror());
118
unlink(d_transaction_tmpname.c_str());
123
*d_of<<"; Written by PowerDNS, don't edit!"<<endl;
124
*d_of<<"; Zone '"+bbd.d_name+"' retrieved from master "<<bbd.d_master<<endl<<"; at "<<nowTime()<<endl;
129
bool BindBackend::commitTransaction()
133
if(rename(d_transaction_tmpname.c_str(),d_bbds[d_transaction_id].d_filename.c_str())<0)
134
throw DBException("Unable to commit (rename to: '"+d_bbds[d_transaction_id].d_filename+"') AXFRed zone: "+stringerror());
136
queueReload(&d_bbds[d_transaction_id]);
137
d_bbds[d_transaction_id].unlock();
142
bool BindBackend::abortTransaction()
144
if(d_transaction_id) {
145
d_bbds[d_transaction_id].unlock();
148
unlink(d_transaction_tmpname.c_str());
156
bool BindBackend::feedRecord(const DNSResourceRecord &r)
158
string qname=r.qname;
159
string domain=d_bbds[d_transaction_id].d_name;
161
if(!stripDomainSuffix(&qname,domain))
162
throw DBException("out-of-zone data '"+qname+"' during AXFR of zone '"+domain+"'");
164
string content=r.content;
166
// SOA needs stripping too! XXX FIXME
167
switch(r.qtype.getCode()) {
169
*d_of<<qname<<"\t"<<r.ttl<<"\t"<<r.qtype.getName()<<"\t\""<<r.content<<"\""<<endl;
172
if(!stripDomainSuffix(&content,domain))
174
*d_of<<qname<<"\t"<<r.ttl<<"\t"<<r.qtype.getName()<<"\t"<<r.priority<<"\t"<<content<<endl;
178
if(!stripDomainSuffix(&content,domain))
180
*d_of<<qname<<"\t"<<r.ttl<<"\t"<<r.qtype.getName()<<"\t"<<content<<endl;
183
*d_of<<qname<<"\t"<<r.ttl<<"\t"<<r.qtype.getName()<<"\t"<<r.content<<endl;
190
void BindBackend::getUpdatedMasters(vector<DomainInfo> *changedDomains)
193
for(map<uint32_t,BBDomainInfo>::iterator i=d_bbds.begin();i!=d_bbds.end();++i) {
194
if(!i->second.d_master.empty())
198
getSOA(i->second.d_name,soadata); // we might not *have* a SOA yet
203
di.serial=soadata.serial;
204
di.zone=i->second.d_name;
205
di.last_check=i->second.d_last_check;
207
di.kind=DomainInfo::Master;
208
if(!i->second.d_lastnotified) // don't do notification storm on startup
209
i->second.d_lastnotified=soadata.serial;
211
if(soadata.serial!=i->second.d_lastnotified)
212
changedDomains->push_back(di);
217
void BindBackend::getUnfreshSlaveInfos(vector<DomainInfo> *unfreshDomains)
219
for(map<uint32_t,BBDomainInfo>::const_iterator i=d_bbds.begin();i!=d_bbds.end();++i) {
220
if(i->second.d_master.empty())
224
sd.zone=i->second.d_name;
225
sd.master=i->second.d_master;
226
sd.last_check=i->second.d_last_check;
228
sd.kind=DomainInfo::Slave;
233
soadata.db=(DNSBackend *)-1; // not sure if this is useful, inhibits any caches that might be around
235
getSOA(i->second.d_name,soadata); // we might not *have* a SOA yet
238
sd.serial=soadata.serial;
239
if(sd.last_check+soadata.refresh<(unsigned int)time(0))
240
unfreshDomains->push_back(sd);
244
bool BindBackend::getDomainInfo(const string &domain, DomainInfo &di)
246
for(map<uint32_t,BBDomainInfo>::const_iterator i=d_bbds.begin();i!=d_bbds.end();++i) {
247
if(i->second.d_name==domain) {
250
di.master=i->second.d_master;
251
di.last_check=i->second.d_last_check;
253
di.kind=i->second.d_master.empty() ? DomainInfo::Master : DomainInfo::Slave;
259
getSOA(i->second.d_name,sd); // we might not *have* a SOA yet
271
static string canonic(string ret)
278
*i=*i; //tolower(*i);
282
ret.resize(i-ret.begin()-1);
286
/** This function adds a record to a domain with a certain id. */
287
void BindBackend::insert(int id, const string &qnameu, const string &qtype, const string &content, int ttl=300, int prio=25)
290
static unsigned int len;
291
static unsigned int ulen;
293
if(!((s_count++)%10000))
294
cerr<<"\r"<<s_count-1<<", "<<s_contents.size()<<" different contents, "<<d_qnames.size()<<" different qnames, "<<len/1000000<<"MB, saved: "<<
298
s_hc.encode(toLower(canonic(qnameu)),compressed);
299
// string(compressed).swap(compressed);
300
// cout<<"saved: "<<qnameu.size()-compressed.size()<<endl;
302
vector<BBResourceRecord>::const_iterator i;
304
if(d_qnames[compressed].empty()) { // NEW! NEW! NEW! in de top 40!
305
d_zone_id_map[id].push_back(&d_qnames[compressed]);
306
i=d_qnames[compressed].end();
309
for(i=d_qnames[compressed].begin();i!=d_qnames[compressed].end();++i)
310
if(((i)->qtype==QType::chartocode(qtype.c_str())))
311
if((*(i)->content==canonic(content)))
314
// never saw this specific name/type/content triple before
315
if(i==d_qnames[compressed].end()) {
316
BBResourceRecord v=resourceMaker(id,qtype,canonic(content),ttl,prio);
317
v.qnameptr=&d_qnames.find(compressed)->first;
318
len+=compressed.size();
320
d_qnames[compressed].push_back(v);
322
d_qnames[compressed].reserve(0);
323
// vector<BBResourceRecord>&tmp=d_qnames[compressed];
324
// vector<BBResourceRecord>(tmp).swap(tmp);
332
/** Helper function that creates a BBResourceRecord and does s_content housekeeping */
333
BBResourceRecord BindBackend::resourceMaker(int id, const string &qtype, const string &content, int ttl, int prio)
335
BBResourceRecord make;
339
make.qtype=QType::chartocode(qtype.c_str());
341
throw AhuException("Unknown qtype '"+qtype+"'"); // never leaves the BindBackend
343
set<string>::const_iterator i=s_contents.find(content);
344
if(i==s_contents.end()) {
345
s_contents.insert(content);
346
i=s_contents.find(content);
354
static BindBackend *us;
356
void BindBackend::reload()
358
for(map<uint32_t,BBDomainInfo>::iterator i=us->d_bbds.begin();i!=us->d_bbds.end();++i)
359
i->second.d_checknow=true;
362
string BindBackend::DLReloadNowHandler(const vector<string>&parts, Utility::pid_t ppid)
366
for(map<uint32_t,BBDomainInfo>::iterator j=us->d_bbds.begin();j!=us->d_bbds.end();++j) {
371
for(vector<string>::const_iterator i=parts.begin()+1;i<parts.end();++i) // O(N) badness XXX FIXME
372
if(*i==j->second.d_name) {
380
us->queueReload(&j->second);
382
ret<<j->second.d_name<< (j->second.d_loaded ? "": "[rejected]") <<"\t"<<j->second.d_status<<"\n";
391
string BindBackend::DLDomStatusHandler(const vector<string>&parts, Utility::pid_t ppid)
395
for(map<uint32_t,BBDomainInfo>::iterator j=us->d_bbds.begin();j!=us->d_bbds.end();++j) {
401
for(vector<string>::const_iterator i=parts.begin()+1;i<parts.end();++i) // O(N) badness XXX FIXME
402
if(*i==j->second.d_name) {
408
line<<j->second.d_name<< (j->second.d_loaded ? "": "[rejected]") <<"\t"<<j->second.d_status<<"\n";
417
string BindBackend::DLListRejectsHandler(const vector<string>&parts, Utility::pid_t ppid)
420
for(map<uint32_t,BBDomainInfo>::iterator j=us->d_bbds.begin();j!=us->d_bbds.end();++j)
421
if(!j->second.d_loaded)
422
ret<<j->second.d_name<<"\t"<<j->second.d_status<<endl;
427
static void callback(unsigned int domain_id, const string &domain, const string &qtype, const string &content, int ttl, int prio)
429
us->insert(domain_id,domain,qtype,content,ttl,prio);
432
BindBackend::BindBackend(const string &suffix)
434
d_logprefix="[bind1"+suffix+"backend]";
435
setArgPrefix("bind1"+suffix);
436
Lock l(&s_startup_lock);
444
if(!mustDo("enable-huffman"))
445
s_hc.passthrough(true);
447
if(mustDo("example-zones")) {
448
insert(0,"www.example.com","A","1.2.3.4");
449
insert(0,"example.com","SOA","ns1.example.com hostmaster.example.com");
450
insert(0,"example.com","NS","ns1.example.com",86400);
451
insert(0,"example.com","NS","ns2.example.com",86400);
452
insert(0,"example.com","MX","mail.example.com",3600,25);
453
insert(0,"example.com","MX","mail1.example.com",3600,25);
454
insert(0,"mail.example.com","A","4.3.2.1");
455
insert(0,"mail1.example.com","A","5.4.3.2");
456
insert(0,"ns1.example.com","A","4.3.2.1");
457
insert(0,"ns2.example.com","A","5.4.3.2");
459
for(int i=0;i<1000;i++)
460
insert(0,"host-"+itoa(i)+".example.com","A","2.3.4.5");
463
bbd.d_name="example.com";
467
d_bbds[0].d_loaded=true;
468
d_bbds[0].d_status="parsed into memory at "+nowTime();
474
extern DynListener *dl;
476
dl->registerFunc("BIND1-RELOAD-NOW", &DLReloadNowHandler);
477
dl->registerFunc("BIND1-DOMAIN-STATUS", &DLDomStatusHandler);
478
dl->registerFunc("BIND1-LIST-REJECTS", &DLListRejectsHandler);
482
void BindBackend::rediscover(string *status)
487
void BindBackend::loadConfig(string* status)
489
static int domain_id=1;
491
if(!getArg("config").empty()) {
494
BP.parse(getArg("config"));
496
catch(AhuException &ae) {
497
L<<Logger::Error<<"Error parsing bind configuration: "<<ae.reason<<endl;
503
vector<BindDomainInfo> domains=BP.getDomains();
507
ZP.setDirectory(BP.getDirectory());
508
ZP.setCallback(&callback);
509
L<<Logger::Warning<<d_logprefix<<" Parsing "<<domains.size()<<" domain(s), will report when done"<<endl;
514
map<unsigned int, BBDomainInfo> nbbds;
516
for(vector<BindDomainInfo>::const_iterator i=domains.begin();
521
if(i->type!="master" && i->type!="slave") {
522
L<<Logger::Warning<<d_logprefix<<" Warning! Skipping '"<<i->type<<"' zone '"<<i->name<<"'"<<endl;
525
map<unsigned int, BBDomainInfo>::const_iterator j=d_bbds.begin();
526
for(;j!=d_bbds.end();++j)
527
if(j->second.d_name==i->name) {
531
if(j==d_bbds.end()) { // entirely new
532
bbd.d_id=domain_id++;
534
bbd.setCheckInterval(getArgAsNum("check-interval"));
535
bbd.d_lastnotified=0;
540
bbd.d_filename=i->filename;
541
bbd.d_master=i->master;
545
L<<Logger::Info<<d_logprefix<<" parsing '"<<i->name<<"' from file '"<<i->filename<<"'"<<endl;
548
ZP.parse(i->filename,i->name,bbd.d_id); // calls callback for us
549
nbbds[bbd.d_id].setCtime();
550
nbbds[bbd.d_id].d_loaded=true; // does this perform locking for us?
551
nbbds[bbd.d_id].d_status="parsed into memory at "+nowTime();
554
catch(AhuException &ae) {
556
msg<<" error at "+nowTime()+" parsing '"<<i->name<<"' from file '"<<i->filename<<"': "<<ae.reason;
559
nbbds[bbd.d_id].d_status=msg.str();
560
L<<Logger::Warning<<d_logprefix<<msg.str()<<endl;
565
vector<vector<BBResourceRecord> *>&tmp=d_zone_id_map[bbd.d_id]; // shrink trick
566
vector<vector<BBResourceRecord> *>(tmp).swap(tmp);
571
set<string> oldnames, newnames;
572
for(map<unsigned int, BBDomainInfo>::const_iterator j=d_bbds.begin();j!=d_bbds.end();++j) {
573
oldnames.insert(j->second.d_name);
575
for(map<unsigned int, BBDomainInfo>::const_iterator j=nbbds.begin();j!=nbbds.end();++j) {
576
newnames.insert(j->second.d_name);
580
set_difference(oldnames.begin(), oldnames.end(), newnames.begin(), newnames.end(), back_inserter(diff));
581
remdomains=diff.size();
582
for(vector<string>::const_iterator k=diff.begin();k!=diff.end();++k)
583
L<<Logger::Error<<"Removed: "<<*k<<endl;
585
for(map<unsigned int, BBDomainInfo>::iterator j=d_bbds.begin();j!=d_bbds.end();++j) { // O(N*M)
586
for(vector<string>::const_iterator k=diff.begin();k!=diff.end();++k)
587
if(j->second.d_name==*k) {
588
L<<Logger::Error<<"Removing records from zone '"<<j->second.d_name<<"' from memory"<<endl;
590
j->second.d_loaded=false;
591
nukeZoneRecords(&j->second);
597
vector<string> diff2;
598
set_difference(newnames.begin(), newnames.end(), oldnames.begin(), oldnames.end(), back_inserter(diff2));
599
newdomains=diff2.size();
601
d_bbds.swap(nbbds); // commit
603
msg<<" Done parsing domains, "<<rejected<<" rejected, "<<newdomains<<" new, "<<remdomains<<" removed";
607
L<<Logger::Error<<d_logprefix<<msg.str()<<endl;
608
L<<Logger::Info<<d_logprefix<<" Number of hash buckets: "<<d_qnames.bucket_count()<<", number of entries: "<<d_qnames.size()<< endl;
612
/** nuke all records from memory, keep bbd intact though. Must be called with bbd lock held already! */
613
void BindBackend::nukeZoneRecords(BBDomainInfo *bbd)
615
bbd->d_loaded=0; // block further access
617
// this emtpies all d_qnames vectors belonging to this domain. We find these vectors via d_zone_id_map
618
for(vector<vector<BBResourceRecord> *>::iterator i=d_zone_id_map[bbd->d_id].begin();
619
i!=d_zone_id_map[bbd->d_id].end();++i) {
623
// empty our d_zone_id_map of the references to the now empty vectors (which are not gone from d_qnames, btw)
624
d_zone_id_map[bbd->d_id].clear();
627
/** Must be called with bbd locked already. Will not be unlocked on return, is your own problem.
628
Does not throw errors or anything, may update d_status however */
631
void BindBackend::queueReload(BBDomainInfo *bbd)
633
// we reload *now* for the time being
634
//cout<<"unlock domain"<<endl;
636
//cout<<"lock it again"<<endl;
640
nukeZoneRecords(bbd);
644
ZP.setCallback(&callback);
645
ZP.parse(bbd->d_filename,bbd->d_name,bbd->d_id);
647
// and raise d_loaded again!
650
bbd->d_status="parsed into memory at "+nowTime();
651
L<<Logger::Warning<<"Zone '"<<bbd->d_name<<"' ("<<bbd->d_filename<<") reloaded"<<endl;
653
catch(AhuException &ae) {
655
msg<<" error at "+nowTime()+" parsing '"<<bbd->d_name<<"' from file '"<<bbd->d_filename<<"': "<<ae.reason;
656
bbd->d_status=msg.str();
660
void BindBackend::lookup(const QType &qtype,const string &qname, DNSPacket *pkt_p, int zoneId )
662
d_handle=new BindBackend::handle;
663
DLOG(L<<"BindBackend constructing handle for search for "<<qtype.getName()<<" for "<<
666
d_handle->qname=qname;
667
d_handle->parent=this;
668
d_handle->qtype=qtype;
670
s_hc.encode(toLower(qname),compressed);
671
d_handle->d_records=d_qnames[compressed];
673
if(!d_handle->d_records.empty()) {
674
BBDomainInfo& bbd=d_bbds[d_handle->d_records.begin()->domain_id];
677
throw DBException("Zone temporarily not available (file missing, or master dead)"); // fuck
680
if(!bbd.tryRLock()) {
681
L<<Logger::Warning<<"Can't get read lock on zone '"<<bbd.d_name<<"'"<<endl;
683
throw DBException("Temporarily unavailable due to a zone lock"); // fuck
688
L<<Logger::Warning<<"Zone '"<<bbd.d_name<<"' ("<<bbd.d_filename<<") needs reloading"<<endl;
691
d_handle->d_bbd=&bbd;
694
DLOG(L<<"Query with no results"<<endl);
696
d_handle->d_iter=d_handle->d_records.begin();
697
d_handle->d_list=false;
700
BindBackend::handle::handle()
706
bool BindBackend::get(DNSResourceRecord &r)
708
if(!d_handle->get(r)) {
716
bool BindBackend::handle::get(DNSResourceRecord &r)
721
return get_normal(r);
724
bool BindBackend::handle::get_normal(DNSResourceRecord &r)
726
DLOG(L << "BindBackend get() was called for "<<qtype.getName() << " record for "<<
727
qname<<"- "<<d_records.size()<<" available!"<<endl);
730
while(d_iter!=d_records.end() && !(qtype=="ANY" || (d_iter)->qtype==QType(qtype).getCode())) {
731
DLOG(L<<"Skipped "<<qname<<"/"<<QType(d_iter->qtype).getName()<<": '"<<*d_iter->content<<"'"<<endl);
734
if(d_iter==d_records.end()) { // we've reached the end
742
DLOG(L << "BindBackend get() returning a rr with a "<<QType(d_iter->qtype).getCode()<<endl);
744
r.qname=qname; // fill this in
746
r.content=*(d_iter)->content;
747
r.domain_id=(d_iter)->domain_id;
748
r.qtype=(d_iter)->qtype;
750
r.priority=(d_iter)->priority;
756
bool BindBackend::list(const string &target, int id)
758
if(!d_zone_id_map.count(id))
761
d_handle=new BindBackend::handle;
762
DLOG(L<<"BindBackend constructing handle for list of "<<id<<endl);
764
d_handle->d_qname_iter=d_zone_id_map[id].begin();
765
d_handle->d_qname_end=d_zone_id_map[id].end(); // iter now points to a vector of pointers to vector<BBResourceRecords>
766
d_handle->d_riter=(*(d_handle->d_qname_iter))->begin();
767
d_handle->d_rend=(*(d_handle->d_qname_iter))->end();
769
d_handle->parent=this;
771
d_handle->d_list=true;
775
// naam -> naamnummer
776
// naamnummer -> vector<BBResourceRecords>, BBResourceRecord bevat ook een pointer naar
778
bool BindBackend::handle::get_list(DNSResourceRecord &r)
780
DLOG(L << "BindBackend get_list()"<<endl);
782
while(d_riter==d_rend) {
783
DLOG(L<<"Starting new record"<<endl);
785
if(d_qname_iter==d_qname_end) { // we've reached the end of recordsets for this id
786
DLOG(L<<"Really stop!"<<endl);
789
d_riter=(*(d_qname_iter))->begin();
790
d_rend=(*(d_qname_iter))->end();
792
// d_riter points to a pointer to BBResourceRecord
794
// r.qname=qname; // fill this in HOW?!
796
r.qname=parent->s_hc.decode(*d_riter->qnameptr);
798
r.content=*(d_riter)->content;
799
r.domain_id=(d_riter)->domain_id;
800
r.qtype=(d_riter)->qtype;
801
r.ttl=(d_riter)->ttl;
802
r.priority=(d_riter)->priority;
807
bool BindBackend::isMaster(const string &name, const string &ip)
809
for(map<uint32_t,BBDomainInfo>::iterator j=us->d_bbds.begin();j!=us->d_bbds.end();++j)
810
if(j->second.d_name==name)
811
return j->second.d_master==ip;
815
class BindFactory : public BackendFactory
818
BindFactory() : BackendFactory("bind1") {}
820
void declareArguments(const string &suffix="")
822
declare(suffix,"config","Location of named.conf","");
823
declare(suffix,"example-zones","Install example zones","no");
824
declare(suffix,"enable-huffman","Enable huffman compression","no");
825
declare(suffix,"check-interval","Interval for zonefile changes","0");
828
DNSBackend *make(const string &suffix="")
830
return new BindBackend(suffix);
837
//! Magic class that is activated when the dynamic library is loaded
843
BackendMakers().report(new BindFactory);
844
L<<Logger::Notice<<"[BindBackend] This is the bind backend version "VERSION" ("__DATE__", "__TIME__") reporting"<<endl;
847
static BindLoader bindloader;