]> git.ipfire.org Git - thirdparty/pdns.git/blob - modules/bindbackend/bindbackend2.cc
Merge pull request #7984 from qvr/slave-renotify-metadata
[thirdparty/pdns.git] / 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 <errno.h>
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
38 #include "pdns/dnsseckeeper.hh"
39 #include "pdns/dnssecinfra.hh"
40 #include "pdns/base32.hh"
41 #include "pdns/namespaces.hh"
42 #include "pdns/dns.hh"
43 #include "pdns/dnsbackend.hh"
44 #include "bindbackend2.hh"
45 #include "pdns/dnspacket.hh"
46 #include "pdns/zoneparser-tng.hh"
47 #include "pdns/bindparserclasses.hh"
48 #include "pdns/logger.hh"
49 #include "pdns/arguments.hh"
50 #include "pdns/qtype.hh"
51 #include "pdns/misc.hh"
52 #include "pdns/dynlistener.hh"
53 #include "pdns/lock.hh"
54 #include "pdns/namespaces.hh"
55
56 /*
57 All instances of this backend share one s_state, which is indexed by zone name and zone id.
58 The s_state is protected by a read/write lock, and the goal it to only interact with it briefly.
59 When a query comes in, we take a read lock and COPY the best zone to answer from s_state (BB2DomainInfo object)
60 All answers are served from this copy.
61
62 To interact with s_state, use safeGetBBDomainInfo (search on name or id), safePutBBDomainInfo (to update)
63 or safeRemoveBBDomainInfo. These all lock as they should.
64
65 Several functions need to traverse s_state to get data for the rest of PowerDNS. When doing so,
66 you need to manually take the s_state_lock (read).
67
68 Parsing zones happens with parseZone(), which fills a BB2DomainInfo object. This can then be stored with safePutBBDomainInfo.
69
70 Finally, the BB2DomainInfo contains all records as a LookButDontTouch object. This makes sure you only look, but don't touch, since
71 the records might be in use in other places.
72 */
73
74 Bind2Backend::state_t Bind2Backend::s_state;
75 int Bind2Backend::s_first=1;
76 bool Bind2Backend::s_ignore_broken_records=false;
77
78 pthread_rwlock_t Bind2Backend::s_state_lock=PTHREAD_RWLOCK_INITIALIZER;
79 pthread_mutex_t Bind2Backend::s_supermaster_config_lock=PTHREAD_MUTEX_INITIALIZER; // protects writes to config file
80 pthread_mutex_t Bind2Backend::s_startup_lock=PTHREAD_MUTEX_INITIALIZER;
81 string Bind2Backend::s_binddirectory;
82
83 BB2DomainInfo::BB2DomainInfo()
84 {
85 d_loaded=false;
86 d_lastcheck=0;
87 d_checknow=false;
88 d_status="Unknown";
89 }
90
91 void BB2DomainInfo::setCheckInterval(time_t seconds)
92 {
93 d_checkinterval=seconds;
94 }
95
96 bool BB2DomainInfo::current()
97 {
98 if(d_checknow) {
99 return false;
100 }
101
102 if(!d_checkinterval)
103 return true;
104
105 if(time(0) - d_lastcheck < d_checkinterval)
106 return true;
107
108 if(d_filename.empty())
109 return true;
110
111 return (getCtime()==d_ctime);
112 }
113
114 time_t BB2DomainInfo::getCtime()
115 {
116 struct stat buf;
117
118 if(d_filename.empty() || stat(d_filename.c_str(),&buf)<0)
119 return 0;
120 d_lastcheck=time(0);
121 return buf.st_ctime;
122 }
123
124 void BB2DomainInfo::setCtime()
125 {
126 struct stat buf;
127 if(stat(d_filename.c_str(),&buf)<0)
128 return;
129 d_ctime=buf.st_ctime;
130 }
131
132 bool Bind2Backend::safeGetBBDomainInfo(int id, BB2DomainInfo* bbd)
133 {
134 ReadLock rl(&s_state_lock);
135 state_t::const_iterator iter = s_state.find(id);
136 if(iter == s_state.end())
137 return false;
138 *bbd=*iter;
139 return true;
140 }
141
142 bool Bind2Backend::safeGetBBDomainInfo(const DNSName& name, BB2DomainInfo* bbd)
143 {
144 ReadLock rl(&s_state_lock);
145 typedef state_t::index<NameTag>::type nameindex_t;
146 nameindex_t& nameindex = boost::multi_index::get<NameTag>(s_state);
147
148 nameindex_t::const_iterator iter = nameindex.find(name);
149 if(iter == nameindex.end())
150 return false;
151 *bbd=*iter;
152 return true;
153 }
154
155 bool Bind2Backend::safeRemoveBBDomainInfo(const DNSName& name)
156 {
157 WriteLock rl(&s_state_lock);
158 typedef state_t::index<NameTag>::type nameindex_t;
159 nameindex_t& nameindex = boost::multi_index::get<NameTag>(s_state);
160
161 nameindex_t::iterator iter = nameindex.find(name);
162 if(iter == nameindex.end())
163 return false;
164 nameindex.erase(iter);
165 return true;
166 }
167
168 void Bind2Backend::safePutBBDomainInfo(const BB2DomainInfo& bbd)
169 {
170 WriteLock rl(&s_state_lock);
171 replacing_insert(s_state, bbd);
172 }
173
174 void Bind2Backend::setNotified(uint32_t id, uint32_t serial)
175 {
176 BB2DomainInfo bbd;
177 if (!safeGetBBDomainInfo(id, &bbd))
178 return;
179 bbd.d_lastnotified = serial;
180 safePutBBDomainInfo(bbd);
181 }
182
183 void Bind2Backend::setFresh(uint32_t domain_id)
184 {
185 BB2DomainInfo bbd;
186 if(safeGetBBDomainInfo(domain_id, &bbd)) {
187 bbd.d_lastcheck=time(0);
188 safePutBBDomainInfo(bbd);
189 }
190 }
191
192 bool Bind2Backend::startTransaction(const DNSName &qname, int id)
193 {
194 if(id < 0) {
195 d_transaction_tmpname.clear();
196 d_transaction_id=id;
197 return false;
198 }
199 if(id == 0) {
200 throw DBException("domain_id 0 is invalid for this backend.");
201 }
202
203 d_transaction_id=id;
204 BB2DomainInfo bbd;
205 if(safeGetBBDomainInfo(id, &bbd)) {
206 d_transaction_tmpname = bbd.d_filename + "XXXXXX";
207 int fd = mkstemp(&d_transaction_tmpname.at(0));
208 if (fd == -1) {
209 throw DBException("Unable to create a unique temporary zonefile '"+d_transaction_tmpname+"': "+stringerror());
210 return false;
211 }
212
213 d_of = std::unique_ptr<ofstream>(new ofstream(d_transaction_tmpname.c_str()));
214 if(!*d_of) {
215 unlink(d_transaction_tmpname.c_str());
216 close(fd);
217 fd = -1;
218 d_of.reset();
219 throw DBException("Unable to open temporary zonefile '"+d_transaction_tmpname+"': "+stringerror());
220 }
221 close(fd);
222 fd = -1;
223
224 *d_of<<"; Written by PowerDNS, don't edit!"<<endl;
225 *d_of<<"; Zone '"<<bbd.d_name<<"' retrieved from master "<<endl<<"; at "<<nowTime()<<endl; // insert master info here again
226
227 return true;
228 }
229 return false;
230 }
231
232 bool Bind2Backend::commitTransaction()
233 {
234 if(d_transaction_id < 0)
235 return false;
236 d_of.reset();
237
238 BB2DomainInfo bbd;
239 if(safeGetBBDomainInfo(d_transaction_id, &bbd)) {
240 if(rename(d_transaction_tmpname.c_str(), bbd.d_filename.c_str())<0)
241 throw DBException("Unable to commit (rename to: '" + bbd.d_filename+"') AXFRed zone: "+stringerror());
242 queueReloadAndStore(bbd.d_id);
243 }
244
245 d_transaction_id=0;
246
247 return true;
248 }
249
250 bool Bind2Backend::abortTransaction()
251 {
252 // -1 = dnssec speciality
253 // 0 = invalid transact
254 // >0 = actual transaction
255 if(d_transaction_id > 0) {
256 unlink(d_transaction_tmpname.c_str());
257 d_of.reset();
258 d_transaction_id=0;
259 }
260
261 return true;
262 }
263
264 bool Bind2Backend::feedRecord(const DNSResourceRecord &rr, const DNSName &ordername, bool ordernameIsNSEC3)
265 {
266 BB2DomainInfo bbd;
267 if (!safeGetBBDomainInfo(d_transaction_id, &bbd))
268 return false;
269
270 string qname;
271 string name = bbd.d_name.toString();
272 if (bbd.d_name.empty()) {
273 qname = rr.qname.toString();
274 }
275 else if (rr.qname.isPartOf(bbd.d_name)) {
276 if (rr.qname == bbd.d_name) {
277 qname = "@";
278 }
279 else {
280 DNSName relName = rr.qname.makeRelative(bbd.d_name);
281 qname = relName.toStringNoDot();
282 }
283 }
284 else {
285 throw DBException("out-of-zone data '"+rr.qname.toLogString()+"' during AXFR of zone '"+bbd.d_name.toLogString()+"'");
286 }
287
288 shared_ptr<DNSRecordContent> drc(DNSRecordContent::mastermake(rr.qtype.getCode(), 1, rr.content));
289 string content = drc->getZoneRepresentation();
290
291 // SOA needs stripping too! XXX FIXME - also, this should not be here I think
292 switch(rr.qtype.getCode()) {
293 case QType::MX:
294 case QType::SRV:
295 case QType::CNAME:
296 case QType::DNAME:
297 case QType::NS:
298 stripDomainSuffix(&content, name);
299 // fallthrough
300 default:
301 if (d_of && *d_of) {
302 *d_of<<qname<<"\t"<<rr.ttl<<"\t"<<rr.qtype.getName()<<"\t"<<content<<endl;
303 }
304 }
305 return true;
306 }
307
308 void Bind2Backend::getUpdatedMasters(vector<DomainInfo> *changedDomains)
309 {
310 vector<DomainInfo> consider;
311 {
312 ReadLock rl(&s_state_lock);
313
314 for(state_t::const_iterator i = s_state.begin(); i != s_state.end() ; ++i) {
315 if(i->d_kind != DomainInfo::Master && this->alsoNotify.empty() && i->d_also_notify.empty())
316 continue;
317
318 DomainInfo di;
319 di.id=i->d_id;
320 di.zone=i->d_name;
321 di.last_check=i->d_lastcheck;
322 di.notified_serial=i->d_lastnotified;
323 di.backend=this;
324 di.kind=DomainInfo::Master;
325 consider.push_back(di);
326 }
327 }
328
329 SOAData soadata;
330 for(DomainInfo& di : consider) {
331 soadata.serial=0;
332 try {
333 this->getSOA(di.zone, soadata); // we might not *have* a SOA yet, but this might trigger a load of it
334 }
335 catch(...) {
336 continue;
337 }
338 if(di.notified_serial != soadata.serial) {
339 BB2DomainInfo bbd;
340 if(safeGetBBDomainInfo(di.id, &bbd)) {
341 bbd.d_lastnotified=soadata.serial;
342 safePutBBDomainInfo(bbd);
343 }
344 if(di.notified_serial) { // don't do notification storm on startup
345 di.serial=soadata.serial;
346 changedDomains->push_back(di);
347 }
348 }
349 }
350 }
351
352 void Bind2Backend::getAllDomains(vector<DomainInfo> *domains, bool include_disabled)
353 {
354 SOAData soadata;
355
356 // prevent deadlock by using getSOA() later on
357 {
358 ReadLock rl(&s_state_lock);
359
360 for(state_t::const_iterator i = s_state.begin(); i != s_state.end() ; ++i) {
361 DomainInfo di;
362 di.id=i->d_id;
363 di.zone=i->d_name;
364 di.last_check=i->d_lastcheck;
365 di.kind=i->d_kind;
366 di.masters=i->d_masters;
367 di.backend=this;
368 domains->push_back(di);
369 };
370 }
371
372 for(DomainInfo &di : *domains) {
373 // do not corrupt di if domain supplied by another backend.
374 if (di.backend != this)
375 continue;
376 try {
377 this->getSOA(di.zone, soadata);
378 } catch(...) {
379 continue;
380 }
381 di.serial=soadata.serial;
382 }
383 }
384
385 void Bind2Backend::getUnfreshSlaveInfos(vector<DomainInfo> *unfreshDomains)
386 {
387 vector<DomainInfo> domains;
388 {
389 ReadLock rl(&s_state_lock);
390 for(state_t::const_iterator i = s_state.begin(); i != s_state.end() ; ++i) {
391 if(i->d_kind != DomainInfo::Slave)
392 continue;
393 DomainInfo sd;
394 sd.id=i->d_id;
395 sd.zone=i->d_name;
396 sd.masters=i->d_masters;
397 sd.last_check=i->d_lastcheck;
398 sd.backend=this;
399 sd.kind=DomainInfo::Slave;
400 domains.push_back(sd);
401 }
402 }
403
404 for(DomainInfo &sd : domains) {
405 SOAData soadata;
406 soadata.refresh=0;
407 soadata.serial=0;
408 try {
409 getSOA(sd.zone,soadata); // we might not *have* a SOA yet
410 }
411 catch(...){}
412 sd.serial=soadata.serial;
413 if(sd.last_check+soadata.refresh < (unsigned int)time(0))
414 unfreshDomains->push_back(sd);
415 }
416 }
417
418 bool Bind2Backend::getDomainInfo(const DNSName& domain, DomainInfo &di, bool getSerial)
419 {
420 BB2DomainInfo bbd;
421 if(!safeGetBBDomainInfo(domain, &bbd))
422 return false;
423
424 di.id=bbd.d_id;
425 di.zone=domain;
426 di.masters=bbd.d_masters;
427 di.last_check=bbd.d_lastcheck;
428 di.backend=this;
429 di.kind=bbd.d_kind;
430 di.serial=0;
431 if(getSerial) {
432 try {
433 SOAData sd;
434 sd.serial=0;
435
436 getSOA(bbd.d_name,sd); // we might not *have* a SOA yet
437 di.serial=sd.serial;
438 }
439 catch(...){}
440 }
441
442 return true;
443 }
444
445 void Bind2Backend::alsoNotifies(const DNSName& domain, set<string> *ips)
446 {
447 // combine global list with local list
448 for(set<string>::iterator i = this->alsoNotify.begin(); i != this->alsoNotify.end(); i++) {
449 (*ips).insert(*i);
450 }
451 ReadLock rl(&s_state_lock);
452 for(state_t::const_iterator i = s_state.begin(); i != s_state.end() ; ++i) {
453 if(i->d_name == domain) {
454 for(set<string>::iterator it = i->d_also_notify.begin(); it != i->d_also_notify.end(); it++) {
455 (*ips).insert(*it);
456 }
457 return;
458 }
459 }
460 }
461
462 // only parses, does NOT add to s_state!
463 void Bind2Backend::parseZoneFile(BB2DomainInfo *bbd)
464 {
465 NSEC3PARAMRecordContent ns3pr;
466 bool nsec3zone;
467 if (d_hybrid) {
468 DNSSECKeeper dk;
469 nsec3zone=dk.getNSEC3PARAM(bbd->d_name, &ns3pr);
470 } else
471 nsec3zone=getNSEC3PARAM(bbd->d_name, &ns3pr);
472
473 bbd->d_records = shared_ptr<recordstorage_t>(new recordstorage_t());
474
475 ZoneParserTNG zpt(bbd->d_filename, bbd->d_name, s_binddirectory);
476 DNSResourceRecord rr;
477 string hashed;
478 while(zpt.get(rr)) {
479 if(rr.qtype.getCode() == QType::NSEC || rr.qtype.getCode() == QType::NSEC3 || rr.qtype.getCode() == QType::NSEC3PARAM)
480 continue; // we synthesise NSECs on demand
481
482 insertRecord(*bbd, rr.qname, rr.qtype, rr.content, rr.ttl, "");
483 }
484 fixupOrderAndAuth(*bbd, nsec3zone, ns3pr);
485 doEmptyNonTerminals(*bbd, nsec3zone, ns3pr);
486 bbd->setCtime();
487 bbd->d_loaded=true;
488 bbd->d_checknow=false;
489 bbd->d_status="parsed into memory at "+nowTime();
490 }
491
492 /** THIS IS AN INTERNAL FUNCTION! It does moadnsparser prio impedance matching
493 Much of the complication is due to the efforts to benefit from std::string reference counting copy on write semantics */
494 void Bind2Backend::insertRecord(BB2DomainInfo& bb2, const DNSName &qname, const QType &qtype, const string &content, int ttl, const std::string& hashed, bool *auth)
495 {
496 Bind2DNSRecord bdr;
497 shared_ptr<recordstorage_t> records = bb2.d_records.getWRITABLE();
498 bdr.qname=qname;
499
500 if(bb2.d_name.empty())
501 ;
502 else if(bdr.qname.isPartOf(bb2.d_name))
503 bdr.qname = bdr.qname.makeRelative(bb2.d_name);
504 else {
505 string msg = "Trying to insert non-zone data, name='"+bdr.qname.toLogString()+"', qtype="+qtype.getName()+", zone='"+bb2.d_name.toLogString()+"'";
506 if(s_ignore_broken_records) {
507 g_log<<Logger::Warning<<msg<< " ignored" << endl;
508 return;
509 }
510 else
511 throw PDNSException(msg);
512 }
513
514 // bdr.qname.swap(bdr.qname);
515
516 if(!records->empty() && bdr.qname==boost::prior(records->end())->qname)
517 bdr.qname=boost::prior(records->end())->qname;
518
519 bdr.qname=bdr.qname;
520 bdr.qtype=qtype.getCode();
521 bdr.content=content;
522 bdr.nsec3hash = hashed;
523
524 if (auth) // Set auth on empty non-terminals
525 bdr.auth=*auth;
526 else
527 bdr.auth=true;
528
529 bdr.ttl=ttl;
530 records->insert(bdr);
531 }
532
533 string Bind2Backend::DLReloadNowHandler(const vector<string>&parts, Utility::pid_t ppid)
534 {
535 ostringstream ret;
536
537 for(vector<string>::const_iterator i=parts.begin()+1;i<parts.end();++i) {
538 BB2DomainInfo bbd;
539 DNSName zone(*i);
540 if(safeGetBBDomainInfo(zone, &bbd)) {
541 Bind2Backend bb2;
542 bb2.queueReloadAndStore(bbd.d_id);
543 if (!safeGetBBDomainInfo(zone, &bbd)) // Read the *new* domain status
544 ret << *i << ": [missing]\n";
545 else
546 ret<< *i << ": "<< (bbd.d_wasRejectedLastReload ? "[rejected]": "") <<"\t"<<bbd.d_status<<"\n";
547 }
548 else
549 ret<< *i << " no such domain\n";
550 }
551 if(ret.str().empty())
552 ret<<"no domains reloaded";
553 return ret.str();
554 }
555
556
557 string Bind2Backend::DLDomStatusHandler(const vector<string>&parts, Utility::pid_t ppid)
558 {
559 ostringstream ret;
560
561 if(parts.size() > 1) {
562 for(vector<string>::const_iterator i=parts.begin()+1;i<parts.end();++i) {
563 BB2DomainInfo bbd;
564 if(safeGetBBDomainInfo(DNSName(*i), &bbd)) {
565 ret<< *i << ": "<< (bbd.d_loaded ? "": "[rejected]") <<"\t"<<bbd.d_status<<"\n";
566 }
567 else
568 ret<< *i << " no such domain\n";
569 }
570 }
571 else {
572 ReadLock rl(&s_state_lock);
573 for(state_t::const_iterator i = s_state.begin(); i != s_state.end() ; ++i) {
574 ret<< i->d_name << ": "<< (i->d_loaded ? "": "[rejected]") <<"\t"<<i->d_status<<"\n";
575 }
576 }
577
578 if(ret.str().empty())
579 ret<<"no domains passed";
580
581 return ret.str();
582 }
583
584 string Bind2Backend::DLListRejectsHandler(const vector<string>&parts, Utility::pid_t ppid)
585 {
586 ostringstream ret;
587 ReadLock rl(&s_state_lock);
588 for(state_t::const_iterator i = s_state.begin(); i != s_state.end() ; ++i) {
589 if(!i->d_loaded)
590 ret<<i->d_name<<"\t"<<i->d_status<<endl;
591 }
592 return ret.str();
593 }
594
595 string Bind2Backend::DLAddDomainHandler(const vector<string>&parts, Utility::pid_t ppid)
596 {
597 if(parts.size() < 3)
598 return "ERROR: Domain name and zone filename are required";
599
600 DNSName domainname(parts[1]);
601 const string &filename = parts[2];
602 BB2DomainInfo bbd;
603 if(safeGetBBDomainInfo(domainname, &bbd))
604 return "Already loaded";
605
606 if (!boost::starts_with(filename, "/") && ::arg()["chroot"].empty())
607 return "Unable to load zone " + domainname.toLogString() + " from " + filename + " as the filename is not absolute.";
608
609 struct stat buf;
610 if (stat(filename.c_str(), &buf) != 0)
611 return "Unable to load zone " + domainname.toLogString() + " from " + filename + ": " + strerror(errno);
612
613 Bind2Backend bb2; // createdomainentry needs access to our configuration
614 bbd=bb2.createDomainEntry(domainname, filename);
615 bbd.d_filename=filename;
616 bbd.d_checknow=true;
617 bbd.d_loaded=true;
618 bbd.d_lastcheck=0;
619 bbd.d_status="parsing into memory";
620 bbd.setCtime();
621
622 safePutBBDomainInfo(bbd);
623
624 g_log<<Logger::Warning<<"Zone "<<domainname<< " loaded"<<endl;
625 return "Loaded zone " + domainname.toLogString() + " from " + filename;
626 }
627
628 Bind2Backend::Bind2Backend(const string &suffix, bool loadZones)
629 {
630 d_getAllDomainMetadataQuery_stmt = NULL;
631 d_getDomainMetadataQuery_stmt = NULL;
632 d_deleteDomainMetadataQuery_stmt = NULL;
633 d_insertDomainMetadataQuery_stmt = NULL;
634 d_getDomainKeysQuery_stmt = NULL;
635 d_deleteDomainKeyQuery_stmt = NULL;
636 d_insertDomainKeyQuery_stmt = NULL;
637 d_GetLastInsertedKeyIdQuery_stmt = NULL;
638 d_activateDomainKeyQuery_stmt = NULL;
639 d_deactivateDomainKeyQuery_stmt = NULL;
640 d_getTSIGKeyQuery_stmt = NULL;
641 d_setTSIGKeyQuery_stmt = NULL;
642 d_deleteTSIGKeyQuery_stmt = NULL;
643 d_getTSIGKeysQuery_stmt = NULL;
644
645 setArgPrefix("bind"+suffix);
646 d_logprefix="[bind"+suffix+"backend]";
647 d_hybrid=mustDo("hybrid");
648 d_transaction_id=0;
649 s_ignore_broken_records=mustDo("ignore-broken-records");
650
651 if (!loadZones && d_hybrid)
652 return;
653
654 Lock l(&s_startup_lock);
655
656 setupDNSSEC();
657 if(!s_first) {
658 return;
659 }
660
661 if(loadZones) {
662 loadConfig();
663 s_first=0;
664 }
665
666 extern DynListener *dl;
667 dl->registerFunc("BIND-RELOAD-NOW", &DLReloadNowHandler, "bindbackend: reload domains", "<domains>");
668 dl->registerFunc("BIND-DOMAIN-STATUS", &DLDomStatusHandler, "bindbackend: list status of all domains", "[domains]");
669 dl->registerFunc("BIND-LIST-REJECTS", &DLListRejectsHandler, "bindbackend: list rejected domains");
670 dl->registerFunc("BIND-ADD-ZONE", &DLAddDomainHandler, "bindbackend: add zone", "<domain> <filename>");
671 }
672
673 Bind2Backend::~Bind2Backend()
674 { freeStatements(); } // deallocate statements
675
676 void Bind2Backend::rediscover(string *status)
677 {
678 loadConfig(status);
679 }
680
681 void Bind2Backend::reload()
682 {
683 WriteLock rwl(&s_state_lock);
684 for(state_t::iterator i = s_state.begin(); i != s_state.end() ; ++i) {
685 i->d_checknow=true; // being a bit cheeky here, don't index state_t on this (mutable)
686 }
687 }
688
689 void Bind2Backend::fixupOrderAndAuth(BB2DomainInfo& bbd, bool nsec3zone, NSEC3PARAMRecordContent ns3pr)
690 {
691 shared_ptr<recordstorage_t> records = bbd.d_records.getWRITABLE();
692
693 bool skip;
694 DNSName shorter;
695 set<DNSName> nssets, dssets;
696
697 for(const auto& bdr: *records) {
698 if(!bdr.qname.isRoot() && bdr.qtype == QType::NS)
699 nssets.insert(bdr.qname);
700 else if(bdr.qtype == QType::DS)
701 dssets.insert(bdr.qname);
702 }
703
704 for(auto iter = records->begin(); iter != records->end(); iter++) {
705 skip = false;
706 shorter = iter->qname;
707
708 if (!iter->qname.isRoot() && shorter.chopOff() && !iter->qname.isRoot()) {
709 do {
710 if(nssets.count(shorter)) {
711 skip = true;
712 break;
713 }
714 } while(shorter.chopOff() && !iter->qname.isRoot());
715 }
716
717 iter->auth = (!skip && (iter->qtype == QType::DS || iter->qtype == QType::RRSIG || !nssets.count(iter->qname)));
718
719 if(!skip && nsec3zone && iter->qtype != QType::RRSIG && (iter->auth || (iter->qtype == QType::NS && !ns3pr.d_flags) || dssets.count(iter->qname))) {
720 Bind2DNSRecord bdr = *iter;
721 bdr.nsec3hash = toBase32Hex(hashQNameWithSalt(ns3pr, bdr.qname+bbd.d_name));
722 records->replace(iter, bdr);
723 }
724
725 // cerr<<iter->qname<<"\t"<<QType(iter->qtype).getName()<<"\t"<<iter->nsec3hash<<"\t"<<iter->auth<<endl;
726 }
727 }
728
729 void Bind2Backend::doEmptyNonTerminals(BB2DomainInfo& bbd, bool nsec3zone, NSEC3PARAMRecordContent ns3pr)
730 {
731 shared_ptr<const recordstorage_t> records = bbd.d_records.get();
732
733 bool auth;
734 DNSName shorter;
735 set<DNSName> qnames;
736 map<DNSName, bool> nonterm;
737
738 uint32_t maxent = ::arg().asNum("max-ent-entries");
739
740 for(const auto& bdr : *records)
741 qnames.insert(bdr.qname);
742
743 for(const auto& bdr : *records) {
744
745 if (!bdr.auth && bdr.qtype == QType::NS)
746 auth = (!nsec3zone || !ns3pr.d_flags);
747 else
748 auth = bdr.auth;
749
750 shorter = bdr.qname;
751 while(shorter.chopOff())
752 {
753 if(!qnames.count(shorter))
754 {
755 if(!(maxent))
756 {
757 g_log<<Logger::Error<<"Zone '"<<bbd.d_name<<"' has too many empty non terminals."<<endl;
758 return;
759 }
760
761 if (!nonterm.count(shorter)) {
762 nonterm.insert(pair<DNSName, bool>(shorter, auth));
763 --maxent;
764 } else if (auth)
765 nonterm[shorter] = true;
766 }
767 }
768 }
769
770 DNSResourceRecord rr;
771 rr.qtype = "#0";
772 rr.content = "";
773 rr.ttl = 0;
774 for(auto& nt : nonterm)
775 {
776 string hashed;
777 rr.qname = nt.first + bbd.d_name;
778 if(nsec3zone && nt.second)
779 hashed = toBase32Hex(hashQNameWithSalt(ns3pr, rr.qname));
780 insertRecord(bbd, rr.qname, rr.qtype, rr.content, rr.ttl, hashed, &nt.second);
781
782 // cerr<<rr.qname<<"\t"<<rr.qtype.getName()<<"\t"<<hashed<<"\t"<<nt.second<<endl;
783 }
784 }
785
786 void Bind2Backend::loadConfig(string* status)
787 {
788 static int domain_id=1;
789
790 if(!getArg("config").empty()) {
791 BindParser BP;
792 try {
793 BP.parse(getArg("config"));
794 }
795 catch(PDNSException &ae) {
796 g_log<<Logger::Error<<"Error parsing bind configuration: "<<ae.reason<<endl;
797 throw;
798 }
799
800 vector<BindDomainInfo> domains=BP.getDomains();
801 this->alsoNotify = BP.getAlsoNotify();
802
803 s_binddirectory=BP.getDirectory();
804 // ZP.setDirectory(d_binddirectory);
805
806 g_log<<Logger::Warning<<d_logprefix<<" Parsing "<<domains.size()<<" domain(s), will report when done"<<endl;
807
808 set<DNSName> oldnames, newnames;
809 {
810 ReadLock rl(&s_state_lock);
811 for(const BB2DomainInfo& bbd : s_state) {
812 oldnames.insert(bbd.d_name);
813 }
814 }
815 int rejected=0;
816 int newdomains=0;
817
818 struct stat st;
819
820 for(vector<BindDomainInfo>::iterator i=domains.begin(); i!=domains.end(); ++i)
821 {
822 if(stat(i->filename.c_str(), &st) == 0) {
823 i->d_dev = st.st_dev;
824 i->d_ino = st.st_ino;
825 }
826 }
827
828 sort(domains.begin(), domains.end()); // put stuff in inode order
829 for(vector<BindDomainInfo>::const_iterator i=domains.begin();
830 i!=domains.end();
831 ++i)
832 {
833 if (!(i->hadFileDirective)) {
834 g_log<<Logger::Warning<<d_logprefix<<" Zone '"<<i->name<<"' has no 'file' directive set in "<<getArg("config")<<endl;
835 rejected++;
836 continue;
837 }
838
839 if(i->type == "")
840 g_log<<Logger::Notice<<d_logprefix<<" Zone '"<<i->name<<"' has no type specified, assuming 'native'"<<endl;
841 if(i->type!="master" && i->type!="slave" && i->type != "native" && i->type != "") {
842 g_log<<Logger::Warning<<d_logprefix<<" Warning! Skipping zone '"<<i->name<<"' because type '"<<i->type<<"' is invalid"<<endl;
843 rejected++;
844 continue;
845 }
846
847 BB2DomainInfo bbd;
848 bool isNew = false;
849
850 if(!safeGetBBDomainInfo(i->name, &bbd)) {
851 isNew = true;
852 bbd.d_id=domain_id++;
853 bbd.setCheckInterval(getArgAsNum("check-interval"));
854 bbd.d_lastnotified=0;
855 bbd.d_loaded=false;
856 }
857
858 // overwrite what we knew about the domain
859 bbd.d_name=i->name;
860 bool filenameChanged = (bbd.d_filename!=i->filename);
861 bbd.d_filename=i->filename;
862 bbd.d_masters=i->masters;
863 bbd.d_also_notify=i->alsoNotify;
864
865 bbd.d_kind = DomainInfo::Native;
866 if (i->type == "master")
867 bbd.d_kind = DomainInfo::Master;
868 if (i->type == "slave")
869 bbd.d_kind = DomainInfo::Slave;
870
871 newnames.insert(bbd.d_name);
872 if(filenameChanged || !bbd.d_loaded || !bbd.current()) {
873 g_log<<Logger::Info<<d_logprefix<<" parsing '"<<i->name<<"' from file '"<<i->filename<<"'"<<endl;
874
875 try {
876 parseZoneFile(&bbd);
877 }
878 catch(PDNSException &ae) {
879 ostringstream msg;
880 msg<<" error at "+nowTime()+" parsing '"<<i->name<<"' from file '"<<i->filename<<"': "<<ae.reason;
881
882 if(status)
883 *status+=msg.str();
884 bbd.d_status=msg.str();
885
886 g_log<<Logger::Warning<<d_logprefix<<msg.str()<<endl;
887 rejected++;
888 }
889 catch(std::system_error &ae) {
890 ostringstream msg;
891 if (ae.code().value() == ENOENT && isNew && i->type == "slave")
892 msg<<" error at "+nowTime()<<" no file found for new slave domain '"<<i->name<<"'. Has not been AXFR'd yet";
893 else
894 msg<<" error at "+nowTime()+" parsing '"<<i->name<<"' from file '"<<i->filename<<"': "<<ae.what();
895
896 if(status)
897 *status+=msg.str();
898 bbd.d_status=msg.str();
899 g_log<<Logger::Warning<<d_logprefix<<msg.str()<<endl;
900 rejected++;
901 }
902 catch(std::exception &ae) {
903 ostringstream msg;
904 msg<<" error at "+nowTime()+" parsing '"<<i->name<<"' from file '"<<i->filename<<"': "<<ae.what();
905
906 if(status)
907 *status+=msg.str();
908 bbd.d_status=msg.str();
909
910 g_log<<Logger::Warning<<d_logprefix<<msg.str()<<endl;
911 rejected++;
912 }
913 safePutBBDomainInfo(bbd);
914
915 }
916 }
917 vector<DNSName> diff;
918
919 set_difference(oldnames.begin(), oldnames.end(), newnames.begin(), newnames.end(), back_inserter(diff));
920 unsigned int remdomains=diff.size();
921
922 for(const DNSName& name: diff) {
923 safeRemoveBBDomainInfo(name);
924 }
925
926 // count number of entirely new domains
927 diff.clear();
928 set_difference(newnames.begin(), newnames.end(), oldnames.begin(), oldnames.end(), back_inserter(diff));
929 newdomains=diff.size();
930
931 ostringstream msg;
932 msg<<" Done parsing domains, "<<rejected<<" rejected, "<<newdomains<<" new, "<<remdomains<<" removed";
933 if(status)
934 *status=msg.str();
935
936 g_log<<Logger::Error<<d_logprefix<<msg.str()<<endl;
937 }
938 }
939
940 void Bind2Backend::queueReloadAndStore(unsigned int id)
941 {
942 BB2DomainInfo bbold;
943 try {
944 if(!safeGetBBDomainInfo(id, &bbold))
945 return;
946 BB2DomainInfo bbnew(bbold);
947 parseZoneFile(&bbnew);
948 bbnew.d_checknow=false;
949 bbnew.d_wasRejectedLastReload=false;
950 safePutBBDomainInfo(bbnew);
951 g_log<<Logger::Warning<<"Zone '"<<bbnew.d_name<<"' ("<<bbnew.d_filename<<") reloaded"<<endl;
952 }
953 catch(PDNSException &ae) {
954 ostringstream msg;
955 msg<<" error at "+nowTime()+" parsing '"<<bbold.d_name<<"' from file '"<<bbold.d_filename<<"': "<<ae.reason;
956 g_log<<Logger::Warning<<" error parsing '"<<bbold.d_name<<"' from file '"<<bbold.d_filename<<"': "<<ae.reason<<endl;
957 bbold.d_status=msg.str();
958 bbold.d_wasRejectedLastReload=true;
959 safePutBBDomainInfo(bbold);
960 }
961 catch(std::exception &ae) {
962 ostringstream msg;
963 msg<<" error at "+nowTime()+" parsing '"<<bbold.d_name<<"' from file '"<<bbold.d_filename<<"': "<<ae.what();
964 g_log<<Logger::Warning<<" error parsing '"<<bbold.d_name<<"' from file '"<<bbold.d_filename<<"': "<<ae.what()<<endl;
965 bbold.d_status=msg.str();
966 bbold.d_wasRejectedLastReload=true;
967 safePutBBDomainInfo(bbold);
968 }
969 }
970
971 bool Bind2Backend::findBeforeAndAfterUnhashed(BB2DomainInfo& bbd, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
972 {
973 shared_ptr<const recordstorage_t> records = bbd.d_records.get();
974
975 // for(const auto& record: *records)
976 // cerr<<record.qname<<"\t"<<makeHexDump(record.qname.toDNSString())<<endl;
977
978 recordstorage_t::const_iterator iterBefore, iterAfter;
979
980 iterBefore = iterAfter = records->upper_bound(qname.makeLowerCase());
981
982 if(iterBefore != records->begin())
983 --iterBefore;
984 while((!iterBefore->auth && iterBefore->qtype != QType::NS) || !iterBefore->qtype)
985 --iterBefore;
986 before=iterBefore->qname;
987
988 if(iterAfter == records->end()) {
989 iterAfter = records->begin();
990 } else {
991 while((!iterAfter->auth && iterAfter->qtype != QType::NS) || !iterAfter->qtype) {
992 ++iterAfter;
993 if(iterAfter == records->end()) {
994 iterAfter = records->begin();
995 break;
996 }
997 }
998 }
999 after = iterAfter->qname;
1000
1001 return true;
1002 }
1003
1004 bool Bind2Backend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
1005 {
1006 BB2DomainInfo bbd;
1007 if (!safeGetBBDomainInfo(id, &bbd))
1008 return false;
1009
1010 NSEC3PARAMRecordContent ns3pr;
1011
1012 bool nsec3zone;
1013 if (d_hybrid) {
1014 DNSSECKeeper dk;
1015 nsec3zone=dk.getNSEC3PARAM(bbd.d_name, &ns3pr);
1016 } else
1017 nsec3zone=getNSEC3PARAM(bbd.d_name, &ns3pr);
1018
1019 if(!nsec3zone) {
1020 return findBeforeAndAfterUnhashed(bbd, qname, unhashed, before, after);
1021 }
1022 else {
1023 auto& hashindex=boost::multi_index::get<NSEC3Tag>(*bbd.d_records.getWRITABLE());
1024
1025 // for(auto iter = first; iter != hashindex.end(); iter++)
1026 // cerr<<iter->nsec3hash<<endl;
1027
1028 auto first = hashindex.upper_bound("");
1029 auto iter = hashindex.upper_bound(qname.toStringNoDot());
1030
1031 if (iter == hashindex.end()) {
1032 --iter;
1033 before = DNSName(iter->nsec3hash);
1034 after = DNSName(first->nsec3hash);
1035 } else {
1036 after = DNSName(iter->nsec3hash);
1037 if (iter != first)
1038 --iter;
1039 else
1040 iter = --hashindex.end();
1041 before = DNSName(iter->nsec3hash);
1042 }
1043 unhashed = iter->qname+bbd.d_name;
1044
1045 return true;
1046 }
1047 }
1048
1049 void Bind2Backend::lookup(const QType &qtype, const DNSName &qname, DNSPacket *pkt_p, int zoneId )
1050 {
1051 d_handle.reset();
1052 DNSName domain(qname);
1053
1054 static bool mustlog=::arg().mustDo("query-logging");
1055 if(mustlog)
1056 g_log<<Logger::Warning<<"Lookup for '"<<qtype.getName()<<"' of '"<<domain<<"' within zoneID "<<zoneId<<endl;
1057 bool found=false;
1058 BB2DomainInfo bbd;
1059
1060 do {
1061 found = safeGetBBDomainInfo(domain, &bbd);
1062 } while ((!found || (zoneId != (int)bbd.d_id && zoneId != -1)) && domain.chopOff());
1063
1064 if(!found) {
1065 if(mustlog)
1066 g_log<<Logger::Warning<<"Found no authoritative zone for "<<qname<<endl;
1067 d_handle.d_list=false;
1068 return;
1069 }
1070
1071 if(mustlog)
1072 g_log<<Logger::Warning<<"Found a zone '"<<domain<<"' (with id " << bbd.d_id<<") that might contain data "<<endl;
1073
1074 d_handle.id=bbd.d_id;
1075
1076 DLOG(g_log<<"Bind2Backend constructing handle for search for "<<qtype.getName()<<" for "<<
1077 qname<<endl);
1078
1079 if(domain.empty())
1080 d_handle.qname=qname;
1081 else if(qname.isPartOf(domain))
1082 d_handle.qname=qname.makeRelative(domain); // strip domain name
1083
1084 d_handle.qtype=qtype;
1085 d_handle.domain=domain;
1086
1087 if(!bbd.d_loaded) {
1088 d_handle.reset();
1089 throw DBException("Zone for '"+bbd.d_name.toLogString()+"' in '"+bbd.d_filename+"' temporarily not available (file missing, or master dead)"); // fsck
1090 }
1091
1092 if(!bbd.current()) {
1093 g_log<<Logger::Warning<<"Zone '"<<bbd.d_name<<"' ("<<bbd.d_filename<<") needs reloading"<<endl;
1094 queueReloadAndStore(bbd.d_id);
1095 if (!safeGetBBDomainInfo(domain, &bbd))
1096 throw DBException("Zone '"+bbd.d_name.toLogString()+"' ("+bbd.d_filename+") gone after reload"); // if we don't throw here, we crash for some reason
1097 }
1098
1099 d_handle.d_records = bbd.d_records.get();
1100
1101 if(d_handle.d_records->empty())
1102 DLOG(g_log<<"Query with no results"<<endl);
1103
1104 d_handle.mustlog = mustlog;
1105
1106 auto& hashedidx = boost::multi_index::get<UnorderedNameTag>(*d_handle.d_records);
1107 auto range = hashedidx.equal_range(d_handle.qname);
1108
1109 if(range.first==range.second) {
1110 d_handle.d_list=false;
1111 d_handle.d_iter = d_handle.d_end_iter = range.first;
1112 return;
1113 }
1114 else {
1115 d_handle.d_iter=range.first;
1116 d_handle.d_end_iter=range.second;
1117 }
1118
1119 d_handle.d_list=false;
1120 }
1121
1122 Bind2Backend::handle::handle()
1123 {
1124 mustlog=false;
1125 }
1126
1127 bool Bind2Backend::get(DNSResourceRecord &r)
1128 {
1129 if(!d_handle.d_records) {
1130 if(d_handle.mustlog)
1131 g_log<<Logger::Warning<<"There were no answers"<<endl;
1132 return false;
1133 }
1134
1135 if(!d_handle.get(r)) {
1136 if(d_handle.mustlog)
1137 g_log<<Logger::Warning<<"End of answers"<<endl;
1138
1139 d_handle.reset();
1140
1141 return false;
1142 }
1143 if(d_handle.mustlog)
1144 g_log<<Logger::Warning<<"Returning: '"<<r.qtype.getName()<<"' of '"<<r.qname<<"', content: '"<<r.content<<"'"<<endl;
1145 return true;
1146 }
1147
1148 bool Bind2Backend::handle::get(DNSResourceRecord &r)
1149 {
1150 if(d_list)
1151 return get_list(r);
1152 else
1153 return get_normal(r);
1154 }
1155
1156 void Bind2Backend::handle::reset()
1157 {
1158 d_records.reset();
1159 qname.clear();
1160 mustlog=false;
1161 }
1162
1163 //#define DLOG(x) x
1164 bool Bind2Backend::handle::get_normal(DNSResourceRecord &r)
1165 {
1166 DLOG(g_log << "Bind2Backend get() was called for "<<qtype.getName() << " record for '"<<
1167 qname<<"' - "<<d_records->size()<<" available in total!"<<endl);
1168
1169 if(d_iter==d_end_iter) {
1170 return false;
1171 }
1172
1173 while(d_iter!=d_end_iter && !(qtype.getCode()==QType::ANY || (d_iter)->qtype==qtype.getCode())) {
1174 DLOG(g_log<<Logger::Warning<<"Skipped "<<qname<<"/"<<QType(d_iter->qtype).getName()<<": '"<<d_iter->content<<"'"<<endl);
1175 d_iter++;
1176 }
1177 if(d_iter==d_end_iter) {
1178 return false;
1179 }
1180 DLOG(g_log << "Bind2Backend get() returning a rr with a "<<QType(d_iter->qtype).getCode()<<endl);
1181
1182 r.qname=qname.empty() ? domain : (qname+domain);
1183 r.domain_id=id;
1184 r.content=(d_iter)->content;
1185 // r.domain_id=(d_iter)->domain_id;
1186 r.qtype=(d_iter)->qtype;
1187 r.ttl=(d_iter)->ttl;
1188
1189 //if(!d_iter->auth && r.qtype.getCode() != QType::A && r.qtype.getCode()!=QType::AAAA && r.qtype.getCode() != QType::NS)
1190 // cerr<<"Warning! Unauth response for qtype "<< r.qtype.getName() << " for '"<<r.qname<<"'"<<endl;
1191 r.auth = d_iter->auth;
1192
1193 d_iter++;
1194
1195 return true;
1196 }
1197
1198 bool Bind2Backend::list(const DNSName& target, int id, bool include_disabled)
1199 {
1200 BB2DomainInfo bbd;
1201
1202 if(!safeGetBBDomainInfo(id, &bbd))
1203 return false;
1204
1205 d_handle.reset();
1206 DLOG(g_log<<"Bind2Backend constructing handle for list of "<<id<<endl);
1207
1208 d_handle.d_records=bbd.d_records.get(); // give it a copy, which will stay around
1209 d_handle.d_qname_iter= d_handle.d_records->begin();
1210 d_handle.d_qname_end=d_handle.d_records->end(); // iter now points to a vector of pointers to vector<BBResourceRecords>
1211
1212 d_handle.id=id;
1213 d_handle.domain=bbd.d_name;
1214 d_handle.d_list=true;
1215 return true;
1216 }
1217
1218 bool Bind2Backend::handle::get_list(DNSResourceRecord &r)
1219 {
1220 if(d_qname_iter!=d_qname_end) {
1221 r.qname=d_qname_iter->qname.empty() ? domain : (d_qname_iter->qname+domain);
1222 r.domain_id=id;
1223 r.content=(d_qname_iter)->content;
1224 r.qtype=(d_qname_iter)->qtype;
1225 r.ttl=(d_qname_iter)->ttl;
1226 r.auth = d_qname_iter->auth;
1227 d_qname_iter++;
1228 return true;
1229 }
1230 return false;
1231 }
1232
1233 bool Bind2Backend::superMasterBackend(const string &ip, const DNSName& domain, const vector<DNSResourceRecord>&nsset, string *nameserver, string *account, DNSBackend **db)
1234 {
1235 // Check whether we have a configfile available.
1236 if (getArg("supermaster-config").empty())
1237 return false;
1238
1239 ifstream c_if(getArg("supermasters").c_str(), std::ios::in); // this was nocreate?
1240 if (!c_if) {
1241 g_log << Logger::Error << "Unable to open supermasters file for read: " << stringerror() << endl;
1242 return false;
1243 }
1244
1245 // Format:
1246 // <ip> <accountname>
1247 string line, sip, saccount;
1248 while (getline(c_if, line)) {
1249 std::istringstream ii(line);
1250 ii >> sip;
1251 if (sip == ip) {
1252 ii >> saccount;
1253 break;
1254 }
1255 }
1256 c_if.close();
1257
1258 if (sip != ip) // ip not found in authorization list - reject
1259 return false;
1260
1261 // ip authorized as supermaster - accept
1262 *db = this;
1263 if (saccount.length() > 0)
1264 *account = saccount.c_str();
1265
1266 return true;
1267 }
1268
1269 BB2DomainInfo Bind2Backend::createDomainEntry(const DNSName& domain, const string &filename)
1270 {
1271 int newid=1;
1272 { // Find a free zone id nr.
1273 ReadLock rl(&s_state_lock);
1274 if (!s_state.empty()) {
1275 newid = s_state.rbegin()->d_id+1;
1276 }
1277 }
1278
1279 BB2DomainInfo bbd;
1280 bbd.d_kind = DomainInfo::Native;
1281 bbd.d_id = newid;
1282 bbd.d_records = shared_ptr<recordstorage_t >(new recordstorage_t);
1283 bbd.d_name = domain;
1284 bbd.setCheckInterval(getArgAsNum("check-interval"));
1285 bbd.d_filename = filename;
1286
1287 return bbd;
1288 }
1289
1290 bool Bind2Backend::createSlaveDomain(const string &ip, const DNSName& domain, const string &nameserver, const string &account)
1291 {
1292 string filename = getArg("supermaster-destdir")+'/'+domain.toStringNoDot();
1293
1294 g_log << Logger::Warning << d_logprefix
1295 << " Writing bind config zone statement for superslave zone '" << domain
1296 << "' from supermaster " << ip << endl;
1297
1298 {
1299 Lock l2(&s_supermaster_config_lock);
1300
1301 ofstream c_of(getArg("supermaster-config").c_str(), std::ios::app);
1302 if (!c_of) {
1303 g_log << Logger::Error << "Unable to open supermaster configfile for append: " << stringerror() << endl;
1304 throw DBException("Unable to open supermaster configfile for append: "+stringerror());
1305 }
1306
1307 c_of << endl;
1308 c_of << "# Superslave zone '" << domain.toString() << "' (added: " << nowTime() << ") (account: " << account << ')' << endl;
1309 c_of << "zone \"" << domain.toStringNoDot() << "\" {" << endl;
1310 c_of << "\ttype slave;" << endl;
1311 c_of << "\tfile \"" << filename << "\";" << endl;
1312 c_of << "\tmasters { " << ip << "; };" << endl;
1313 c_of << "};" << endl;
1314 c_of.close();
1315 }
1316
1317 BB2DomainInfo bbd = createDomainEntry(domain, filename);
1318 bbd.d_kind = DomainInfo::Slave;
1319 bbd.d_masters.push_back(ComboAddress(ip, 53));
1320 bbd.setCtime();
1321 safePutBBDomainInfo(bbd);
1322 return true;
1323 }
1324
1325 bool Bind2Backend::searchRecords(const string &pattern, int maxResults, vector<DNSResourceRecord>& result)
1326 {
1327 SimpleMatch sm(pattern,true);
1328 static bool mustlog=::arg().mustDo("query-logging");
1329 if(mustlog)
1330 g_log<<Logger::Warning<<"Search for pattern '"<<pattern<<"'"<<endl;
1331
1332 {
1333 ReadLock rl(&s_state_lock);
1334
1335 for(state_t::const_iterator i = s_state.begin(); i != s_state.end() ; ++i) {
1336 BB2DomainInfo h;
1337 if (!safeGetBBDomainInfo(i->d_id, &h)) {
1338 continue;
1339 }
1340
1341 shared_ptr<const recordstorage_t> rhandle = h.d_records.get();
1342
1343 for(recordstorage_t::const_iterator ri = rhandle->begin(); result.size() < static_cast<vector<DNSResourceRecord>::size_type>(maxResults) && ri != rhandle->end(); ri++) {
1344 DNSName name = ri->qname.empty() ? i->d_name : (ri->qname+i->d_name);
1345 if (sm.match(name) || sm.match(ri->content)) {
1346 DNSResourceRecord r;
1347 r.qname=name;
1348 r.domain_id=i->d_id;
1349 r.content=ri->content;
1350 r.qtype=ri->qtype;
1351 r.ttl=ri->ttl;
1352 r.auth = ri->auth;
1353 result.push_back(r);
1354 }
1355 }
1356 }
1357 }
1358
1359 return true;
1360 }
1361
1362 class Bind2Factory : public BackendFactory
1363 {
1364 public:
1365 Bind2Factory() : BackendFactory("bind") {}
1366
1367 void declareArguments(const string &suffix="")
1368 {
1369 declare(suffix,"ignore-broken-records","Ignore records that are out-of-bound for the zone.","no");
1370 declare(suffix,"config","Location of named.conf","");
1371 declare(suffix,"check-interval","Interval for zonefile changes","0");
1372 declare(suffix,"supermaster-config","Location of (part of) named.conf where pdns can write zone-statements to","");
1373 declare(suffix,"supermasters","List of IP-addresses of supermasters","");
1374 declare(suffix,"supermaster-destdir","Destination directory for newly added slave zones",::arg()["config-dir"]);
1375 declare(suffix,"dnssec-db","Filename to store & access our DNSSEC metadatabase, empty for none", "");
1376 declare(suffix,"dnssec-db-journal-mode","SQLite3 journal mode", "WAL");
1377 declare(suffix,"hybrid","Store DNSSEC metadata in other backend","no");
1378 }
1379
1380 DNSBackend *make(const string &suffix="")
1381 {
1382 assertEmptySuffix(suffix);
1383 return new Bind2Backend(suffix);
1384 }
1385
1386 DNSBackend *makeMetadataOnly(const string &suffix="")
1387 {
1388 assertEmptySuffix(suffix);
1389 return new Bind2Backend(suffix, false);
1390 }
1391 private:
1392 void assertEmptySuffix(const string &suffix)
1393 {
1394 if(suffix.length())
1395 throw PDNSException("launch= suffixes are not supported on the bindbackend");
1396 }
1397 };
1398
1399 //! Magic class that is activated when the dynamic library is loaded
1400 class Bind2Loader
1401 {
1402 public:
1403 Bind2Loader()
1404 {
1405 BackendMakers().report(new Bind2Factory);
1406 g_log << Logger::Info << "[bind2backend] This is the bind backend version " << VERSION
1407 #ifndef REPRODUCIBLE
1408 << " (" __DATE__ " " __TIME__ ")"
1409 #endif
1410 #ifdef HAVE_SQLITE3
1411 << " (with bind-dnssec-db support)"
1412 #endif
1413 << " reporting" << endl;
1414 }
1415 };
1416 static Bind2Loader bind2loader;