]>
git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/dnsbackend.cc
2 PowerDNS Versatile Database Driven Nameserver
3 Copyright (C) 2005 PowerDNS.COM BV
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
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.
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.
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
26 #include "dnsbackend.hh"
27 #include "arguments.hh"
28 #include "ueberbackend.hh"
31 #include <sys/types.h>
32 #include "pdns/packetcache.hh"
33 #include "dnspacket.hh"
36 bool DNSBackend::getAuth(DNSPacket
*p
, SOAData
*sd
, const DNSName
&target
, const int best_match_len
)
39 DNSName
subdomain(target
);
41 if( best_match_len
>= (int)subdomain
.toString().length() )
44 if( this->getSOA( subdomain
, *sd
, p
) ) {
45 sd
->qname
= subdomain
;
47 if(p
->qtype
.getCode() == QType::DS
&& subdomain
==target
) {
48 // Found authoritative zone but look for parent zone with 'DS' record.
54 while( subdomain
.chopOff() ); // 'www.powerdns.org' -> 'powerdns.org' -> 'org' -> ''
59 void DNSBackend::setArgPrefix(const string
&prefix
)
64 bool DNSBackend::mustDo(const string
&key
)
66 return arg().mustDo(d_prefix
+"-"+key
);
69 const string
&DNSBackend::getArg(const string
&key
)
71 return arg()[d_prefix
+"-"+key
];
74 int DNSBackend::getArgAsNum(const string
&key
)
76 return arg().asNum(d_prefix
+"-"+key
);
79 void BackendFactory::declare(const string
&suffix
, const string
¶m
, const string
&help
, const string
&value
)
81 string fullname
=d_name
+suffix
+"-"+param
;
82 arg().set(fullname
,help
)=value
;
85 const string
&BackendFactory::getName() const
90 BackendMakerClass
&BackendMakers()
92 static BackendMakerClass bmc
;
96 void BackendMakerClass::report(BackendFactory
*bf
)
98 d_repository
[bf
->getName()]=bf
;
102 vector
<string
> BackendMakerClass::getModules()
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
);
112 void BackendMakerClass::load_all()
114 // TODO: Implement this?
115 DIR *dir
=opendir(arg()["module-dir"].c_str());
117 L
<<Logger::Error
<<"Unable to open module directory '"<<arg()["module-dir"]<<"'"<<endl
;
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"))
130 void BackendMakerClass::load(const string
&module
)
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
);
139 res
=UeberBackend::loadmodule(arg()["module-dir"]+"/"+module
);
142 L
<<Logger::Error
<<"DNSBackend unable to load module in "<<module
<<endl
;
147 void BackendMakerClass::launch(const string
&instr
)
150 // throw ArgException("Not launching any backends - nameserver won't function");
152 vector
<string
> parts
;
153 stringtok(parts
,instr
,", ");
155 for(vector
<string
>::const_iterator i
=parts
.begin();i
!=parts
.end();++i
) {
156 const string
&part
=*i
;
159 vector
<string
>pparts
;
160 stringtok(pparts
,part
,": ");
165 if(d_repository
.find(module
)==d_repository
.end()) {
166 // this is *so* userfriendly
168 if(d_repository
.find(module
)==d_repository
.end())
169 throw ArgException("Trying to launch unknown backend '"+module
+"'");
171 d_repository
[module
]->declareArguments(name
);
172 d_instances
.push_back(make_pair(module
,name
));
176 int BackendMakerClass::numLauncheable()
178 return d_instances
.size();
181 vector
<DNSBackend
*>BackendMakerClass::all(bool metadataOnly
)
183 vector
<DNSBackend
*>ret
;
184 if(d_instances
.empty())
185 throw PDNSException("No database backends configured for launch, unable to function");
188 for(vector
<pair
<string
,string
> >::const_iterator i
=d_instances
.begin();i
!=d_instances
.end();++i
) {
191 made
= d_repository
[i
->first
]->makeMetadataOnly(i
->second
);
193 made
= d_repository
[i
->first
]->make(i
->second
);
195 throw PDNSException("Unable to launch backend '"+i
->first
+"'");
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
)
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
)
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
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.
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!
228 \param domain Domain we want to get the SOA details of
229 \param sd SOAData which is filled with the SOA details
231 bool DNSBackend::getSOA(const DNSName
&domain
, SOAData
&sd
, DNSPacket
*p
)
233 this->lookup(QType(QType::SOA
),domain
,p
);
235 DNSResourceRecord rr
;
240 while(this->get(rr
)) {
241 if (rr
.qtype
!= QType::SOA
) throw PDNSException("Got non-SOA record when asking for SOA");
243 fillSOAData(rr
.content
, sd
);
244 sd
.domain_id
=rr
.domain_id
;
246 sd
.scopeMask
= rr
.scopeMask
;
252 if(!sd
.nameserver
.countLabels())
253 sd
.nameserver
=arg()["default-soa-name"];
255 if(!sd
.hostmaster
.countLabels()) {
256 if (!arg().isEmpty("default-soa-mail")) {
257 sd
.hostmaster
=arg()["default-soa-mail"];
258 // attodot(sd.hostmaster); FIXME
261 sd
.hostmaster
="hostmaster."+domain
;
264 if(!sd
.serial
) { // magic time!
265 DLOG(L
<<Logger::Warning
<<"Doing SOA serial number autocalculation for "<<rr
.qname
.toString()<<endl
);
268 if (calculateSOASerial(domain
, sd
, serial
)) {
270 //DLOG(L<<"autocalculated soa serialnumber for "<<rr.qname<<" is "<<newest<<endl);
272 DLOG(L
<<"soa serialnumber calculation failed for "<<rr
.qname
.toString()<<endl
);
280 bool DNSBackend::getBeforeAndAfterNames(uint32_t id
, const DNSName
& zonename
, const DNSName
& qname
, DNSName
& before
, DNSName
& after
)
282 // string lcqname=toLower(qname); FIXME tolower?
283 // string lczonename=toLower(zonename); FIXME tolower?
284 // lcqname=makeRelative(lcqname, lczonename);
286 // lcqname=labelReverse(lcqname);
288 bool ret
= this->getBeforeAndAfterNamesAbsolute(id
, qname
, dnc
, before
, after
);
290 // before=dotConcat(labelReverse(before), lczonename); FIXME
291 // after=dotConcat(labelReverse(after), lczonename); FIXME
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
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
306 bool DNSBackend::calculateSOASerial(const DNSName
& domain
, const SOAData
& sd
, time_t& serial
)
308 // we do this by listing the domain and taking the maximum last modified timestamp
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
);
318 while(this->get(i
)) {
319 if(i
.last_modified
>newest
)
320 newest
=i
.last_modified
;
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.
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.
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
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
352 extern PacketCache PC
;
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
363 DLOG(L
<<Logger::Error
<<"Adding to neg qcache: " << zone
<<endl
);
364 PC
.insert(zone
, QType(QType::SOA
), PacketCache::QUERYCACHE
, "", negqueryttl
, 0);
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");
372 DLOG(L
<<Logger::Error
<<"SOA Query: " <<querykey
<<endl
);
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
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
;
385 /* Look up in the negative querycache to see if we have already tried and
386 * failed to look up this zone */
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
;
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
;
401 DLOG(L
<<Logger::Error
<<"Queried: " << querykey
<< " and found record: " <<foundkey
<<endl
);
403 // Got a match from a previous backend that was longer than this - no need
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
;
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
);
416 DLOG(L
<<Logger::Error
<<"Successfully got record: " <<foundkey
<< " : " << querykey
.substr( 0, foundkey
.length() ) << " : " << soa
->qname
.toString()<<endl
);
418 return GET_AUTH_SUCCESS
;
421 return GET_AUTH_NEG_CACHE
;
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
);
429 int ret
= _getAuth( p
, soa
, inZone
, querykey
, best_match_len
);
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
435 if( ret
== GET_AUTH_NEG_CACHE
)
436 _add_to_negcache( inZone
);
438 return ret
== GET_AUTH_SUCCESS
;
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
444 bool DNSReversedBackend::_getSOA(const string
&querykey
, SOAData
&soa
, DNSPacket
*p
)
446 string
searchkey( querykey
);
448 if( !getAuthZone( searchkey
) )
451 DLOG(L
<<Logger::Error
<<"search key " << searchkey
<< " query key " << querykey
<<endl
);
453 if( querykey
.compare( searchkey
) != 0 )
456 return getAuthData( soa
, p
);
459 bool DNSReversedBackend::getSOA(const string
&inZone
, SOAData
&soa
, DNSPacket
*p
)
461 // prepare the query string
462 string zone
= toLower( inZone
);
463 string querykey
= labelReverse( zone
);
465 if( !_getSOA( querykey
, soa
, p
) )