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