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