]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/reczones.cc
Merge pull request #9088 from neheb/nbm
[thirdparty/pdns.git] / pdns / reczones.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 "syncres.hh"
26 #include "arguments.hh"
27 #include "zoneparser-tng.hh"
28 #include "logger.hh"
29 #include "dnsrecords.hh"
30 #include "root-addresses.hh"
31
32 extern int g_argc;
33 extern char** g_argv;
34
35 static thread_local set<DNSName> t_rootNSZones;
36
37 static void insertIntoRootNSZones(const DNSName &name) {
38 // do not insert dot, wiping dot's NS records from the cache in primeRootNSZones()
39 // will cause infinite recursion
40 if (!name.isRoot()) {
41 t_rootNSZones.insert(name);
42 }
43 }
44
45 bool primeHints(void)
46 {
47 // prime root cache
48 const vState validationState = vState::Insecure;
49 vector<DNSRecord> nsset;
50 t_rootNSZones.clear();
51
52 if(::arg()["hint-file"].empty()) {
53 DNSRecord arr, aaaarr, nsrr;
54 nsrr.d_name=g_rootdnsname;
55 arr.d_type=QType::A;
56 aaaarr.d_type=QType::AAAA;
57 nsrr.d_type=QType::NS;
58 arr.d_ttl=aaaarr.d_ttl=nsrr.d_ttl=time(0)+3600000;
59
60 for(char c='a';c<='m';++c) {
61 char templ[40];
62 strncpy(templ,"a.root-servers.net.", sizeof(templ) - 1);
63 templ[sizeof(templ)-1] = '\0';
64 *templ=c;
65 aaaarr.d_name=arr.d_name=DNSName(templ);
66 insertIntoRootNSZones(arr.d_name.getLastLabel());
67 nsrr.d_content=std::make_shared<NSRecordContent>(DNSName(templ));
68 arr.d_content=std::make_shared<ARecordContent>(ComboAddress(rootIps4[c-'a']));
69 vector<DNSRecord> aset;
70 aset.push_back(arr);
71 s_RC->replace(time(0), DNSName(templ), QType(QType::A), aset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), true, boost::none, boost::none, validationState); // auth, nuke it all
72 if (rootIps6[c-'a'] != NULL) {
73 aaaarr.d_content=std::make_shared<AAAARecordContent>(ComboAddress(rootIps6[c-'a']));
74
75 vector<DNSRecord> aaaaset;
76 aaaaset.push_back(aaaarr);
77 s_RC->replace(time(0), DNSName(templ), QType(QType::AAAA), aaaaset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), true, boost::none, boost::none, validationState);
78 }
79
80 nsset.push_back(nsrr);
81 }
82 }
83 else {
84 ZoneParserTNG zpt(::arg()["hint-file"]);
85 zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
86 DNSResourceRecord rr;
87 set<DNSName> seenNS;
88 set<DNSName> seenA;
89 set<DNSName> seenAAAA;
90
91 while(zpt.get(rr)) {
92 rr.ttl+=time(0);
93 if(rr.qtype.getCode()==QType::A) {
94 seenA.insert(rr.qname);
95 vector<DNSRecord> aset;
96 aset.push_back(DNSRecord(rr));
97 s_RC->replace(time(0), rr.qname, QType(QType::A), aset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), true, boost::none, boost::none, validationState); // auth, etc see above
98 } else if(rr.qtype.getCode()==QType::AAAA) {
99 seenAAAA.insert(rr.qname);
100 vector<DNSRecord> aaaaset;
101 aaaaset.push_back(DNSRecord(rr));
102 s_RC->replace(time(0), rr.qname, QType(QType::AAAA), aaaaset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), true, boost::none, boost::none, validationState);
103 } else if(rr.qtype.getCode()==QType::NS) {
104 seenNS.insert(DNSName(rr.content));
105 rr.content=toLower(rr.content);
106 nsset.push_back(DNSRecord(rr));
107 }
108 insertIntoRootNSZones(rr.qname.getLastLabel());
109 }
110
111 // Check reachability of A and AAAA records
112 bool reachableA = false, reachableAAAA = false;
113 for (auto const& r: seenA) {
114 if (seenNS.count(r)) {
115 reachableA = true;
116 }
117 }
118 for (auto const& r: seenAAAA) {
119 if (seenNS.count(r)) {
120 reachableAAAA = true;
121 }
122 }
123 if (SyncRes::s_doIPv4 && !SyncRes::s_doIPv6 && !reachableA) {
124 g_log<<Logger::Error<<"Running IPv4 only but no IPv4 root hints"<<endl;
125 return false;
126 }
127 if (!SyncRes::s_doIPv4 && SyncRes::s_doIPv6 && !reachableAAAA) {
128 g_log<<Logger::Error<<"Running IPv6 only but no IPv6 root hints"<<endl;
129 return false;
130 }
131 if (SyncRes::s_doIPv4 && SyncRes::s_doIPv6 && !reachableA && !reachableAAAA) {
132 g_log<<Logger::Error<<"No valid root hints"<<endl;
133 return false;
134 }
135 }
136
137 s_RC->doWipeCache(g_rootdnsname, false, QType::NS);
138 s_RC->replace(time(0), g_rootdnsname, QType(QType::NS), nsset, vector<std::shared_ptr<RRSIGRecordContent>>(), vector<std::shared_ptr<DNSRecord>>(), false, boost::none, boost::none, validationState); // and stuff in the cache
139 return true;
140 }
141
142
143 // Do not only put the root hints into the cache, but also make sure
144 // the NS records of the top level domains of the names of the root
145 // servers are in the cache. We need these to correctly determine the
146 // security status of that specific domain (normally
147 // root-servers.net). This is caused by the accident that the root
148 // servers are authoritative for root-servers.net, and some
149 // implementations reply not with a delegation on a root-servers.net
150 // DS query, but with a NODATA response (the domain is unsigned).
151 void primeRootNSZones(bool dnssecmode, unsigned int depth)
152 {
153 struct timeval now;
154 gettimeofday(&now, 0);
155 SyncRes sr(now);
156
157 if (dnssecmode) {
158 sr.setDoDNSSEC(true);
159 sr.setDNSSECValidationRequested(true);
160 }
161
162 // beginResolve() can yield to another mthread that could trigger t_rootNSZones updates,
163 // so make a local copy
164 set<DNSName> copy(t_rootNSZones);
165 for (const auto & qname: copy) {
166 s_RC->doWipeCache(qname, false, QType::NS);
167 vector<DNSRecord> ret;
168 sr.beginResolve(qname, QType(QType::NS), QClass::IN, ret, depth + 1);
169 }
170 }
171
172 static void makeNameToIPZone(std::shared_ptr<SyncRes::domainmap_t> newMap, const DNSName& hostname, const string& ip)
173 {
174 SyncRes::AuthDomain ad;
175 ad.d_rdForward=false;
176
177 DNSRecord dr;
178 dr.d_name=hostname;
179 dr.d_place=DNSResourceRecord::ANSWER;
180 dr.d_ttl=86400;
181 dr.d_type=QType::SOA;
182 dr.d_class = 1;
183 dr.d_content = DNSRecordContent::mastermake(QType::SOA, 1, "localhost. root 1 604800 86400 2419200 604800");
184
185 ad.d_records.insert(dr);
186
187 dr.d_type=QType::NS;
188 dr.d_content=std::make_shared<NSRecordContent>("localhost.");
189
190 ad.d_records.insert(dr);
191
192 dr.d_type=QType::A;
193 dr.d_content = DNSRecordContent::mastermake(QType::A, 1, ip);
194 ad.d_records.insert(dr);
195
196 if(newMap->count(dr.d_name)) {
197 g_log<<Logger::Warning<<"Hosts file will not overwrite zone '"<<dr.d_name<<"' already loaded"<<endl;
198 }
199 else {
200 g_log<<Logger::Warning<<"Inserting forward zone '"<<dr.d_name<<"' based on hosts file"<<endl;
201 ad.d_name=dr.d_name;
202 (*newMap)[ad.d_name]=ad;
203 }
204 }
205
206 //! parts[0] must be an IP address, the rest must be host names
207 static void makeIPToNamesZone(std::shared_ptr<SyncRes::domainmap_t> newMap, const vector<string>& parts)
208 {
209 string address=parts[0];
210 vector<string> ipparts;
211 stringtok(ipparts, address,".");
212
213 SyncRes::AuthDomain ad;
214 ad.d_rdForward=false;
215
216 DNSRecord dr;
217 for(int n=ipparts.size()-1; n>=0 ; --n) {
218 dr.d_name.appendRawLabel(ipparts[n]);
219 }
220 dr.d_name.appendRawLabel("in-addr");
221 dr.d_name.appendRawLabel("arpa");
222 dr.d_class = 1;
223 dr.d_place=DNSResourceRecord::ANSWER;
224 dr.d_ttl=86400;
225 dr.d_type=QType::SOA;
226 dr.d_content=DNSRecordContent::mastermake(QType::SOA, 1, "localhost. root 1 604800 86400 2419200 604800");
227
228 ad.d_records.insert(dr);
229
230 dr.d_type=QType::NS;
231 dr.d_content=std::make_shared<NSRecordContent>(DNSName("localhost."));
232
233 ad.d_records.insert(dr);
234 dr.d_type=QType::PTR;
235
236 if(ipparts.size()==4) // otherwise this is a partial zone
237 for(unsigned int n=1; n < parts.size(); ++n) {
238 dr.d_content=DNSRecordContent::mastermake(QType::PTR, 1, DNSName(parts[n]).toString()); // XXX FIXME DNSNAME PAIN CAN THIS BE RIGHT?
239 ad.d_records.insert(dr);
240 }
241
242 if(newMap->count(dr.d_name)) {
243 g_log<<Logger::Warning<<"Will not overwrite zone '"<<dr.d_name<<"' already loaded"<<endl;
244 }
245 else {
246 if(ipparts.size()==4)
247 g_log<<Logger::Warning<<"Inserting reverse zone '"<<dr.d_name<<"' based on hosts file"<<endl;
248 ad.d_name = dr.d_name;
249 (*newMap)[ad.d_name]=ad;
250 }
251 }
252
253
254
255 /* mission in life: parse three cases
256 1) 1.2.3.4
257 2) 1.2.3.4:5300
258 3) 2001::1
259 4) [2002::1]:53
260 */
261
262 ComboAddress parseIPAndPort(const std::string& input, uint16_t port)
263 {
264 if(input.find(':') == string::npos || input.empty()) // common case
265 return ComboAddress(input, port);
266
267 pair<string,string> both;
268
269 try { // case 2
270 both=splitField(input,':');
271 uint16_t newport=static_cast<uint16_t>(pdns_stou(both.second));
272 return ComboAddress(both.first, newport);
273 }
274 catch(...){}
275
276 if(input[0]=='[') { // case 4
277 both=splitField(input.substr(1),']');
278 return ComboAddress(both.first, both.second.empty() ? port : static_cast<uint16_t>(pdns_stou(both.second.substr(1))));
279 }
280
281 return ComboAddress(input, port); // case 3
282 }
283
284
285 static void convertServersForAD(const std::string& input, SyncRes::AuthDomain& ad, const char* sepa, bool verbose=true)
286 {
287 vector<string> servers;
288 stringtok(servers, input, sepa);
289 ad.d_servers.clear();
290
291 for(vector<string>::const_iterator iter = servers.begin(); iter != servers.end(); ++iter) {
292 if(verbose && iter != servers.begin())
293 g_log<<", ";
294
295 ComboAddress addr=parseIPAndPort(*iter, 53);
296 if(verbose)
297 g_log<<addr.toStringWithPort();
298 ad.d_servers.push_back(addr);
299 }
300 if(verbose)
301 g_log<<endl;
302 }
303
304 static void* pleaseUseNewSDomainsMap(std::shared_ptr<SyncRes::domainmap_t> newmap)
305 {
306 SyncRes::setDomainMap(newmap);
307 return 0;
308 }
309
310 string reloadAuthAndForwards()
311 {
312 std::shared_ptr<SyncRes::domainmap_t> original=SyncRes::getDomainMap();
313
314 try {
315 g_log<<Logger::Warning<<"Reloading zones, purging data from cache"<<endl;
316
317 string configname=::arg()["config-dir"]+"/recursor.conf";
318 if(::arg()["config-name"]!="") {
319 configname=::arg()["config-dir"]+"/recursor-"+::arg()["config-name"]+".conf";
320 }
321 cleanSlashes(configname);
322
323 if(!::arg().preParseFile(configname.c_str(), "forward-zones"))
324 throw runtime_error("Unable to re-parse configuration file '"+configname+"'");
325 ::arg().preParseFile(configname.c_str(), "forward-zones-file");
326 ::arg().preParseFile(configname.c_str(), "forward-zones-recurse");
327 ::arg().preParseFile(configname.c_str(), "auth-zones");
328 ::arg().preParseFile(configname.c_str(), "export-etc-hosts", "off");
329 ::arg().preParseFile(configname.c_str(), "serve-rfc1918");
330 ::arg().preParseFile(configname.c_str(), "include-dir");
331 ::arg().preParse(g_argc, g_argv, "include-dir");
332
333 // then process includes
334 std::vector<std::string> extraConfigs;
335 ::arg().gatherIncludes(extraConfigs);
336
337 for(const std::string& fn : extraConfigs) {
338 if(!::arg().preParseFile(fn.c_str(), "forward-zones", ::arg()["forward-zones"]))
339 throw runtime_error("Unable to re-parse configuration file include '"+fn+"'");
340 ::arg().preParseFile(fn.c_str(), "forward-zones-file", ::arg()["forward-zones-file"]);
341 ::arg().preParseFile(fn.c_str(), "forward-zones-recurse", ::arg()["forward-zones-recurse"]);
342 ::arg().preParseFile(fn.c_str(), "auth-zones",::arg()["auth-zones"]);
343 ::arg().preParseFile(fn.c_str(), "export-etc-hosts",::arg()["export-etc-hosts"]);
344 ::arg().preParseFile(fn.c_str(), "serve-rfc1918",::arg()["serve-rfc1918"]);
345 }
346
347 ::arg().preParse(g_argc, g_argv, "forward-zones");
348 ::arg().preParse(g_argc, g_argv, "forward-zones-file");
349 ::arg().preParse(g_argc, g_argv, "forward-zones-recurse");
350 ::arg().preParse(g_argc, g_argv, "auth-zones");
351 ::arg().preParse(g_argc, g_argv, "export-etc-hosts");
352 ::arg().preParse(g_argc, g_argv, "serve-rfc1918");
353
354 std::shared_ptr<SyncRes::domainmap_t> newDomainMap = parseAuthAndForwards();
355
356 // purge both original and new names
357 std::set<DNSName> oldAndNewDomains;
358 for(const auto& i : *newDomainMap) {
359 oldAndNewDomains.insert(i.first);
360 }
361
362 if(original) {
363 for(const auto& i : *original) {
364 oldAndNewDomains.insert(i.first);
365 }
366 }
367
368 for(const auto& i : oldAndNewDomains) {
369 broadcastAccFunction<uint64_t>([&]{return pleaseWipeCache(i, true, 0xffff);});
370 broadcastAccFunction<uint64_t>([&]{return pleaseWipePacketCache(i, true, 0xffff);});
371 broadcastAccFunction<uint64_t>([&]{return pleaseWipeAndCountNegCache(i, true);});
372 }
373
374 broadcastFunction([=]{return pleaseUseNewSDomainsMap(newDomainMap);});
375 return "ok\n";
376 }
377 catch(std::exception& e) {
378 g_log<<Logger::Error<<"Encountered error reloading zones, keeping original data: "<<e.what()<<endl;
379 }
380 catch(PDNSException& ae) {
381 g_log<<Logger::Error<<"Encountered error reloading zones, keeping original data: "<<ae.reason<<endl;
382 }
383 catch(...) {
384 g_log<<Logger::Error<<"Encountered unknown error reloading zones, keeping original data"<<endl;
385 }
386 return "reloading failed, see log\n";
387 }
388
389 std::shared_ptr<SyncRes::domainmap_t> parseAuthAndForwards()
390 {
391 TXTRecordContent::report();
392 OPTRecordContent::report();
393
394 auto newMap = std::make_shared<SyncRes::domainmap_t>();
395
396 typedef vector<string> parts_t;
397 parts_t parts;
398 const char *option_names[3]={"auth-zones", "forward-zones", "forward-zones-recurse"};
399 for(int n=0; n < 3 ; ++n ) {
400 parts.clear();
401 stringtok(parts, ::arg()[option_names[n]], " ,\t\n\r");
402 for(parts_t::const_iterator iter = parts.begin(); iter != parts.end(); ++iter) {
403 SyncRes::AuthDomain ad;
404 if ((*iter).find('=') == string::npos)
405 throw PDNSException("Error parsing '" + *iter + "', missing =");
406 pair<string,string> headers=splitField(*iter, '=');
407 trim(headers.first);
408 trim(headers.second);
409 // headers.first=toCanonic("", headers.first);
410 if(n==0) {
411 ad.d_rdForward = false;
412 g_log<<Logger::Error<<"Parsing authoritative data for zone '"<<headers.first<<"' from file '"<<headers.second<<"'"<<endl;
413 ZoneParserTNG zpt(headers.second, DNSName(headers.first));
414 zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
415 DNSResourceRecord rr;
416 DNSRecord dr;
417 while(zpt.get(rr)) {
418 try {
419 dr=DNSRecord(rr);
420 dr.d_place=DNSResourceRecord::ANSWER;
421 }
422 catch(std::exception &e) {
423 throw PDNSException("Error parsing record '"+rr.qname.toLogString()+"' of type "+rr.qtype.getName()+" in zone '"+headers.first+"' from file '"+headers.second+"': "+e.what());
424 }
425 catch(...) {
426 throw PDNSException("Error parsing record '"+rr.qname.toLogString()+"' of type "+rr.qtype.getName()+" in zone '"+headers.first+"' from file '"+headers.second+"'");
427 }
428
429 ad.d_records.insert(dr);
430 }
431 }
432 else {
433 g_log<<Logger::Error<<"Redirecting queries for zone '"<<headers.first<<"' ";
434 if(n == 2) {
435 g_log<<"with recursion ";
436 ad.d_rdForward = true;
437 }
438 else ad.d_rdForward = false;
439 g_log<<"to: ";
440
441 convertServersForAD(headers.second, ad, ";");
442 if(n == 2) {
443 ad.d_rdForward = true;
444 }
445 }
446
447 ad.d_name = DNSName(headers.first);
448 (*newMap)[ad.d_name]=ad;
449 }
450 }
451
452 if(!::arg()["forward-zones-file"].empty()) {
453 g_log<<Logger::Warning<<"Reading zone forwarding information from '"<<::arg()["forward-zones-file"]<<"'"<<endl;
454 SyncRes::AuthDomain ad;
455 auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(::arg()["forward-zones-file"].c_str(), "r"), fclose);
456 if(!fp) {
457 throw PDNSException("Error opening forward-zones-file '"+::arg()["forward-zones-file"]+"': "+stringerror());
458 }
459
460 string line;
461 int linenum=0;
462 uint64_t before = newMap->size();
463 while(linenum++, stringfgets(fp.get(), line)) {
464 trim(line);
465 if (line[0] == '#') // Comment line, skip to the next line
466 continue;
467 string domain, instructions;
468 tie(domain, instructions)=splitField(line, '=');
469 instructions = splitField(instructions, '#').first; // Remove EOL comments
470 trim(domain);
471 trim(instructions);
472 if(domain.empty() && instructions.empty()) { // empty line
473 continue;
474 }
475 if(boost::starts_with(domain,"+")) {
476 domain=domain.c_str()+1;
477 ad.d_rdForward = true;
478 }
479 else
480 ad.d_rdForward = false;
481 if(domain.empty()) {
482 throw PDNSException("Error parsing line "+std::to_string(linenum)+" of " +::arg()["forward-zones-file"]);
483 }
484
485 try {
486 convertServersForAD(instructions, ad, ",; ", false);
487 }
488 catch(...) {
489 throw PDNSException("Conversion error parsing line "+std::to_string(linenum)+" of " +::arg()["forward-zones-file"]);
490 }
491
492 ad.d_name = DNSName(domain);
493 (*newMap)[ad.d_name]=ad;
494 }
495 g_log<<Logger::Warning<<"Done parsing " << newMap->size() - before<<" forwarding instructions from file '"<<::arg()["forward-zones-file"]<<"'"<<endl;
496 }
497
498 if(::arg().mustDo("export-etc-hosts")) {
499 string line;
500 string fname=::arg()["etc-hosts-file"];
501
502 ifstream ifs(fname.c_str());
503 if(!ifs) {
504 g_log<<Logger::Warning<<"Could not open "<<fname<<" for reading"<<endl;
505 }
506 else {
507 string searchSuffix = ::arg()["export-etc-hosts-search-suffix"];
508 string::size_type pos;
509 while(getline(ifs,line)) {
510 pos=line.find('#');
511 if(pos!=string::npos)
512 line.resize(pos);
513 trim(line);
514 if(line.empty())
515 continue;
516 parts.clear();
517 stringtok(parts, line, "\t\r\n ");
518 if(parts[0].find(':')!=string::npos)
519 continue;
520
521 for(unsigned int n=1; n < parts.size(); ++n) {
522 if(searchSuffix.empty() || parts[n].find('.') != string::npos)
523 makeNameToIPZone(newMap, DNSName(parts[n]), parts[0]);
524 else {
525 DNSName canonic=toCanonic(DNSName(searchSuffix), parts[n]); /// XXXX DNSName pain
526 if(canonic != DNSName(parts[n])) { // XXX further DNSName pain
527 makeNameToIPZone(newMap, canonic, parts[0]);
528 }
529 }
530 }
531 makeIPToNamesZone(newMap, parts);
532 }
533 }
534 }
535 if(::arg().mustDo("serve-rfc1918")) {
536 g_log<<Logger::Warning<<"Inserting rfc 1918 private space zones"<<endl;
537 parts.clear();
538 parts.push_back("127");
539 makeIPToNamesZone(newMap, parts);
540 parts[0]="10";
541 makeIPToNamesZone(newMap, parts);
542
543 parts[0]="192.168";
544 makeIPToNamesZone(newMap, parts);
545 for(int n=16; n < 32; n++) {
546 parts[0]="172."+std::to_string(n);
547 makeIPToNamesZone(newMap,parts);
548 }
549 }
550 return newMap;
551 }