]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/dnsbackend.cc
snap
[thirdparty/pdns.git] / pdns / dnsbackend.cc
1 /*
2 PowerDNS Versatile Database Driven Nameserver
3 Copyright (C) 2005 PowerDNS.COM BV
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
7 as published by the Free Software Foundation
8
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
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 St, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include "utility.hh"
26 #include "dnsbackend.hh"
27 #include "arguments.hh"
28 #include "ueberbackend.hh"
29 #include "logger.hh"
30
31 #include <sys/types.h>
32 #include "pdns/packetcache.hh"
33 #include "dnspacket.hh"
34 #include "dns.hh"
35
36 bool DNSBackend::getAuth(DNSPacket *p, SOAData *sd, const DNSName &target, const int best_match_len)
37 {
38 bool found=false;
39 DNSName subdomain(target);
40 do {
41 if( best_match_len >= (int)subdomain.toString().length() )
42 break;
43
44 if( this->getSOA( subdomain, *sd, p ) ) {
45 sd->qname = subdomain;
46
47 if(p->qtype.getCode() == QType::DS && subdomain==target) {
48 // Found authoritative zone but look for parent zone with 'DS' record.
49 found=true;
50 } else
51 return true;
52 }
53 }
54 while( subdomain.chopOff() ); // 'www.powerdns.org' -> 'powerdns.org' -> 'org' -> ''
55
56 return found;
57 }
58
59 void DNSBackend::setArgPrefix(const string &prefix)
60 {
61 d_prefix=prefix;
62 }
63
64 bool DNSBackend::mustDo(const string &key)
65 {
66 return arg().mustDo(d_prefix+"-"+key);
67 }
68
69 const string &DNSBackend::getArg(const string &key)
70 {
71 return arg()[d_prefix+"-"+key];
72 }
73
74 int DNSBackend::getArgAsNum(const string &key)
75 {
76 return arg().asNum(d_prefix+"-"+key);
77 }
78
79 void BackendFactory::declare(const string &suffix, const string &param, const string &help, const string &value)
80 {
81 string fullname=d_name+suffix+"-"+param;
82 arg().set(fullname,help)=value;
83 }
84
85 const string &BackendFactory::getName() const
86 {
87 return d_name;
88 }
89
90 BackendMakerClass &BackendMakers()
91 {
92 static BackendMakerClass bmc;
93 return bmc;
94 }
95
96 void BackendMakerClass::report(BackendFactory *bf)
97 {
98 d_repository[bf->getName()]=bf;
99 }
100
101
102 vector<string> BackendMakerClass::getModules()
103 {
104 load_all();
105 vector<string> ret;
106 // copy(d_repository.begin(), d_repository.end(),back_inserter(ret));
107 for(d_repository_t::const_iterator i=d_repository.begin();i!=d_repository.end();++i)
108 ret.push_back(i->first);
109 return ret;
110 }
111
112 void BackendMakerClass::load_all()
113 {
114 // TODO: Implement this?
115 DIR *dir=opendir(arg()["module-dir"].c_str());
116 if(!dir) {
117 L<<Logger::Error<<"Unable to open module directory '"<<arg()["module-dir"]<<"'"<<endl;
118 return;
119 }
120 struct dirent *entry;
121 while((entry=readdir(dir))) {
122 if(!strncmp(entry->d_name,"lib",3) &&
123 strlen(entry->d_name)>13 &&
124 !strcmp(entry->d_name+strlen(entry->d_name)-10,"backend.so"))
125 load(entry->d_name);
126 }
127 closedir(dir);
128 }
129
130 void BackendMakerClass::load(const string &module)
131 {
132 int res;
133
134 if(module.find(".")==string::npos)
135 res=UeberBackend::loadmodule(arg()["module-dir"]+"/lib"+module+"backend.so");
136 else if(module[0]=='/' || (module[0]=='.' && module[1]=='/') || (module[0]=='.' && module[1]=='.')) // absolute or current path
137 res=UeberBackend::loadmodule(module);
138 else
139 res=UeberBackend::loadmodule(arg()["module-dir"]+"/"+module);
140
141 if(res==false) {
142 L<<Logger::Error<<"DNSBackend unable to load module in "<<module<<endl;
143 exit(1);
144 }
145 }
146
147 void BackendMakerClass::launch(const string &instr)
148 {
149 // if(instr.empty())
150 // throw ArgException("Not launching any backends - nameserver won't function");
151
152 vector<string> parts;
153 stringtok(parts,instr,", ");
154
155 for(vector<string>::const_iterator i=parts.begin();i!=parts.end();++i) {
156 const string &part=*i;
157
158 string module, name;
159 vector<string>pparts;
160 stringtok(pparts,part,": ");
161 module=pparts[0];
162 if(pparts.size()>1)
163 name="-"+pparts[1];
164
165 if(d_repository.find(module)==d_repository.end()) {
166 // this is *so* userfriendly
167 load(module);
168 if(d_repository.find(module)==d_repository.end())
169 throw ArgException("Trying to launch unknown backend '"+module+"'");
170 }
171 d_repository[module]->declareArguments(name);
172 d_instances.push_back(make_pair(module,name));
173 }
174 }
175
176 int BackendMakerClass::numLauncheable()
177 {
178 return d_instances.size();
179 }
180
181 vector<DNSBackend *>BackendMakerClass::all(bool metadataOnly)
182 {
183 vector<DNSBackend *>ret;
184 if(d_instances.empty())
185 throw PDNSException("No database backends configured for launch, unable to function");
186
187 try {
188 for(vector<pair<string,string> >::const_iterator i=d_instances.begin();i!=d_instances.end();++i) {
189 DNSBackend *made;
190 if(metadataOnly)
191 made = d_repository[i->first]->makeMetadataOnly(i->second);
192 else
193 made = d_repository[i->first]->make(i->second);
194 if(!made)
195 throw PDNSException("Unable to launch backend '"+i->first+"'");
196
197 ret.push_back(made);
198 }
199 }
200 catch(PDNSException &ae) {
201 L<<Logger::Error<<"Caught an exception instantiating a backend: "<<ae.reason<<endl;
202 L<<Logger::Error<<"Cleaning up"<<endl;
203 for(vector<DNSBackend *>::const_iterator i=ret.begin();i!=ret.end();++i)
204 delete *i;
205 throw;
206 } catch(...) {
207 // and cleanup
208 L<<Logger::Error<<"Caught an exception instantiating a backend, cleaning up"<<endl;
209 for(vector<DNSBackend *>::const_iterator i=ret.begin();i!=ret.end();++i)
210 delete *i;
211 throw;
212 }
213
214 return ret;
215 }
216
217 /** getSOA() is a function that is called to get the SOA of a domain. Callers should ONLY
218 use getSOA() and not perform a lookup() themselves as backends may decide to special case
219 the SOA record.
220
221 Returns false if there is definitely no SOA for the domain. May throw a DBException
222 to indicate that the backend is currently unable to supply an answer.
223
224 WARNING: This function *may* fill out the db attribute of the SOAData, but then again,
225 it may not! If you find a zero in there, you may have been handed a non-live and cached
226 answer, in which case you need to perform a getDomainInfo call!
227
228 \param domain Domain we want to get the SOA details of
229 \param sd SOAData which is filled with the SOA details
230 */
231 bool DNSBackend::getSOA(const DNSName &domain, SOAData &sd, DNSPacket *p)
232 {
233 this->lookup(QType(QType::SOA),domain,p);
234
235 DNSResourceRecord rr;
236 rr.auth = true;
237
238 int hits=0;
239
240 while(this->get(rr)) {
241 if (rr.qtype != QType::SOA) throw PDNSException("Got non-SOA record when asking for SOA");
242 hits++;
243 fillSOAData(rr.content, sd);
244 sd.domain_id=rr.domain_id;
245 sd.ttl=rr.ttl;
246 sd.scopeMask = rr.scopeMask;
247 }
248
249 if(!hits)
250 return false;
251 sd.qname = domain;
252 if(!sd.nameserver.countLabels())
253 sd.nameserver=arg()["default-soa-name"];
254
255 if(!sd.hostmaster.countLabels()) {
256 if (!arg().isEmpty("default-soa-mail")) {
257 sd.hostmaster=arg()["default-soa-mail"];
258 // attodot(sd.hostmaster); FIXME
259 }
260 else
261 sd.hostmaster="hostmaster."+domain;
262 }
263
264 if(!sd.serial) { // magic time!
265 DLOG(L<<Logger::Warning<<"Doing SOA serial number autocalculation for "<<rr.qname.toString()<<endl);
266
267 time_t serial;
268 if (calculateSOASerial(domain, sd, serial)) {
269 sd.serial = serial;
270 //DLOG(L<<"autocalculated soa serialnumber for "<<rr.qname<<" is "<<newest<<endl);
271 } else {
272 DLOG(L<<"soa serialnumber calculation failed for "<<rr.qname.toString()<<endl);
273 }
274
275 }
276 sd.db=this;
277 return true;
278 }
279
280 bool DNSBackend::getBeforeAndAfterNames(uint32_t id, const DNSName& zonename, const DNSName& qname, DNSName& before, DNSName& after)
281 {
282 // string lcqname=toLower(qname); FIXME tolower?
283 // string lczonename=toLower(zonename); FIXME tolower?
284 // lcqname=makeRelative(lcqname, lczonename);
285
286 // lcqname=labelReverse(lcqname);
287 DNSName dnc;
288 bool ret = this->getBeforeAndAfterNamesAbsolute(id, qname, dnc, before, after);
289
290 // before=dotConcat(labelReverse(before), lczonename); FIXME
291 // after=dotConcat(labelReverse(after), lczonename); FIXME
292 return ret;
293 }
294
295 /**
296 * Calculates a SOA serial for the zone and stores it in the third
297 * argument. Returns false if calculation is not possible for some
298 * reason (in this case, the third argument is not inspected). If it
299 * returns true, the value returned in the third argument will be set
300 * as the SOA serial.
301 *
302 * \param domain The name of the domain
303 * \param sd Information about the SOA record already available
304 * \param serial Output parameter. Only inspected when we return true
305 */
306 bool DNSBackend::calculateSOASerial(const DNSName& domain, const SOAData& sd, time_t& serial)
307 {
308 // we do this by listing the domain and taking the maximum last modified timestamp
309
310 DNSResourceRecord i;
311 time_t newest=0;
312
313 if(!(this->list(domain, sd.domain_id))) {
314 DLOG(L<<Logger::Warning<<"Backend error trying to determine magic serial number of zone '"<<domain.toString()<<"'"<<endl);
315 return false;
316 }
317
318 while(this->get(i)) {
319 if(i.last_modified>newest)
320 newest=i.last_modified;
321 }
322
323 serial=newest;
324
325 return true;
326 }
327
328 /* This is a subclass of DNSBackend that, assuming you have your zones reversed
329 * and stored in an ordered fashion, will be able to look up SOA's much quicker
330 * than the DNSBackend code. The normal case for a SOA that exists is 1 backend
331 * query no matter how much the depth (although if there are sub-SOA's then
332 * this could require one or two more queries). The normal case for an SOA that
333 * does not exist is 2 or 3 queries depending on the system, although this will
334 * be reduced if the negative cache is active.
335 *
336 * The subclass MUST implement bool getAuthZone(string &reversed_zone_name)
337 * which, given a reversed zone name will return false if there was some sort
338 * of error (eg no record found as top of database was hit, lookup issues),
339 * otherwise returns true and sets reversed_zone_name to be the exact entry
340 * found, otherwise the entry directly preceding where it would be.
341 *
342 * The subclass MUST implement getAuthData( const string &rev_zone_name, SOAData *soa )
343 * which is basically the same as getSOA() but is called with the reversed zone name
344 */
345 enum {
346 GET_AUTH_NEG_DONTCACHE, // not found but don't cache this fact
347 GET_AUTH_NEG_CACHE, // not found and negcache this
348 GET_AUTH_SUCCESS, // entry found
349 };
350
351 #undef PC
352 extern PacketCache PC;
353
354 #if 0
355 #undef DLOG
356 #define DLOG(x) x
357 #endif
358
359 bool _add_to_negcache( const string &zone ) {
360 static int negqueryttl=::arg().asNum("negquery-cache-ttl");
361 // add the zone to the negative query cache and return false
362 if(negqueryttl) {
363 DLOG(L<<Logger::Error<<"Adding to neg qcache: " << zone<<endl);
364 PC.insert(zone, QType(QType::SOA), PacketCache::QUERYCACHE, "", negqueryttl, 0);
365 }
366 return false;
367 }
368
369 inline int DNSReversedBackend::_getAuth(DNSPacket *p, SOAData *soa, const string &inZone, const string &querykey, const int best_match_len) {
370 static int negqueryttl=::arg().asNum("negquery-cache-ttl");
371
372 DLOG(L<<Logger::Error<<"SOA Query: " <<querykey<<endl);
373
374 /* Got a match from a previous backend that was longer than this - no need
375 * to continue. This is something of an optimization as we would hit the
376 * similar test below in any cases that this was hit, although we would run
377 * the risk of something being added to the neg-querycache that may
378 * interfear with future queries
379 */
380 if( best_match_len >= (int)querykey.length() ) {
381 DLOG(L<<Logger::Error<<"Best match was better from a different client"<<endl);
382 return GET_AUTH_NEG_DONTCACHE;
383 }
384
385 /* Look up in the negative querycache to see if we have already tried and
386 * failed to look up this zone */
387 if( negqueryttl ) {
388 string content;
389 bool ret = PC.getEntry( inZone, QType(QType::SOA), PacketCache::QUERYCACHE, content, 0 );
390 if( ret && content.empty() ) {
391 DLOG(L<<Logger::Error<<"Found in neg qcache: " << inZone << ":" << content << ":" << ret << ":"<<endl);
392 return GET_AUTH_NEG_DONTCACHE;
393 }
394 }
395
396 /* Find the SOA entry on- or before- the position that we want in the b-tree */
397 string foundkey = querykey;
398 if( !getAuthZone( foundkey ) )
399 return GET_AUTH_NEG_CACHE;
400
401 DLOG(L<<Logger::Error<<"Queried: " << querykey << " and found record: " <<foundkey<<endl);
402
403 // Got a match from a previous backend that was longer than this - no need
404 // to continue.
405 if( best_match_len && best_match_len >= (int) foundkey.length() ) {
406 DLOG(L<<Logger::Error<<"Best match was better from a different client"<<endl);
407 return GET_AUTH_NEG_DONTCACHE;
408 }
409
410 // Found record successfully now, fill in the data.
411 if( getAuthData( *soa, p ) ) {
412 /* all the keys are reversed. rather than reversing them again it is
413 * presumably quicker to just substring the zone down to size */
414 soa->qname = inZone.substr( inZone.length() - foundkey.length(), string::npos );
415
416 DLOG(L<<Logger::Error<<"Successfully got record: " <<foundkey << " : " << querykey.substr( 0, foundkey.length() ) << " : " << soa->qname.toString()<<endl);
417
418 return GET_AUTH_SUCCESS;
419 }
420
421 return GET_AUTH_NEG_CACHE;
422 }
423
424 bool DNSReversedBackend::getAuth(DNSPacket *p, SOAData *soa, const string &inZone, const int best_match_len) {
425 // Reverse the lowercased query string
426 string zone = toLower(inZone);
427 string querykey = labelReverse(zone);
428
429 int ret = _getAuth( p, soa, inZone, querykey, best_match_len );
430
431 /* If this is disabled then we would just cache the tree structure not the
432 * leaves which should give the best performance and a nice small negcache
433 * size
434 */
435 if( ret == GET_AUTH_NEG_CACHE )
436 _add_to_negcache( inZone );
437
438 return ret == GET_AUTH_SUCCESS;
439 }
440
441 /* getAuthData() is very similar to getSOA() so implement a default getSOA
442 * based on that. This will only be called very occasionally for example during
443 * an AXFR */
444 bool DNSReversedBackend::_getSOA(const string &querykey, SOAData &soa, DNSPacket *p)
445 {
446 string searchkey( querykey );
447
448 if( !getAuthZone( searchkey ) )
449 return false;
450
451 DLOG(L<<Logger::Error<<"search key " << searchkey << " query key " << querykey<<endl);
452
453 if( querykey.compare( searchkey ) != 0 )
454 return false;
455
456 return getAuthData( soa, p );
457 }
458
459 bool DNSReversedBackend::getSOA(const string &inZone, SOAData &soa, DNSPacket *p)
460 {
461 // prepare the query string
462 string zone = toLower( inZone );
463 string querykey = labelReverse( zone );
464
465 if( !_getSOA( querykey, soa, p ) )
466 return false;
467
468 soa.qname = inZone;
469 return true;
470 }