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