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