2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
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.
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.
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.
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.
26 #include "arguments.hh"
27 #include "zoneparser-tng.hh"
29 #include "dnsrecords.hh"
30 #include "rec-lua-conf.hh"
33 #include "rpzloader.hh"
34 #include "root-addresses.hh"
42 const vState validationState
= Insecure
;
43 vector
<DNSRecord
> nsset
;
45 t_RC
= std::unique_ptr
<MemRecursorCache
>(new MemRecursorCache());
47 if(::arg()["hint-file"].empty()) {
48 DNSRecord arr
, aaaarr
, nsrr
;
49 nsrr
.d_name
=g_rootdnsname
;
51 aaaarr
.d_type
=QType::AAAA
;
52 nsrr
.d_type
=QType::NS
;
53 arr
.d_ttl
=aaaarr
.d_ttl
=nsrr
.d_ttl
=time(0)+3600000;
55 for(char c
='a';c
<='m';++c
) {
56 static char templ
[40];
57 strncpy(templ
,"a.root-servers.net.", sizeof(templ
) - 1);
58 templ
[sizeof(templ
)-1] = '\0';
60 aaaarr
.d_name
=arr
.d_name
=DNSName(templ
);
61 nsrr
.d_content
=std::make_shared
<NSRecordContent
>(DNSName(templ
));
62 arr
.d_content
=std::make_shared
<ARecordContent
>(ComboAddress(rootIps4
[c
-'a']));
63 vector
<DNSRecord
> aset
;
65 t_RC
->replace(time(0), DNSName(templ
), QType(QType::A
), aset
, vector
<std::shared_ptr
<RRSIGRecordContent
>>(), vector
<std::shared_ptr
<DNSRecord
>>(), true, boost::none
, validationState
); // auth, nuke it all
66 if (rootIps6
[c
-'a'] != NULL
) {
67 aaaarr
.d_content
=std::make_shared
<AAAARecordContent
>(ComboAddress(rootIps6
[c
-'a']));
69 vector
<DNSRecord
> aaaaset
;
70 aaaaset
.push_back(aaaarr
);
71 t_RC
->replace(time(0), DNSName(templ
), QType(QType::AAAA
), aaaaset
, vector
<std::shared_ptr
<RRSIGRecordContent
>>(), vector
<std::shared_ptr
<DNSRecord
>>(), true, boost::none
, validationState
);
74 nsset
.push_back(nsrr
);
78 ZoneParserTNG
zpt(::arg()["hint-file"]);
83 if(rr
.qtype
.getCode()==QType::A
) {
84 vector
<DNSRecord
> aset
;
85 aset
.push_back(DNSRecord(rr
));
86 t_RC
->replace(time(0), rr
.qname
, QType(QType::A
), aset
, vector
<std::shared_ptr
<RRSIGRecordContent
>>(), vector
<std::shared_ptr
<DNSRecord
>>(), true, boost::none
, validationState
); // auth, etc see above
87 } else if(rr
.qtype
.getCode()==QType::AAAA
) {
88 vector
<DNSRecord
> aaaaset
;
89 aaaaset
.push_back(DNSRecord(rr
));
90 t_RC
->replace(time(0), rr
.qname
, QType(QType::AAAA
), aaaaset
, vector
<std::shared_ptr
<RRSIGRecordContent
>>(), vector
<std::shared_ptr
<DNSRecord
>>(), true, boost::none
, validationState
);
91 } else if(rr
.qtype
.getCode()==QType::NS
) {
92 rr
.content
=toLower(rr
.content
);
93 nsset
.push_back(DNSRecord(rr
));
97 t_RC
->doWipeCache(g_rootdnsname
, false, QType::NS
);
98 t_RC
->replace(time(0), g_rootdnsname
, QType(QType::NS
), nsset
, vector
<std::shared_ptr
<RRSIGRecordContent
>>(), vector
<std::shared_ptr
<DNSRecord
>>(), false, boost::none
, validationState
); // and stuff in the cache
101 static void makeNameToIPZone(std::shared_ptr
<SyncRes::domainmap_t
> newMap
, const DNSName
& hostname
, const string
& ip
)
103 SyncRes::AuthDomain ad
;
104 ad
.d_rdForward
=false;
108 dr
.d_place
=DNSResourceRecord::ANSWER
;
110 dr
.d_type
=QType::SOA
;
112 dr
.d_content
= DNSRecordContent::mastermake(QType::SOA
, 1, "localhost. root 1 604800 86400 2419200 604800");
114 ad
.d_records
.insert(dr
);
117 dr
.d_content
=std::make_shared
<NSRecordContent
>("localhost.");
119 ad
.d_records
.insert(dr
);
122 dr
.d_content
= DNSRecordContent::mastermake(QType::A
, 1, ip
);
123 ad
.d_records
.insert(dr
);
125 if(newMap
->count(dr
.d_name
)) {
126 L
<<Logger::Warning
<<"Hosts file will not overwrite zone '"<<dr
.d_name
<<"' already loaded"<<endl
;
129 L
<<Logger::Warning
<<"Inserting forward zone '"<<dr
.d_name
<<"' based on hosts file"<<endl
;
131 (*newMap
)[ad
.d_name
]=ad
;
135 //! parts[0] must be an IP address, the rest must be host names
136 static void makeIPToNamesZone(std::shared_ptr
<SyncRes::domainmap_t
> newMap
, const vector
<string
>& parts
)
138 string address
=parts
[0];
139 vector
<string
> ipparts
;
140 stringtok(ipparts
, address
,".");
142 SyncRes::AuthDomain ad
;
143 ad
.d_rdForward
=false;
146 for(int n
=ipparts
.size()-1; n
>=0 ; --n
) {
147 dr
.d_name
.appendRawLabel(ipparts
[n
]);
149 dr
.d_name
.appendRawLabel("in-addr");
150 dr
.d_name
.appendRawLabel("arpa");
152 dr
.d_place
=DNSResourceRecord::ANSWER
;
154 dr
.d_type
=QType::SOA
;
155 dr
.d_content
=DNSRecordContent::mastermake(QType::SOA
, 1, "localhost. root 1 604800 86400 2419200 604800");
157 ad
.d_records
.insert(dr
);
160 dr
.d_content
=std::make_shared
<NSRecordContent
>(DNSName("localhost."));
162 ad
.d_records
.insert(dr
);
163 dr
.d_type
=QType::PTR
;
165 if(ipparts
.size()==4) // otherwise this is a partial zone
166 for(unsigned int n
=1; n
< parts
.size(); ++n
) {
167 dr
.d_content
=DNSRecordContent::mastermake(QType::PTR
, 1, DNSName(parts
[n
]).toString()); // XXX FIXME DNSNAME PAIN CAN THIS BE RIGHT?
168 ad
.d_records
.insert(dr
);
171 if(newMap
->count(dr
.d_name
)) {
172 L
<<Logger::Warning
<<"Will not overwrite zone '"<<dr
.d_name
<<"' already loaded"<<endl
;
175 if(ipparts
.size()==4)
176 L
<<Logger::Warning
<<"Inserting reverse zone '"<<dr
.d_name
<<"' based on hosts file"<<endl
;
177 ad
.d_name
= dr
.d_name
;
178 (*newMap
)[ad
.d_name
]=ad
;
184 /* mission in life: parse three cases
191 ComboAddress
parseIPAndPort(const std::string
& input
, uint16_t port
)
193 if(input
.find(':') == string::npos
|| input
.empty()) // common case
194 return ComboAddress(input
, port
);
196 pair
<string
,string
> both
;
199 both
=splitField(input
,':');
200 uint16_t newport
=static_cast<uint16_t>(pdns_stou(both
.second
));
201 return ComboAddress(both
.first
, newport
);
205 if(input
[0]=='[') { // case 4
206 both
=splitField(input
.substr(1),']');
207 return ComboAddress(both
.first
, both
.second
.empty() ? port
: static_cast<uint16_t>(pdns_stou(both
.second
.substr(1))));
210 return ComboAddress(input
, port
); // case 3
214 void convertServersForAD(const std::string
& input
, SyncRes::AuthDomain
& ad
, const char* sepa
, bool verbose
=true)
216 vector
<string
> servers
;
217 stringtok(servers
, input
, sepa
);
218 ad
.d_servers
.clear();
220 for(vector
<string
>::const_iterator iter
= servers
.begin(); iter
!= servers
.end(); ++iter
) {
221 if(verbose
&& iter
!= servers
.begin())
224 ComboAddress addr
=parseIPAndPort(*iter
, 53);
226 L
<<addr
.toStringWithPort();
227 ad
.d_servers
.push_back(addr
);
233 void* pleaseWipeNegCache()
235 SyncRes::clearNegCache();
239 void* pleaseUseNewSDomainsMap(std::shared_ptr
<SyncRes::domainmap_t
> newmap
)
241 SyncRes::setDomainMap(newmap
);
245 string
reloadAuthAndForwards()
247 std::shared_ptr
<SyncRes::domainmap_t
> original
=SyncRes::getDomainMap();
250 L
<<Logger::Warning
<<"Reloading zones, purging data from cache"<<endl
;
253 for(const auto& i
: *original
) {
254 for(const auto& j
: i
.second
.d_records
)
255 broadcastAccFunction
<uint64_t>(boost::bind(pleaseWipeCache
, j
.d_name
, false));
259 string configname
=::arg()["config-dir"]+"/recursor.conf";
260 cleanSlashes(configname
);
262 if(!::arg().preParseFile(configname
.c_str(), "forward-zones"))
263 throw runtime_error("Unable to re-parse configuration file '"+configname
+"'");
264 ::arg().preParseFile(configname
.c_str(), "forward-zones-file");
265 ::arg().preParseFile(configname
.c_str(), "forward-zones-recurse");
266 ::arg().preParseFile(configname
.c_str(), "auth-zones");
267 ::arg().preParseFile(configname
.c_str(), "export-etc-hosts", "off");
268 ::arg().preParseFile(configname
.c_str(), "serve-rfc1918");
269 ::arg().preParseFile(configname
.c_str(), "include-dir");
270 ::arg().preParse(g_argc
, g_argv
, "include-dir");
272 // then process includes
273 std::vector
<std::string
> extraConfigs
;
274 ::arg().gatherIncludes(extraConfigs
);
276 for(const std::string
& fn
: extraConfigs
) {
277 if(!::arg().preParseFile(fn
.c_str(), "forward-zones", ::arg()["forward-zones"]))
278 throw runtime_error("Unable to re-parse configuration file include '"+fn
+"'");
279 ::arg().preParseFile(fn
.c_str(), "forward-zones-file", ::arg()["forward-zones-file"]);
280 ::arg().preParseFile(fn
.c_str(), "forward-zones-recurse", ::arg()["forward-zones-recurse"]);
281 ::arg().preParseFile(fn
.c_str(), "auth-zones",::arg()["auth-zones"]);
282 ::arg().preParseFile(fn
.c_str(), "export-etc-hosts",::arg()["export-etc-hosts"]);
283 ::arg().preParseFile(fn
.c_str(), "serve-rfc1918",::arg()["serve-rfc1918"]);
286 ::arg().preParse(g_argc
, g_argv
, "forward-zones");
287 ::arg().preParse(g_argc
, g_argv
, "forward-zones-file");
288 ::arg().preParse(g_argc
, g_argv
, "forward-zones-recurse");
289 ::arg().preParse(g_argc
, g_argv
, "auth-zones");
290 ::arg().preParse(g_argc
, g_argv
, "export-etc-hosts");
291 ::arg().preParse(g_argc
, g_argv
, "serve-rfc1918");
293 std::shared_ptr
<SyncRes::domainmap_t
> newDomainMap
= parseAuthAndForwards();
295 // purge again - new zones need to blank out the cache
296 for(const auto& i
: *newDomainMap
) {
297 broadcastAccFunction
<uint64_t>(boost::bind(pleaseWipeCache
, i
.first
, true));
298 broadcastAccFunction
<uint64_t>(boost::bind(pleaseWipePacketCache
, i
.first
, true));
299 broadcastAccFunction
<uint64_t>(boost::bind(pleaseWipeAndCountNegCache
, i
.first
, true));
302 broadcastFunction(boost::bind(pleaseUseNewSDomainsMap
, newDomainMap
));
305 catch(std::exception
& e
) {
306 L
<<Logger::Error
<<"Encountered error reloading zones, keeping original data: "<<e
.what()<<endl
;
308 catch(PDNSException
& ae
) {
309 L
<<Logger::Error
<<"Encountered error reloading zones, keeping original data: "<<ae
.reason
<<endl
;
312 L
<<Logger::Error
<<"Encountered unknown error reloading zones, keeping original data"<<endl
;
314 return "reloading failed, see log\n";
318 void RPZIXFRTracker(const ComboAddress
& master
, const DNSName
& zoneName
, boost::optional
<DNSFilterEngine::Policy
> defpol
, uint32_t maxTTL
, size_t zoneIdx
, const TSIGTriplet
& tt
, shared_ptr
<SOARecordContent
> oursr
, size_t maxReceivedBytes
, const ComboAddress
& localAddress
)
320 uint32_t refresh
= oursr
->d_st
.refresh
;
327 L
<<Logger::Info
<<"Getting IXFR deltas for "<<zoneName
<<" from "<<master
.toStringWithPort()<<", our serial: "<<getRR
<SOARecordContent
>(dr
)->d_st
.serial
<<endl
;
328 vector
<pair
<vector
<DNSRecord
>, vector
<DNSRecord
> > > deltas
;
330 ComboAddress
local(localAddress
);
331 if (local
== ComboAddress())
332 local
= getQueryLocalAddress(master
.sin4
.sin_family
, 0);
335 deltas
= getIXFRDeltas(master
, zoneName
, dr
, tt
, &local
, maxReceivedBytes
);
336 } catch(std::runtime_error
& e
){
337 L
<<Logger::Warning
<<e
.what()<<endl
;
342 L
<<Logger::Info
<<"Processing "<<deltas
.size()<<" delta"<<addS(deltas
)<<" for RPZ "<<zoneName
<<endl
;
344 auto luaconfsLocal
= g_luaconfs
.getLocal();
345 const std::shared_ptr
<DNSFilterEngine::Zone
> oldZone
= luaconfsLocal
->dfe
.getZone(zoneIdx
);
346 /* we need to make a _full copy_ of the zone we are going to work on */
347 std::shared_ptr
<DNSFilterEngine::Zone
> newZone
= std::make_shared
<DNSFilterEngine::Zone
>(*oldZone
);
349 int totremove
=0, totadd
=0;
350 for(const auto& delta
: deltas
) {
351 const auto& remove
= delta
.first
;
352 const auto& add
= delta
.second
;
354 L
<<Logger::Warning
<<"IXFR update is a whole new zone"<<endl
;
357 for(const auto& rr
: remove
) { // should always contain the SOA
358 if(rr
.d_type
== QType::NS
)
360 if(rr
.d_type
== QType::SOA
) {
361 auto oldsr
= getRR
<SOARecordContent
>(rr
);
362 if(oldsr
&& oldsr
->d_st
.serial
== oursr
->d_st
.serial
) {
363 // cout<<"Got good removal of SOA serial "<<oldsr->d_st.serial<<endl;
366 L
<<Logger::Error
<<"GOT WRONG SOA SERIAL REMOVAL, SHOULD TRIGGER WHOLE RELOAD"<<endl
;
370 L
<<Logger::Debug
<<"Had removal of "<<rr
.d_name
<<endl
;
371 RPZRecordToPolicy(rr
, newZone
, false, defpol
, maxTTL
);
375 for(const auto& rr
: add
) { // should always contain the new SOA
376 if(rr
.d_type
== QType::NS
)
378 if(rr
.d_type
== QType::SOA
) {
379 auto newsr
= getRR
<SOARecordContent
>(rr
);
380 // L<<Logger::Info<<"New SOA serial for "<<zoneName<<": "<<newsr->d_st.serial<<endl;
387 L
<<Logger::Debug
<<"Had addition of "<<rr
.d_name
<<endl
;
388 RPZRecordToPolicy(rr
, newZone
, true, defpol
, maxTTL
);
392 L
<<Logger::Info
<<"Had "<<totremove
<<" RPZ removal"<<addS(totremove
)<<", "<<totadd
<<" addition"<<addS(totadd
)<<" for "<<zoneName
<<" New serial: "<<oursr
->d_st
.serial
<<endl
;
394 /* we need to replace the existing zone with the new one,
395 but we don't want to touch anything else, especially other zones,
396 since they might have been updated by another RPZ IXFR tracker thread.
398 g_luaconfs
.modify([zoneIdx
, &newZone
](LuaConfigItems
& lci
) {
399 lci
.dfe
.setZone(zoneIdx
, newZone
);
404 std::shared_ptr
<SyncRes::domainmap_t
> parseAuthAndForwards()
406 TXTRecordContent::report();
407 OPTRecordContent::report();
409 auto newMap
= std::make_shared
<SyncRes::domainmap_t
>();
411 typedef vector
<string
> parts_t
;
413 const char *option_names
[3]={"auth-zones", "forward-zones", "forward-zones-recurse"};
414 for(int n
=0; n
< 3 ; ++n
) {
416 stringtok(parts
, ::arg()[option_names
[n
]], " ,\t\n\r");
417 for(parts_t::const_iterator iter
= parts
.begin(); iter
!= parts
.end(); ++iter
) {
418 SyncRes::AuthDomain ad
;
419 if ((*iter
).find('=') == string::npos
)
420 throw PDNSException("Error parsing '" + *iter
+ "', missing =");
421 pair
<string
,string
> headers
=splitField(*iter
, '=');
423 trim(headers
.second
);
424 // headers.first=toCanonic("", headers.first);
426 ad
.d_rdForward
= false;
427 L
<<Logger::Error
<<"Parsing authoritative data for zone '"<<headers
.first
<<"' from file '"<<headers
.second
<<"'"<<endl
;
428 ZoneParserTNG
zpt(headers
.second
, DNSName(headers
.first
));
429 DNSResourceRecord rr
;
434 dr
.d_place
=DNSResourceRecord::ANSWER
;
436 catch(std::exception
&e
) {
437 throw PDNSException("Error parsing record '"+rr
.qname
.toString()+"' of type "+rr
.qtype
.getName()+" in zone '"+headers
.first
+"' from file '"+headers
.second
+"': "+e
.what());
440 throw PDNSException("Error parsing record '"+rr
.qname
.toString()+"' of type "+rr
.qtype
.getName()+" in zone '"+headers
.first
+"' from file '"+headers
.second
+"'");
443 ad
.d_records
.insert(dr
);
447 L
<<Logger::Error
<<"Redirecting queries for zone '"<<headers
.first
<<"' ";
449 L
<<"with recursion ";
450 ad
.d_rdForward
= true;
452 else ad
.d_rdForward
= false;
455 convertServersForAD(headers
.second
, ad
, ";");
457 ad
.d_rdForward
= true;
461 ad
.d_name
= DNSName(headers
.first
);
462 (*newMap
)[ad
.d_name
]=ad
;
466 if(!::arg()["forward-zones-file"].empty()) {
467 L
<<Logger::Warning
<<"Reading zone forwarding information from '"<<::arg()["forward-zones-file"]<<"'"<<endl
;
468 SyncRes::AuthDomain ad
;
469 FILE *rfp
=fopen(::arg()["forward-zones-file"].c_str(), "r");
472 throw PDNSException("Error opening forward-zones-file '"+::arg()["forward-zones-file"]+"': "+stringerror());
475 shared_ptr
<FILE> fp
=shared_ptr
<FILE>(rfp
, fclose
);
479 uint64_t before
= newMap
->size();
480 while(linenum
++, stringfgets(fp
.get(), line
)) {
482 if (line
[0] == '#') // Comment line, skip to the next line
484 string domain
, instructions
;
485 tie(domain
, instructions
)=splitField(line
, '=');
486 instructions
= splitField(instructions
, '#').first
; // Remove EOL comments
489 if(domain
.empty() && instructions
.empty()) { // empty line
492 if(boost::starts_with(domain
,"+")) {
493 domain
=domain
.c_str()+1;
494 ad
.d_rdForward
= true;
497 ad
.d_rdForward
= false;
499 throw PDNSException("Error parsing line "+std::to_string(linenum
)+" of " +::arg()["forward-zones-file"]);
503 convertServersForAD(instructions
, ad
, ",; ", false);
506 throw PDNSException("Conversion error parsing line "+std::to_string(linenum
)+" of " +::arg()["forward-zones-file"]);
509 ad
.d_name
= DNSName(domain
);
510 (*newMap
)[ad
.d_name
]=ad
;
512 L
<<Logger::Warning
<<"Done parsing " << newMap
->size() - before
<<" forwarding instructions from file '"<<::arg()["forward-zones-file"]<<"'"<<endl
;
515 if(::arg().mustDo("export-etc-hosts")) {
517 string fname
=::arg()["etc-hosts-file"];
519 ifstream
ifs(fname
.c_str());
521 L
<<Logger::Warning
<<"Could not open /etc/hosts for reading"<<endl
;
524 string searchSuffix
= ::arg()["export-etc-hosts-search-suffix"];
525 string::size_type pos
;
526 while(getline(ifs
,line
)) {
528 if(pos
!=string::npos
)
534 stringtok(parts
, line
, "\t\r\n ");
535 if(parts
[0].find(':')!=string::npos
)
538 for(unsigned int n
=1; n
< parts
.size(); ++n
) {
539 if(searchSuffix
.empty() || parts
[n
].find('.') != string::npos
)
540 makeNameToIPZone(newMap
, DNSName(parts
[n
]), parts
[0]);
542 DNSName canonic
=toCanonic(DNSName(searchSuffix
), parts
[n
]); /// XXXX DNSName pain
543 if(canonic
!= DNSName(parts
[n
])) { // XXX further DNSName pain
544 makeNameToIPZone(newMap
, canonic
, parts
[0]);
548 makeIPToNamesZone(newMap
, parts
);
552 if(::arg().mustDo("serve-rfc1918")) {
553 L
<<Logger::Warning
<<"Inserting rfc 1918 private space zones"<<endl
;
555 parts
.push_back("127");
556 makeIPToNamesZone(newMap
, parts
);
558 makeIPToNamesZone(newMap
, parts
);
561 makeIPToNamesZone(newMap
, parts
);
562 for(int n
=16; n
< 32; n
++) {
563 parts
[0]="172."+std::to_string(n
);
564 makeIPToNamesZone(newMap
,parts
);