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