]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/dnsbackend.cc
Merge pull request #5993 from zeha/ecs-add-for
[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 #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(const DNSName &target, SOAData *sd)
37 {
38 return this->getSOA(target, *sd);
39 }
40
41 void DNSBackend::setArgPrefix(const string &prefix)
42 {
43 d_prefix=prefix;
44 }
45
46 bool DNSBackend::mustDo(const string &key)
47 {
48 return arg().mustDo(d_prefix+"-"+key);
49 }
50
51 const string &DNSBackend::getArg(const string &key)
52 {
53 return arg()[d_prefix+"-"+key];
54 }
55
56 int DNSBackend::getArgAsNum(const string &key)
57 {
58 return arg().asNum(d_prefix+"-"+key);
59 }
60
61 void BackendFactory::declare(const string &suffix, const string &param, const string &help, const string &value)
62 {
63 string fullname=d_name+suffix+"-"+param;
64 arg().set(fullname,help)=value;
65 }
66
67 const string &BackendFactory::getName() const
68 {
69 return d_name;
70 }
71
72 BackendMakerClass &BackendMakers()
73 {
74 static BackendMakerClass bmc;
75 return bmc;
76 }
77
78 void BackendMakerClass::report(BackendFactory *bf)
79 {
80 d_repository[bf->getName()]=bf;
81 }
82
83
84 vector<string> BackendMakerClass::getModules()
85 {
86 load_all();
87 vector<string> ret;
88 // copy(d_repository.begin(), d_repository.end(),back_inserter(ret));
89 for(d_repository_t::const_iterator i=d_repository.begin();i!=d_repository.end();++i)
90 ret.push_back(i->first);
91 return ret;
92 }
93
94 void BackendMakerClass::load_all()
95 {
96 // TODO: Implement this?
97 DIR *dir=opendir(arg()["module-dir"].c_str());
98 if(!dir) {
99 L<<Logger::Error<<"Unable to open module directory '"<<arg()["module-dir"]<<"'"<<endl;
100 return;
101 }
102 struct dirent *entry;
103 while((entry=readdir(dir))) {
104 if(!strncmp(entry->d_name,"lib",3) &&
105 strlen(entry->d_name)>13 &&
106 !strcmp(entry->d_name+strlen(entry->d_name)-10,"backend.so"))
107 load(entry->d_name);
108 }
109 closedir(dir);
110 }
111
112 void BackendMakerClass::load(const string &module)
113 {
114 bool res;
115
116 if(module.find(".")==string::npos)
117 res=UeberBackend::loadmodule(arg()["module-dir"]+"/lib"+module+"backend.so");
118 else if(module[0]=='/' || (module[0]=='.' && module[1]=='/') || (module[0]=='.' && module[1]=='.')) // absolute or current path
119 res=UeberBackend::loadmodule(module);
120 else
121 res=UeberBackend::loadmodule(arg()["module-dir"]+"/"+module);
122
123 if(res==false) {
124 L<<Logger::Error<<"DNSBackend unable to load module in "<<module<<endl;
125 exit(1);
126 }
127 }
128
129 void BackendMakerClass::launch(const string &instr)
130 {
131 // if(instr.empty())
132 // throw ArgException("Not launching any backends - nameserver won't function");
133
134 vector<string> parts;
135 stringtok(parts,instr,", ");
136
137 for (const auto part : parts)
138 if (count(parts.begin(), parts.end(), part) > 1)
139 throw ArgException("Refusing to launch multiple backends with the same name '" + part + "', verify all 'launch' statements in your configuration");
140
141 for(vector<string>::const_iterator i=parts.begin();i!=parts.end();++i) {
142 const string &part=*i;
143
144 string module, name;
145 vector<string>pparts;
146 stringtok(pparts,part,": ");
147 module=pparts[0];
148 if(pparts.size()>1)
149 name="-"+pparts[1];
150
151 if(d_repository.find(module)==d_repository.end()) {
152 // this is *so* userfriendly
153 load(module);
154 if(d_repository.find(module)==d_repository.end())
155 throw ArgException("Trying to launch unknown backend '"+module+"'");
156 }
157 d_repository[module]->declareArguments(name);
158 d_instances.push_back(make_pair(module,name));
159 }
160 }
161
162 int BackendMakerClass::numLauncheable()
163 {
164 return d_instances.size();
165 }
166
167 vector<DNSBackend *>BackendMakerClass::all(bool metadataOnly)
168 {
169 vector<DNSBackend *>ret;
170 if(d_instances.empty())
171 throw PDNSException("No database backends configured for launch, unable to function");
172
173 try {
174 for(vector<pair<string,string> >::const_iterator i=d_instances.begin();i!=d_instances.end();++i) {
175 DNSBackend *made;
176 if(metadataOnly)
177 made = d_repository[i->first]->makeMetadataOnly(i->second);
178 else
179 made = d_repository[i->first]->make(i->second);
180 if(!made)
181 throw PDNSException("Unable to launch backend '"+i->first+"'");
182
183 ret.push_back(made);
184 }
185 }
186 catch(PDNSException &ae) {
187 L<<Logger::Error<<"Caught an exception instantiating a backend: "<<ae.reason<<endl;
188 L<<Logger::Error<<"Cleaning up"<<endl;
189 for(vector<DNSBackend *>::const_iterator i=ret.begin();i!=ret.end();++i)
190 delete *i;
191 throw;
192 } catch(...) {
193 // and cleanup
194 L<<Logger::Error<<"Caught an exception instantiating a backend, cleaning up"<<endl;
195 for(vector<DNSBackend *>::const_iterator i=ret.begin();i!=ret.end();++i)
196 delete *i;
197 throw;
198 }
199
200 return ret;
201 }
202
203 /** getSOA() is a function that is called to get the SOA of a domain. Callers should ONLY
204 use getSOA() and not perform a lookup() themselves as backends may decide to special case
205 the SOA record.
206
207 Returns false if there is definitely no SOA for the domain. May throw a DBException
208 to indicate that the backend is currently unable to supply an answer.
209
210 WARNING: This function *may* fill out the db attribute of the SOAData, but then again,
211 it may not! If you find a zero in there, you may have been handed a non-live and cached
212 answer, in which case you need to perform a getDomainInfo call!
213
214 \param domain Domain we want to get the SOA details of
215 \param sd SOAData which is filled with the SOA details
216 \param unmodifiedSerial bool if set, serial will be returned as stored in the backend (maybe 0)
217 */
218 bool DNSBackend::getSOA(const DNSName &domain, SOAData &sd, bool unmodifiedSerial)
219 {
220 this->lookup(QType(QType::SOA),domain);
221
222 DNSResourceRecord rr;
223 rr.auth = true;
224
225 int hits=0;
226
227 while(this->get(rr)) {
228 if (rr.qtype != QType::SOA) throw PDNSException("Got non-SOA record when asking for SOA");
229 hits++;
230 fillSOAData(rr.content, sd);
231 sd.domain_id=rr.domain_id;
232 sd.ttl=rr.ttl;
233 sd.scopeMask = rr.scopeMask;
234 }
235
236 if(!hits)
237 return false;
238 sd.qname = domain;
239 if(!sd.nameserver.countLabels())
240 sd.nameserver= DNSName(arg()["default-soa-name"]);
241
242 if(!sd.hostmaster.countLabels()) {
243 if (!arg().isEmpty("default-soa-mail")) {
244 sd.hostmaster= DNSName(arg()["default-soa-mail"]);
245 // attodot(sd.hostmaster); FIXME400
246 }
247 else
248 sd.hostmaster=DNSName("hostmaster")+domain;
249 }
250
251 if(!unmodifiedSerial && !sd.serial) { // magic time!
252 DLOG(L<<Logger::Warning<<"Doing SOA serial number autocalculation for "<<rr.qname<<endl);
253
254 time_t serial;
255 if (calculateSOASerial(domain, sd, serial)) {
256 sd.serial = serial;
257 //DLOG(L<<"autocalculated soa serialnumber for "<<rr.qname<<" is "<<newest<<endl);
258 } else {
259 DLOG(L<<"soa serialnumber calculation failed for "<<rr.qname<<endl);
260 }
261
262 }
263 sd.db=this;
264 return true;
265 }
266
267 bool DNSBackend::get(DNSZoneRecord& dzr)
268 {
269 // cout<<"DNSBackend::get(DNSZoneRecord&) called - translating into DNSResourceRecord query"<<endl;
270 DNSResourceRecord rr;
271 if(!this->get(rr))
272 return false;
273 dzr.auth = rr.auth;
274 dzr.domain_id = rr.domain_id;
275 dzr.scopeMask = rr.scopeMask;
276 if(rr.qtype.getCode() == QType::TXT && !rr.content.empty() && rr.content[0]!='"')
277 rr.content = "\""+ rr.content + "\"";
278 if(rr.qtype.getCode() == QType::SOA) {
279 try {
280 dzr.dr = DNSRecord(rr);
281 } catch(...) {
282 vector<string> parts;
283 stringtok(parts, rr.content, " \t");
284 if(parts.size() < 1)
285 rr.content = arg()["default-soa-name"];
286 if(parts.size() < 2)
287 rr.content += " " +arg()["default-soa-mail"];
288 if(parts.size() < 3)
289 rr.content += " 0";
290 if(parts.size() < 4)
291 rr.content += " " + ::arg()["soa-refresh-default"];
292 if(parts.size() < 5)
293 rr.content += " " + ::arg()["soa-retry-default"];
294 if(parts.size() < 6)
295 rr.content += " " + ::arg()["soa-expire-default"];
296 if(parts.size() < 7)
297 rr.content += " " + ::arg()["soa-minimum-ttl"];
298 dzr.dr = DNSRecord(rr);
299 }
300 }
301 else {
302 try {
303 dzr.dr = DNSRecord(rr);
304 }
305 catch(...) {
306 while(this->get(rr));
307 throw;
308 }
309 }
310 return true;
311 }
312
313 bool DNSBackend::getBeforeAndAfterNames(uint32_t id, const DNSName& zonename, const DNSName& qname, DNSName& before, DNSName& after)
314 {
315 DNSName unhashed;
316 bool ret = this->getBeforeAndAfterNamesAbsolute(id, qname.makeRelative(zonename).makeLowerCase(), unhashed, before, after);
317 DNSName lczonename = zonename.makeLowerCase();
318 before += lczonename;
319 after += lczonename;
320 return ret;
321 }
322
323 /**
324 * Calculates a SOA serial for the zone and stores it in the third
325 * argument. Returns false if calculation is not possible for some
326 * reason (in this case, the third argument is not inspected). If it
327 * returns true, the value returned in the third argument will be set
328 * as the SOA serial.
329 *
330 * \param domain The name of the domain
331 * \param sd Information about the SOA record already available
332 * \param serial Output parameter. Only inspected when we return true
333 */
334 bool DNSBackend::calculateSOASerial(const DNSName& domain, const SOAData& sd, time_t& serial)
335 {
336 // we do this by listing the domain and taking the maximum last modified timestamp
337
338 DNSResourceRecord i;
339 time_t newest=0;
340
341 if(!(this->list(domain, sd.domain_id))) {
342 DLOG(L<<Logger::Warning<<"Backend error trying to determine magic serial number of zone '"<<domain<<"'"<<endl);
343 return false;
344 }
345
346 while(this->get(i)) {
347 if(i.last_modified>newest)
348 newest=i.last_modified;
349 }
350
351 serial=newest;
352
353 return true;
354 }
355 void fillSOAData(const DNSZoneRecord& in, SOAData& sd)
356 {
357 sd.domain_id = in.domain_id;
358 sd.ttl = in.dr.d_ttl;
359
360 auto src=getRR<SOARecordContent>(in.dr);
361 sd.nameserver = src->d_mname;
362 sd.hostmaster = src->d_rname;
363 sd.serial = src->d_st.serial;
364 sd.refresh = src->d_st.refresh;
365 sd.retry = src->d_st.retry;
366 sd.expire = src->d_st.expire;
367 sd.default_ttl = src->d_st.minimum;
368 }
369
370 std::shared_ptr<DNSRecordContent> makeSOAContent(const SOAData& sd)
371 {
372 struct soatimes st;
373 st.serial = sd.serial;
374 st.refresh = sd.refresh;
375 st.retry = sd.retry;
376 st.expire = sd.expire;
377 st.minimum = sd.default_ttl;
378 return std::make_shared<SOARecordContent>(sd.nameserver, sd.hostmaster, st);
379 }
380
381
382 void fillSOAData(const string &content, SOAData &data)
383 {
384 // content consists of fields separated by spaces:
385 // nameservername hostmaster serial-number [refresh [retry [expire [ minimum] ] ] ]
386
387 // fill out data with some plausible defaults:
388 // 10800 3600 604800 3600
389 vector<string>parts;
390 stringtok(parts,content);
391 int pleft=parts.size();
392
393 // cout<<"'"<<content<<"'"<<endl;
394
395 if(pleft)
396 data.nameserver=DNSName(parts[0]);
397
398 if(pleft>1)
399 data.hostmaster=DNSName(attodot(parts[1])); // ahu@ds9a.nl -> ahu.ds9a.nl, piet.puk@ds9a.nl -> piet\.puk.ds9a.nl
400
401 try {
402 data.serial = pleft > 2 ? pdns_stou(parts[2]) : 0;
403
404 data.refresh = pleft > 3 ? pdns_stou(parts[3])
405 : ::arg().asNum("soa-refresh-default");
406
407 data.retry = pleft > 4 ? pdns_stou(parts[4].c_str())
408 : ::arg().asNum("soa-retry-default");
409
410 data.expire = pleft > 5 ? pdns_stou(parts[5].c_str())
411 : ::arg().asNum("soa-expire-default");
412
413 data.default_ttl = pleft > 6 ? pdns_stou(parts[6].c_str())
414 : ::arg().asNum("soa-minimum-ttl");
415 }
416 catch(const std::out_of_range& oor) {
417 throw PDNSException("Out of range exception parsing "+content);
418 }
419 }