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