]>
Commit | Line | Data |
---|---|---|
12c86877 | 1 | /* |
12471842 PL |
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 | */ | |
870a0fe4 AT |
22 | #ifdef HAVE_CONFIG_H |
23 | #include "config.h" | |
24 | #endif | |
731f58b8 | 25 | #include "utility.hh" |
12c86877 BH |
26 | #include "dnsbackend.hh" |
27 | #include "arguments.hh" | |
28 | #include "ueberbackend.hh" | |
29 | #include "logger.hh" | |
30 | ||
31 | #include <sys/types.h> | |
21e99384 | 32 | #include "pdns/packetcache.hh" |
12c86877 | 33 | #include "dnspacket.hh" |
c87f987e | 34 | #include "dns.hh" |
12c86877 | 35 | |
cec52de6 | 36 | bool DNSBackend::getAuth(const DNSName &target, SOAData *sd) |
c14bc34a | 37 | { |
94bfa5b6 | 38 | return this->getSOA(target, *sd); |
c14bc34a MZ |
39 | } |
40 | ||
12c86877 BH |
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 ¶m, const string &help, const string &value) | |
62 | { | |
98a8b02e | 63 | string fullname=d_name+suffix+"-"+param; |
12c86877 BH |
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 | ||
5f3eca86 | 84 | vector<string> BackendMakerClass::getModules() |
12c86877 BH |
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() | |
731f58b8 BH |
95 | { |
96 | // TODO: Implement this? | |
12c86877 BH |
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))) { | |
5f3eca86 | 104 | if(!strncmp(entry->d_name,"lib",3) && |
731f58b8 BH |
105 | strlen(entry->d_name)>13 && |
106 | !strcmp(entry->d_name+strlen(entry->d_name)-10,"backend.so")) | |
12c86877 BH |
107 | load(entry->d_name); |
108 | } | |
731f58b8 | 109 | closedir(dir); |
12c86877 BH |
110 | } |
111 | ||
112 | void BackendMakerClass::load(const string &module) | |
113 | { | |
34c513f9 | 114 | bool res; |
12c86877 BH |
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); | |
5f3eca86 | 122 | |
12c86877 | 123 | if(res==false) { |
c057bfaa | 124 | L<<Logger::Error<<"DNSBackend unable to load module in "<<module<<endl; |
12c86877 BH |
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"); | |
5f3eca86 | 133 | |
12c86877 BH |
134 | vector<string> parts; |
135 | stringtok(parts,instr,", "); | |
5f3eca86 | 136 | |
c52dd6af PL |
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 | ||
12c86877 BH |
141 | for(vector<string>::const_iterator i=parts.begin();i!=parts.end();++i) { |
142 | const string &part=*i; | |
5f3eca86 | 143 | |
12c86877 BH |
144 | string module, name; |
145 | vector<string>pparts; | |
146 | stringtok(pparts,part,": "); | |
147 | module=pparts[0]; | |
148 | if(pparts.size()>1) | |
98a8b02e | 149 | name="-"+pparts[1]; |
5f3eca86 | 150 | |
12c86877 BH |
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()) | |
4957a608 | 155 | throw ArgException("Trying to launch unknown backend '"+module+"'"); |
12c86877 | 156 | } |
12c86877 BH |
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 | ||
2717b8b3 | 167 | vector<DNSBackend *>BackendMakerClass::all(bool metadataOnly) |
12c86877 BH |
168 | { |
169 | vector<DNSBackend *>ret; | |
170 | if(d_instances.empty()) | |
3f81d239 | 171 | throw PDNSException("No database backends configured for launch, unable to function"); |
12c86877 BH |
172 | |
173 | try { | |
174 | for(vector<pair<string,string> >::const_iterator i=d_instances.begin();i!=d_instances.end();++i) { | |
2717b8b3 BH |
175 | DNSBackend *made; |
176 | if(metadataOnly) | |
177 | made = d_repository[i->first]->makeMetadataOnly(i->second); | |
5f3eca86 | 178 | else |
2717b8b3 | 179 | made = d_repository[i->first]->make(i->second); |
12c86877 | 180 | if(!made) |
3f81d239 | 181 | throw PDNSException("Unable to launch backend '"+i->first+"'"); |
12c86877 BH |
182 | |
183 | ret.push_back(made); | |
184 | } | |
185 | } | |
3f81d239 | 186 | catch(PDNSException &ae) { |
06ad4526 PD |
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; | |
07dbfb4f | 191 | throw; |
06ad4526 | 192 | } catch(...) { |
12c86877 | 193 | // and cleanup |
bdc9f8d2 | 194 | L<<Logger::Error<<"Caught an exception instantiating a backend, cleaning up"<<endl; |
12c86877 BH |
195 | for(vector<DNSBackend *>::const_iterator i=ret.begin();i!=ret.end();++i) |
196 | delete *i; | |
07dbfb4f | 197 | throw; |
12c86877 | 198 | } |
5f3eca86 | 199 | |
12c86877 BH |
200 | return ret; |
201 | } | |
202 | ||
bdc9f8d2 | 203 | /** getSOA() is a function that is called to get the SOA of a domain. Callers should ONLY |
4dfd8645 | 204 | use getSOA() and not perform a lookup() themselves as backends may decide to special case |
bdc9f8d2 | 205 | the SOA record. |
5f3eca86 | 206 | |
bdc9f8d2 BH |
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 | ||
25b1d5d7 BH |
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 | ||
bdc9f8d2 BH |
214 | \param domain Domain we want to get the SOA details of |
215 | \param sd SOAData which is filled with the SOA details | |
216 | */ | |
94bfa5b6 | 217 | bool DNSBackend::getSOA(const DNSName &domain, SOAData &sd) |
12c86877 | 218 | { |
94bfa5b6 | 219 | this->lookup(QType(QType::SOA),domain); |
5f3eca86 | 220 | |
12c86877 | 221 | DNSResourceRecord rr; |
5f3eca86 | 222 | rr.auth = true; |
12c86877 BH |
223 | |
224 | int hits=0; | |
225 | ||
226 | while(this->get(rr)) { | |
5f3eca86 | 227 | if (rr.qtype != QType::SOA) throw PDNSException("Got non-SOA record when asking for SOA"); |
12c86877 | 228 | hits++; |
34b37bbb | 229 | fillSOAData(rr.content, sd); |
12c86877 BH |
230 | sd.domain_id=rr.domain_id; |
231 | sd.ttl=rr.ttl; | |
af7d3ea6 | 232 | sd.scopeMask = rr.scopeMask; |
12c86877 | 233 | } |
7b308b7e | 234 | |
12c86877 BH |
235 | if(!hits) |
236 | return false; | |
5f5221b4 | 237 | sd.qname = domain; |
675fa24c | 238 | if(!sd.nameserver.countLabels()) |
290a083d | 239 | sd.nameserver= DNSName(arg()["default-soa-name"]); |
5f3eca86 | 240 | |
675fa24c | 241 | if(!sd.hostmaster.countLabels()) { |
c87f987e | 242 | if (!arg().isEmpty("default-soa-mail")) { |
290a083d | 243 | sd.hostmaster= DNSName(arg()["default-soa-mail"]); |
3343ad1f | 244 | // attodot(sd.hostmaster); FIXME400 |
c87f987e KM |
245 | } |
246 | else | |
7abbc40f | 247 | sd.hostmaster=DNSName("hostmaster")+domain; |
c87f987e | 248 | } |
12c86877 BH |
249 | |
250 | if(!sd.serial) { // magic time! | |
f43c4448 | 251 | DLOG(L<<Logger::Warning<<"Doing SOA serial number autocalculation for "<<rr.qname<<endl); |
12c86877 | 252 | |
d07fc616 PD |
253 | time_t serial; |
254 | if (calculateSOASerial(domain, sd, serial)) { | |
255 | sd.serial = serial; | |
df8d5183 | 256 | //DLOG(L<<"autocalculated soa serialnumber for "<<rr.qname<<" is "<<newest<<endl); |
d07fc616 | 257 | } else { |
f43c4448 | 258 | DLOG(L<<"soa serialnumber calculation failed for "<<rr.qname<<endl); |
12c86877 BH |
259 | } |
260 | ||
12c86877 BH |
261 | } |
262 | sd.db=this; | |
263 | return true; | |
264 | } | |
265 | ||
90ba52e0 | 266 | bool DNSBackend::get(DNSZoneRecord& dzr) |
267 | { | |
aa7b2405 | 268 | // cout<<"DNSBackend::get(DNSZoneRecord&) called - translating into DNSResourceRecord query"<<endl; |
90ba52e0 | 269 | DNSResourceRecord rr; |
270 | if(!this->get(rr)) | |
271 | return false; | |
272 | dzr.auth = rr.auth; | |
273 | dzr.domain_id = rr.domain_id; | |
158bc1d1 | 274 | dzr.scopeMask = rr.scopeMask; |
c7d5b054 | 275 | if(rr.qtype.getCode() == QType::TXT && !rr.content.empty() && rr.content[0]!='"') |
276 | rr.content = "\""+ rr.content + "\""; | |
9262e6e1 | 277 | if(rr.qtype.getCode() == QType::SOA) { |
278 | try { | |
279 | dzr.dr = DNSRecord(rr); | |
280 | } catch(...) { | |
281 | vector<string> parts; | |
282 | stringtok(parts, rr.content, " \t"); | |
283 | if(parts.size() < 1) | |
484efba7 | 284 | rr.content = arg()["default-soa-name"]; |
9262e6e1 | 285 | if(parts.size() < 2) |
484efba7 | 286 | rr.content += " " +arg()["default-soa-mail"]; |
9262e6e1 | 287 | if(parts.size() < 3) |
288 | rr.content += " 0"; | |
289 | if(parts.size() < 4) | |
290 | rr.content += " " + ::arg()["soa-refresh-default"]; | |
291 | if(parts.size() < 5) | |
484efba7 | 292 | rr.content += " " + ::arg()["soa-retry-default"]; |
9262e6e1 | 293 | if(parts.size() < 6) |
484efba7 KM |
294 | rr.content += " " + ::arg()["soa-expire-default"]; |
295 | if(parts.size() < 7) | |
296 | rr.content += " " + ::arg()["soa-minimum-ttl"]; | |
297 | dzr.dr = DNSRecord(rr); | |
9262e6e1 | 298 | } |
299 | } | |
d42d2664 | 300 | else { |
301 | try { | |
302 | dzr.dr = DNSRecord(rr); | |
303 | } | |
304 | catch(...) { | |
305 | while(this->get(rr)); | |
306 | throw; | |
307 | } | |
308 | } | |
90ba52e0 | 309 | return true; |
310 | } | |
311 | ||
675fa24c | 312 | bool DNSBackend::getBeforeAndAfterNames(uint32_t id, const DNSName& zonename, const DNSName& qname, DNSName& before, DNSName& after) |
75943e62 | 313 | { |
29e0008a KM |
314 | DNSName unhashed; |
315 | bool ret = this->getBeforeAndAfterNamesAbsolute(id, qname.makeRelative(zonename).makeLowerCase(), unhashed, before, after); | |
316 | DNSName lczonename = zonename.makeLowerCase(); | |
317 | before += lczonename; | |
318 | after += lczonename; | |
75943e62 BH |
319 | return ret; |
320 | } | |
d07fc616 PD |
321 | |
322 | /** | |
323 | * Calculates a SOA serial for the zone and stores it in the third | |
324 | * argument. Returns false if calculation is not possible for some | |
325 | * reason (in this case, the third argument is not inspected). If it | |
326 | * returns true, the value returned in the third argument will be set | |
327 | * as the SOA serial. | |
328 | * | |
329 | * \param domain The name of the domain | |
330 | * \param sd Information about the SOA record already available | |
331 | * \param serial Output parameter. Only inspected when we return true | |
332 | */ | |
675fa24c | 333 | bool DNSBackend::calculateSOASerial(const DNSName& domain, const SOAData& sd, time_t& serial) |
d07fc616 PD |
334 | { |
335 | // we do this by listing the domain and taking the maximum last modified timestamp | |
336 | ||
337 | DNSResourceRecord i; | |
338 | time_t newest=0; | |
339 | ||
340 | if(!(this->list(domain, sd.domain_id))) { | |
f43c4448 | 341 | DLOG(L<<Logger::Warning<<"Backend error trying to determine magic serial number of zone '"<<domain<<"'"<<endl); |
d07fc616 PD |
342 | return false; |
343 | } | |
5f3eca86 | 344 | |
d07fc616 PD |
345 | while(this->get(i)) { |
346 | if(i.last_modified>newest) | |
347 | newest=i.last_modified; | |
348 | } | |
349 | ||
a1fe72a4 | 350 | serial=newest; |
d07fc616 PD |
351 | |
352 | return true; | |
353 | } | |
90ba52e0 | 354 | void fillSOAData(const DNSZoneRecord& in, SOAData& sd) |
355 | { | |
356 | sd.domain_id = in.domain_id; | |
357 | sd.ttl = in.dr.d_ttl; | |
358 | ||
359 | auto src=getRR<SOARecordContent>(in.dr); | |
360 | sd.nameserver = src->d_mname; | |
361 | sd.hostmaster = src->d_rname; | |
362 | sd.serial = src->d_st.serial; | |
363 | sd.refresh = src->d_st.refresh; | |
364 | sd.retry = src->d_st.retry; | |
365 | sd.expire = src->d_st.expire; | |
366 | sd.default_ttl = src->d_st.minimum; | |
367 | } | |
368 | ||
369 | std::shared_ptr<DNSRecordContent> makeSOAContent(const SOAData& sd) | |
370 | { | |
371 | struct soatimes st; | |
372 | st.serial = sd.serial; | |
373 | st.refresh = sd.refresh; | |
374 | st.retry = sd.retry; | |
375 | st.expire = sd.expire; | |
376 | st.minimum = sd.default_ttl; | |
377 | return std::make_shared<SOARecordContent>(sd.nameserver, sd.hostmaster, st); | |
378 | } | |
379 | ||
c14bc34a | 380 | |
ceea1ba6 | 381 | void fillSOAData(const string &content, SOAData &data) |
382 | { | |
383 | // content consists of fields separated by spaces: | |
384 | // nameservername hostmaster serial-number [refresh [retry [expire [ minimum] ] ] ] | |
385 | ||
386 | // fill out data with some plausible defaults: | |
387 | // 10800 3600 604800 3600 | |
388 | vector<string>parts; | |
389 | stringtok(parts,content); | |
390 | int pleft=parts.size(); | |
391 | ||
392 | // cout<<"'"<<content<<"'"<<endl; | |
393 | ||
394 | if(pleft) | |
395 | data.nameserver=DNSName(parts[0]); | |
396 | ||
397 | if(pleft>1) | |
398 | data.hostmaster=DNSName(attodot(parts[1])); // ahu@ds9a.nl -> ahu.ds9a.nl, piet.puk@ds9a.nl -> piet\.puk.ds9a.nl | |
399 | ||
97ce13be RG |
400 | try { |
401 | data.serial = pleft > 2 ? pdns_stou(parts[2]) : 0; | |
ceea1ba6 | 402 | |
97ce13be RG |
403 | data.refresh = pleft > 3 ? pdns_stou(parts[3]) |
404 | : ::arg().asNum("soa-refresh-default"); | |
ceea1ba6 | 405 | |
97ce13be RG |
406 | data.retry = pleft > 4 ? pdns_stou(parts[4].c_str()) |
407 | : ::arg().asNum("soa-retry-default"); | |
ceea1ba6 | 408 | |
97ce13be RG |
409 | data.expire = pleft > 5 ? pdns_stou(parts[5].c_str()) |
410 | : ::arg().asNum("soa-expire-default"); | |
ceea1ba6 | 411 | |
97ce13be RG |
412 | data.default_ttl = pleft > 6 ? pdns_stou(parts[6].c_str()) |
413 | : ::arg().asNum("soa-minimum-ttl"); | |
414 | } | |
415 | catch(const std::out_of_range& oor) { | |
416 | throw PDNSException("Out of range exception parsing "+content); | |
417 | } | |
ceea1ba6 | 418 | } |
419 | ||
420 | string serializeSOAData(const SOAData &d) | |
421 | { | |
422 | ostringstream o; | |
423 | // nameservername hostmaster serial-number [refresh [retry [expire [ minimum] ] ] ] | |
424 | o<<d.nameserver.toString()<<" "<< d.hostmaster.toString() <<" "<< d.serial <<" "<< d.refresh << " "<< d.retry << " "<< d.expire << " "<< d.default_ttl; | |
425 | ||
426 | return o.str(); | |
427 | } |