2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
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.
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.
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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
27 #include "dnsbackend.hh"
28 #include "arguments.hh"
29 #include "ueberbackend.hh"
32 #include <sys/types.h>
33 #include "packetcache.hh"
34 #include "auth-zonecache.hh"
35 #include "dnspacket.hh"
41 // this has to be somewhere central, and not in a file that requires Lua
42 // this is so the geoipbackend can set this pointer if loaded for lua-record.cc
43 std::function
<std::string(const std::string
&, int)> g_getGeo
;
45 bool DNSBackend::getAuth(const DNSName
& target
, SOAData
* soaData
)
47 return this->getSOA(target
, *soaData
);
50 void DNSBackend::setArgPrefix(const string
& prefix
)
55 bool DNSBackend::mustDo(const string
& key
)
57 return arg().mustDo(d_prefix
+ "-" + key
);
60 const string
& DNSBackend::getArg(const string
& key
)
62 return arg()[d_prefix
+ "-" + key
];
65 int DNSBackend::getArgAsNum(const string
& key
)
67 return arg().asNum(d_prefix
+ "-" + key
);
70 void BackendFactory::declare(const string
& suffix
, const string
& param
, const string
& explanation
, const string
& value
)
72 string fullname
= d_name
+ suffix
+ "-" + param
;
73 arg().set(fullname
, explanation
) = value
;
74 arg().setDefault(fullname
, value
);
77 const string
& BackendFactory::getName() const
82 BackendMakerClass
& BackendMakers()
84 static BackendMakerClass bmc
;
88 void BackendMakerClass::report(std::unique_ptr
<BackendFactory
>&& backendFactory
)
90 d_repository
[backendFactory
->getName()] = std::move(backendFactory
);
93 void BackendMakerClass::clear()
99 vector
<string
> BackendMakerClass::getModules()
103 // copy(d_repository.begin(), d_repository.end(),back_inserter(ret));
104 for (auto& repo
: d_repository
) {
105 ret
.push_back(repo
.first
);
110 void BackendMakerClass::load_all()
112 auto directoryError
= pdns::visit_directory(arg()["module-dir"], []([[maybe_unused
]] ino_t inodeNumber
, const std::string_view
& name
) {
113 if (boost::starts_with(name
, "lib") && name
.size() > 13 && boost::ends_with(name
, "backend.so")) {
114 load(std::string(name
));
118 if (directoryError
) {
119 g_log
<< Logger::Error
<< "Unable to open module directory '" << arg()["module-dir"] << "': " << *directoryError
<< endl
;
123 void BackendMakerClass::load(const string
& module
)
127 if (module
.find('.') == string::npos
) {
128 res
= UeberBackend::loadmodule(arg()["module-dir"] + "/lib" + module
+ "backend.so");
130 else if (module
[0] == '/' || (module
[0] == '.' && module
[1] == '/') || (module
[0] == '.' && module
[1] == '.')) { // absolute or current path
131 res
= UeberBackend::loadmodule(module
);
134 res
= UeberBackend::loadmodule(arg()["module-dir"] + "/" + module
);
138 g_log
<< Logger::Error
<< "DNSBackend unable to load module in " << module
<< endl
;
143 void BackendMakerClass::launch(const string
& instr
)
146 // throw ArgException("Not launching any backends - nameserver won't function");
148 vector
<string
> parts
;
149 stringtok(parts
, instr
, ", ");
151 for (const auto& part
: parts
) {
152 if (count(parts
.begin(), parts
.end(), part
) > 1) {
153 throw ArgException("Refusing to launch multiple backends with the same name '" + part
+ "', verify all 'launch' statements in your configuration");
157 for (const auto& part
: parts
) {
160 vector
<string
> pparts
;
161 stringtok(pparts
, part
, ": ");
163 if (pparts
.size() > 1) {
164 name
= "-" + pparts
[1];
167 if (d_repository
.find(module
) == d_repository
.end()) {
168 // this is *so* userfriendly
170 if (d_repository
.find(module
) == d_repository
.end()) {
171 throw ArgException("Trying to launch unknown backend '" + module
+ "'");
174 d_repository
[module
]->declareArguments(name
);
175 d_instances
.emplace_back(module
, name
);
179 size_t BackendMakerClass::numLauncheable() const
181 return d_instances
.size();
184 vector
<std::unique_ptr
<DNSBackend
>> BackendMakerClass::all(bool metadataOnly
)
186 if (d_instances
.empty()) {
187 throw PDNSException("No database backends configured for launch, unable to function");
190 vector
<unique_ptr
<DNSBackend
>> ret
;
191 ret
.reserve(d_instances
.size());
193 std::string current
; // to make the exception text more useful
196 for (const auto& instance
: d_instances
) {
197 current
= instance
.first
+ instance
.second
;
198 const auto& repo
= d_repository
[instance
.first
];
199 std::unique_ptr
<DNSBackend
> made
{metadataOnly
? repo
->makeMetadataOnly(instance
.second
) : repo
->make(instance
.second
)};
200 if (made
== nullptr) {
201 throw PDNSException("Unable to launch backend '" + instance
.first
+ "'");
203 ret
.push_back(std::move(made
));
206 catch (const PDNSException
& ae
) {
207 g_log
<< Logger::Error
<< "Caught an exception instantiating a backend (" << current
<< "): " << ae
.reason
<< endl
;
208 g_log
<< Logger::Error
<< "Cleaning up" << endl
;
214 g_log
<< Logger::Error
<< "Caught an exception instantiating a backend (" << current
<< "), cleaning up" << endl
;
222 /** getSOA() is a function that is called to get the SOA of a domain. Callers should ONLY
223 use getSOA() and not perform a lookup() themselves as backends may decide to special case
226 Returns false if there is definitely no SOA for the domain. May throw a DBException
227 to indicate that the backend is currently unable to supply an answer.
229 WARNING: This function *may* fill out the db attribute of the SOAData, but then again,
230 it may not! If you find a zero in there, you may have been handed a non-live and cached
231 answer, in which case you need to perform a getDomainInfo call!
233 \param domain Domain we want to get the SOA details of
234 \param sd SOAData which is filled with the SOA details
235 \param unmodifiedSerial bool if set, serial will be returned as stored in the backend (maybe 0)
237 bool DNSBackend::getSOA(const DNSName
& domain
, SOAData
& soaData
)
239 this->lookup(QType(QType::SOA
), domain
, -1);
240 S
.inc("backend-queries");
242 DNSResourceRecord resourceRecord
;
245 soaData
.db
= nullptr;
248 while (this->get(resourceRecord
)) {
249 if (resourceRecord
.qtype
!= QType::SOA
) {
250 throw PDNSException("Got non-SOA record when asking for SOA, zone: '" + domain
.toLogString() + "'");
253 soaData
.qname
= domain
;
254 soaData
.ttl
= resourceRecord
.ttl
;
256 soaData
.domain_id
= resourceRecord
.domain_id
;
257 fillSOAData(resourceRecord
.content
, soaData
);
261 while (this->get(resourceRecord
)) {
270 bool DNSBackend::get(DNSZoneRecord
& zoneRecord
)
272 // cout<<"DNSBackend::get(DNSZoneRecord&) called - translating into DNSResourceRecord query"<<endl;
273 DNSResourceRecord resourceRecord
;
274 if (!this->get(resourceRecord
)) {
277 zoneRecord
.auth
= resourceRecord
.auth
;
278 zoneRecord
.domain_id
= resourceRecord
.domain_id
;
279 zoneRecord
.scopeMask
= resourceRecord
.scopeMask
;
280 if (resourceRecord
.qtype
.getCode() == QType::TXT
&& !resourceRecord
.content
.empty() && resourceRecord
.content
[0] != '"') {
281 resourceRecord
.content
= "\"" + resourceRecord
.content
+ "\"";
284 zoneRecord
.dr
= DNSRecord(resourceRecord
);
287 while (this->get(resourceRecord
)) {
295 bool DNSBackend::getBeforeAndAfterNames(uint32_t id
, const DNSName
& zonename
, const DNSName
& qname
, DNSName
& before
, DNSName
& after
)
298 bool ret
= this->getBeforeAndAfterNamesAbsolute(id
, qname
.makeRelative(zonename
).makeLowerCase(), unhashed
, before
, after
);
299 DNSName lczonename
= zonename
.makeLowerCase();
300 before
+= lczonename
;
305 void DNSBackend::getAllDomains(vector
<DomainInfo
>* /* domains */, bool /* getSerial */, bool /* include_disabled */)
307 if (g_zoneCache
.isEnabled()) {
308 g_log
<< Logger::Error
<< "One of the backends does not support zone caching. Put zone-cache-refresh-interval=0 in the config file to disable this cache." << endl
;
313 void fillSOAData(const DNSZoneRecord
& inZoneRecord
, SOAData
& soaData
)
315 soaData
.domain_id
= inZoneRecord
.domain_id
;
316 soaData
.ttl
= inZoneRecord
.dr
.d_ttl
;
318 auto src
= getRR
<SOARecordContent
>(inZoneRecord
.dr
);
319 soaData
.nameserver
= src
->d_mname
;
320 soaData
.rname
= src
->d_rname
;
321 soaData
.serial
= src
->d_st
.serial
;
322 soaData
.refresh
= src
->d_st
.refresh
;
323 soaData
.retry
= src
->d_st
.retry
;
324 soaData
.expire
= src
->d_st
.expire
;
325 soaData
.minimum
= src
->d_st
.minimum
;
328 std::shared_ptr
<DNSRecordContent
> makeSOAContent(const SOAData
& soaData
)
330 struct soatimes soaTimes
332 .serial
= soaData
.serial
,
333 .refresh
= soaData
.refresh
,
334 .retry
= soaData
.retry
,
335 .expire
= soaData
.expire
,
336 .minimum
= soaData
.minimum
,
338 return std::make_shared
<SOARecordContent
>(soaData
.nameserver
, soaData
.rname
, soaTimes
);
341 void fillSOAData(const string
& content
, SOAData
& soaData
)
343 vector
<string
> parts
;
345 stringtok(parts
, content
);
348 soaData
.nameserver
= DNSName(parts
.at(0));
349 soaData
.rname
= DNSName(parts
.at(1));
350 pdns::checked_stoi_into(soaData
.serial
, parts
.at(2));
351 pdns::checked_stoi_into(soaData
.refresh
, parts
.at(3));
352 pdns::checked_stoi_into(soaData
.retry
, parts
.at(4));
353 pdns::checked_stoi_into(soaData
.expire
, parts
.at(5));
354 pdns::checked_stoi_into(soaData
.minimum
, parts
.at(6));
356 catch (const std::out_of_range
& oor
) {
357 throw PDNSException("Out of range exception parsing '" + content
+ "'");