]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/dnsbackend.cc
Merge pull request #11431 from jroessler-ox/docs-kskzskroll-update
[thirdparty/pdns.git] / pdns / dnsbackend.cc
1 /*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22 #include <memory>
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 #include "utility.hh"
27 #include "dnsbackend.hh"
28 #include "arguments.hh"
29 #include "ueberbackend.hh"
30 #include "logger.hh"
31
32 #include <sys/types.h>
33 #include "packetcache.hh"
34 #include "auth-zonecache.hh"
35 #include "dnspacket.hh"
36 #include "dns.hh"
37 #include "statbag.hh"
38
39 extern StatBag S;
40
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;
44
45 bool DNSBackend::getAuth(const DNSName& target, SOAData* soaData)
46 {
47 return this->getSOA(target, *soaData);
48 }
49
50 void DNSBackend::setArgPrefix(const string& prefix)
51 {
52 d_prefix = prefix;
53 }
54
55 bool DNSBackend::mustDo(const string& key)
56 {
57 return arg().mustDo(d_prefix + "-" + key);
58 }
59
60 const string& DNSBackend::getArg(const string& key)
61 {
62 return arg()[d_prefix + "-" + key];
63 }
64
65 int DNSBackend::getArgAsNum(const string& key)
66 {
67 return arg().asNum(d_prefix + "-" + key);
68 }
69
70 void BackendFactory::declare(const string& suffix, const string& param, const string& explanation, const string& value)
71 {
72 string fullname = d_name + suffix + "-" + param;
73 arg().set(fullname, explanation) = value;
74 arg().setDefault(fullname, value);
75 }
76
77 const string& BackendFactory::getName() const
78 {
79 return d_name;
80 }
81
82 BackendMakerClass& BackendMakers()
83 {
84 static BackendMakerClass bmc;
85 return bmc;
86 }
87
88 void BackendMakerClass::report(BackendFactory* backendFactory)
89 {
90 d_repository[backendFactory->getName()] = backendFactory;
91 }
92
93 void BackendMakerClass::clear()
94 {
95 d_instances.clear();
96 for (auto& repo : d_repository) {
97 delete repo.second;
98 repo.second = nullptr;
99 }
100 d_repository.clear();
101 }
102
103 vector<string> BackendMakerClass::getModules()
104 {
105 load_all();
106 vector<string> ret;
107 // copy(d_repository.begin(), d_repository.end(),back_inserter(ret));
108 for (auto& repo : d_repository) {
109 ret.push_back(repo.first);
110 }
111 return ret;
112 }
113
114 void BackendMakerClass::load_all()
115 {
116 auto directoryError = pdns::visit_directory(arg()["module-dir"], []([[maybe_unused]] ino_t inodeNumber, const std::string_view& name) {
117 if (boost::starts_with(name, "lib") && name.size() > 13 && boost::ends_with(name, "backend.so")) {
118 load(std::string(name));
119 }
120 return true;
121 });
122 if (directoryError) {
123 g_log << Logger::Error << "Unable to open module directory '" << arg()["module-dir"] << "': " << *directoryError << endl;
124 }
125 }
126
127 void BackendMakerClass::load(const string& module)
128 {
129 bool res = false;
130
131 if (module.find('.') == string::npos) {
132 res = UeberBackend::loadmodule(arg()["module-dir"] + "/lib" + module + "backend.so");
133 }
134 else if (module[0] == '/' || (module[0] == '.' && module[1] == '/') || (module[0] == '.' && module[1] == '.')) { // absolute or current path
135 res = UeberBackend::loadmodule(module);
136 }
137 else {
138 res = UeberBackend::loadmodule(arg()["module-dir"] + "/" + module);
139 }
140
141 if (!res) {
142 g_log << 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 (const auto& part : parts) {
156 if (count(parts.begin(), parts.end(), part) > 1) {
157 throw ArgException("Refusing to launch multiple backends with the same name '" + part + "', verify all 'launch' statements in your configuration");
158 }
159 }
160
161 for (const auto& part : parts) {
162 string module;
163 string name;
164 vector<string> pparts;
165 stringtok(pparts, part, ": ");
166 module = pparts[0];
167 if (pparts.size() > 1) {
168 name = "-" + pparts[1];
169 }
170
171 if (d_repository.find(module) == d_repository.end()) {
172 // this is *so* userfriendly
173 load(module);
174 if (d_repository.find(module) == d_repository.end()) {
175 throw ArgException("Trying to launch unknown backend '" + module + "'");
176 }
177 }
178 d_repository[module]->declareArguments(name);
179 d_instances.emplace_back(module, name);
180 }
181 }
182
183 size_t BackendMakerClass::numLauncheable() const
184 {
185 return d_instances.size();
186 }
187
188 vector<std::unique_ptr<DNSBackend>> BackendMakerClass::all(bool metadataOnly)
189 {
190 if (d_instances.empty()) {
191 throw PDNSException("No database backends configured for launch, unable to function");
192 }
193
194 vector<unique_ptr<DNSBackend>> ret;
195 ret.reserve(d_instances.size());
196
197 std::string current; // to make the exception text more useful
198
199 try {
200 for (const auto& instance : d_instances) {
201 current = instance.first + instance.second;
202 auto* repo = d_repository[instance.first];
203 std::unique_ptr<DNSBackend> made{metadataOnly ? repo->makeMetadataOnly(instance.second) : repo->make(instance.second)};
204 if (made == nullptr) {
205 throw PDNSException("Unable to launch backend '" + instance.first + "'");
206 }
207 ret.push_back(std::move(made));
208 }
209 }
210 catch (const PDNSException& ae) {
211 g_log << Logger::Error << "Caught an exception instantiating a backend (" << current << "): " << ae.reason << endl;
212 g_log << Logger::Error << "Cleaning up" << endl;
213 ret.clear();
214 throw;
215 }
216 catch (...) {
217 // and cleanup
218 g_log << Logger::Error << "Caught an exception instantiating a backend (" << current << "), cleaning up" << endl;
219 ret.clear();
220 throw;
221 }
222
223 return ret;
224 }
225
226 /** getSOA() is a function that is called to get the SOA of a domain. Callers should ONLY
227 use getSOA() and not perform a lookup() themselves as backends may decide to special case
228 the SOA record.
229
230 Returns false if there is definitely no SOA for the domain. May throw a DBException
231 to indicate that the backend is currently unable to supply an answer.
232
233 WARNING: This function *may* fill out the db attribute of the SOAData, but then again,
234 it may not! If you find a zero in there, you may have been handed a non-live and cached
235 answer, in which case you need to perform a getDomainInfo call!
236
237 \param domain Domain we want to get the SOA details of
238 \param sd SOAData which is filled with the SOA details
239 \param unmodifiedSerial bool if set, serial will be returned as stored in the backend (maybe 0)
240 */
241 bool DNSBackend::getSOA(const DNSName& domain, SOAData& soaData)
242 {
243 this->lookup(QType(QType::SOA), domain, -1);
244 S.inc("backend-queries");
245
246 DNSResourceRecord resourceRecord;
247 int hits = 0;
248
249 soaData.db = nullptr;
250
251 try {
252 while (this->get(resourceRecord)) {
253 if (resourceRecord.qtype != QType::SOA) {
254 throw PDNSException("Got non-SOA record when asking for SOA, zone: '" + domain.toLogString() + "'");
255 }
256 hits++;
257 soaData.qname = domain;
258 soaData.ttl = resourceRecord.ttl;
259 soaData.db = this;
260 soaData.domain_id = resourceRecord.domain_id;
261 fillSOAData(resourceRecord.content, soaData);
262 }
263 }
264 catch (...) {
265 while (this->get(resourceRecord)) {
266 ;
267 }
268 throw;
269 }
270
271 return hits != 0;
272 }
273
274 bool DNSBackend::get(DNSZoneRecord& zoneRecord)
275 {
276 // cout<<"DNSBackend::get(DNSZoneRecord&) called - translating into DNSResourceRecord query"<<endl;
277 DNSResourceRecord resourceRecord;
278 if (!this->get(resourceRecord)) {
279 return false;
280 }
281 zoneRecord.auth = resourceRecord.auth;
282 zoneRecord.domain_id = resourceRecord.domain_id;
283 zoneRecord.scopeMask = resourceRecord.scopeMask;
284 if (resourceRecord.qtype.getCode() == QType::TXT && !resourceRecord.content.empty() && resourceRecord.content[0] != '"') {
285 resourceRecord.content = "\"" + resourceRecord.content + "\"";
286 }
287 try {
288 zoneRecord.dr = DNSRecord(resourceRecord);
289 }
290 catch (...) {
291 while (this->get(resourceRecord)) {
292 ;
293 }
294 throw;
295 }
296 return true;
297 }
298
299 bool DNSBackend::getBeforeAndAfterNames(uint32_t id, const DNSName& zonename, const DNSName& qname, DNSName& before, DNSName& after)
300 {
301 DNSName unhashed;
302 bool ret = this->getBeforeAndAfterNamesAbsolute(id, qname.makeRelative(zonename).makeLowerCase(), unhashed, before, after);
303 DNSName lczonename = zonename.makeLowerCase();
304 before += lczonename;
305 after += lczonename;
306 return ret;
307 }
308
309 void DNSBackend::getAllDomains(vector<DomainInfo>* /* domains */, bool /* getSerial */, bool /* include_disabled */)
310 {
311 if (g_zoneCache.isEnabled()) {
312 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 exit(1);
314 }
315 }
316
317 void fillSOAData(const DNSZoneRecord& inZoneRecord, SOAData& soaData)
318 {
319 soaData.domain_id = inZoneRecord.domain_id;
320 soaData.ttl = inZoneRecord.dr.d_ttl;
321
322 auto src = getRR<SOARecordContent>(inZoneRecord.dr);
323 soaData.nameserver = src->d_mname;
324 soaData.rname = src->d_rname;
325 soaData.serial = src->d_st.serial;
326 soaData.refresh = src->d_st.refresh;
327 soaData.retry = src->d_st.retry;
328 soaData.expire = src->d_st.expire;
329 soaData.minimum = src->d_st.minimum;
330 }
331
332 std::shared_ptr<DNSRecordContent> makeSOAContent(const SOAData& soaData)
333 {
334 struct soatimes soaTimes
335 {
336 .serial = soaData.serial,
337 .refresh = soaData.refresh,
338 .retry = soaData.retry,
339 .expire = soaData.expire,
340 .minimum = soaData.minimum,
341 };
342 return std::make_shared<SOARecordContent>(soaData.nameserver, soaData.rname, soaTimes);
343 }
344
345 void fillSOAData(const string& content, SOAData& soaData)
346 {
347 vector<string> parts;
348 parts.reserve(7);
349 stringtok(parts, content);
350
351 try {
352 soaData.nameserver = DNSName(parts.at(0));
353 soaData.rname = DNSName(parts.at(1));
354 pdns::checked_stoi_into(soaData.serial, parts.at(2));
355 pdns::checked_stoi_into(soaData.refresh, parts.at(3));
356 pdns::checked_stoi_into(soaData.retry, parts.at(4));
357 pdns::checked_stoi_into(soaData.expire, parts.at(5));
358 pdns::checked_stoi_into(soaData.minimum, parts.at(6));
359 }
360 catch (const std::out_of_range& oor) {
361 throw PDNSException("Out of range exception parsing '" + content + "'");
362 }
363 }