]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/dnsbackend.cc
Merge pull request #14032 from rgacogne/ddist-192-changelog-secpoll
[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(std::unique_ptr<BackendFactory>&& backendFactory)
89 {
90 d_repository[backendFactory->getName()] = std::move(backendFactory);
91 }
92
93 void BackendMakerClass::clear()
94 {
95 d_instances.clear();
96 d_repository.clear();
97 }
98
99 vector<string> BackendMakerClass::getModules()
100 {
101 load_all();
102 vector<string> ret;
103 // copy(d_repository.begin(), d_repository.end(),back_inserter(ret));
104 for (auto& repo : d_repository) {
105 ret.push_back(repo.first);
106 }
107 return ret;
108 }
109
110 void BackendMakerClass::load_all()
111 {
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));
115 }
116 return true;
117 });
118 if (directoryError) {
119 g_log << Logger::Error << "Unable to open module directory '" << arg()["module-dir"] << "': " << *directoryError << endl;
120 }
121 }
122
123 void BackendMakerClass::load(const string& module)
124 {
125 bool res = false;
126
127 if (module.find('.') == string::npos) {
128 res = UeberBackend::loadmodule(arg()["module-dir"] + "/lib" + module + "backend.so");
129 }
130 else if (module[0] == '/' || (module[0] == '.' && module[1] == '/') || (module[0] == '.' && module[1] == '.')) { // absolute or current path
131 res = UeberBackend::loadmodule(module);
132 }
133 else {
134 res = UeberBackend::loadmodule(arg()["module-dir"] + "/" + module);
135 }
136
137 if (!res) {
138 g_log << Logger::Error << "DNSBackend unable to load module in " << module << endl;
139 exit(1);
140 }
141 }
142
143 void BackendMakerClass::launch(const string& instr)
144 {
145 // if(instr.empty())
146 // throw ArgException("Not launching any backends - nameserver won't function");
147
148 vector<string> parts;
149 stringtok(parts, instr, ", ");
150
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");
154 }
155 }
156
157 for (const auto& part : parts) {
158 string module;
159 string name;
160 vector<string> pparts;
161 stringtok(pparts, part, ": ");
162 module = pparts[0];
163 if (pparts.size() > 1) {
164 name = "-" + pparts[1];
165 }
166
167 if (d_repository.find(module) == d_repository.end()) {
168 // this is *so* userfriendly
169 load(module);
170 if (d_repository.find(module) == d_repository.end()) {
171 throw ArgException("Trying to launch unknown backend '" + module + "'");
172 }
173 }
174 d_repository[module]->declareArguments(name);
175 d_instances.emplace_back(module, name);
176 }
177 }
178
179 size_t BackendMakerClass::numLauncheable() const
180 {
181 return d_instances.size();
182 }
183
184 vector<std::unique_ptr<DNSBackend>> BackendMakerClass::all(bool metadataOnly)
185 {
186 if (d_instances.empty()) {
187 throw PDNSException("No database backends configured for launch, unable to function");
188 }
189
190 vector<unique_ptr<DNSBackend>> ret;
191 ret.reserve(d_instances.size());
192
193 std::string current; // to make the exception text more useful
194
195 try {
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 + "'");
202 }
203 ret.push_back(std::move(made));
204 }
205 }
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;
209 ret.clear();
210 throw;
211 }
212 catch (...) {
213 // and cleanup
214 g_log << Logger::Error << "Caught an exception instantiating a backend (" << current << "), cleaning up" << endl;
215 ret.clear();
216 throw;
217 }
218
219 return ret;
220 }
221
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
224 the SOA record.
225
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.
228
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!
232
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)
236 */
237 bool DNSBackend::getSOA(const DNSName& domain, SOAData& soaData)
238 {
239 this->lookup(QType(QType::SOA), domain, -1);
240 S.inc("backend-queries");
241
242 DNSResourceRecord resourceRecord;
243 int hits = 0;
244
245 soaData.db = nullptr;
246
247 try {
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() + "'");
251 }
252 hits++;
253 soaData.qname = domain;
254 soaData.ttl = resourceRecord.ttl;
255 soaData.db = this;
256 soaData.domain_id = resourceRecord.domain_id;
257 fillSOAData(resourceRecord.content, soaData);
258 }
259 }
260 catch (...) {
261 while (this->get(resourceRecord)) {
262 ;
263 }
264 throw;
265 }
266
267 return hits != 0;
268 }
269
270 bool DNSBackend::get(DNSZoneRecord& zoneRecord)
271 {
272 // cout<<"DNSBackend::get(DNSZoneRecord&) called - translating into DNSResourceRecord query"<<endl;
273 DNSResourceRecord resourceRecord;
274 if (!this->get(resourceRecord)) {
275 return false;
276 }
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 + "\"";
282 }
283 try {
284 zoneRecord.dr = DNSRecord(resourceRecord);
285 }
286 catch (...) {
287 while (this->get(resourceRecord)) {
288 ;
289 }
290 throw;
291 }
292 return true;
293 }
294
295 bool DNSBackend::getBeforeAndAfterNames(uint32_t id, const DNSName& zonename, const DNSName& qname, DNSName& before, DNSName& after)
296 {
297 DNSName unhashed;
298 bool ret = this->getBeforeAndAfterNamesAbsolute(id, qname.makeRelative(zonename).makeLowerCase(), unhashed, before, after);
299 DNSName lczonename = zonename.makeLowerCase();
300 before += lczonename;
301 after += lczonename;
302 return ret;
303 }
304
305 void DNSBackend::getAllDomains(vector<DomainInfo>* /* domains */, bool /* getSerial */, bool /* include_disabled */)
306 {
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;
309 exit(1);
310 }
311 }
312
313 void fillSOAData(const DNSZoneRecord& inZoneRecord, SOAData& soaData)
314 {
315 soaData.domain_id = inZoneRecord.domain_id;
316 soaData.ttl = inZoneRecord.dr.d_ttl;
317
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;
326 }
327
328 std::shared_ptr<DNSRecordContent> makeSOAContent(const SOAData& soaData)
329 {
330 struct soatimes soaTimes
331 {
332 .serial = soaData.serial,
333 .refresh = soaData.refresh,
334 .retry = soaData.retry,
335 .expire = soaData.expire,
336 .minimum = soaData.minimum,
337 };
338 return std::make_shared<SOARecordContent>(soaData.nameserver, soaData.rname, soaTimes);
339 }
340
341 void fillSOAData(const string& content, SOAData& soaData)
342 {
343 vector<string> parts;
344 parts.reserve(7);
345 stringtok(parts, content);
346
347 try {
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));
355 }
356 catch (const std::out_of_range& oor) {
357 throw PDNSException("Out of range exception parsing '" + content + "'");
358 }
359 }