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.
23 #include "rec-zonetocache.hh"
26 #include "zoneparser-tng.hh"
27 #include "query-local-address.hh"
28 #include "axfr-retriever.hh"
29 #include "validate-recursor.hh"
31 #include "rec-lua-conf.hh"
33 #include "validate.hh"
36 #include "minicurl.hh"
43 ZoneData(const std::shared_ptr
<Logr::Logger
>& log
, const std::string
& zone
) :
46 d_now(time(nullptr)) {}
48 // Potentially the two fields below could be merged into a single map. ATM it is not clear to me
49 // if that would make the code easier to read.
50 std::map
<pair
<DNSName
, QType
>, vector
<DNSRecord
>> d_all
;
51 std::map
<pair
<DNSName
, QType
>, vector
<shared_ptr
<const RRSIGRecordContent
>>> d_sigs
;
53 // Maybe use a SuffixMatchTree?
54 std::set
<DNSName
> d_delegations
;
56 std::shared_ptr
<Logr::Logger
> d_log
;
60 [[nodiscard
]] bool isRRSetAuth(const DNSName
& qname
, QType qtype
) const;
61 void parseDRForCache(DNSRecord
& resourceRecord
);
62 pdns::ZoneMD::Result
getByAXFR(const RecZoneToCache::Config
& config
, pdns::ZoneMD
& zonemd
);
63 pdns::ZoneMD::Result
processLines(const std::vector
<std::string
>& lines
, const RecZoneToCache::Config
& config
, pdns::ZoneMD
& zonemd
);
64 void ZoneToCache(const RecZoneToCache::Config
& config
);
65 vState
dnssecValidate(pdns::ZoneMD
& zonemd
, size_t& zonemdCount
) const;
68 bool ZoneData::isRRSetAuth(const DNSName
& qname
, QType qtype
) const
70 DNSName
delegatedZone(qname
);
71 if (qtype
== QType::DS
) {
72 delegatedZone
.chopOff();
74 bool isDelegated
= false;
76 if (d_delegations
.count(delegatedZone
) > 0) {
80 delegatedZone
.chopOff();
81 if (delegatedZone
== g_rootdnsname
|| delegatedZone
== d_zone
) {
88 void ZoneData::parseDRForCache(DNSRecord
& dnsRecord
)
90 if (dnsRecord
.d_class
!= QClass::IN
) {
93 const auto key
= pair(dnsRecord
.d_name
, dnsRecord
.d_type
);
95 dnsRecord
.d_ttl
+= d_now
;
97 switch (dnsRecord
.d_type
) {
102 const auto rrsig
= getRR
<RRSIGRecordContent
>(dnsRecord
);
103 if (rrsig
== nullptr) {
106 const auto sigkey
= pair(key
.first
, rrsig
->d_type
);
107 auto found
= d_sigs
.find(sigkey
);
108 if (found
!= d_sigs
.end()) {
109 found
->second
.push_back(rrsig
);
112 vector
<shared_ptr
<const RRSIGRecordContent
>> sigsrr
;
113 sigsrr
.push_back(rrsig
);
114 d_sigs
.insert({sigkey
, sigsrr
});
119 if (dnsRecord
.d_name
!= d_zone
) {
120 d_delegations
.insert(dnsRecord
.d_name
);
127 auto found
= d_all
.find(key
);
128 if (found
!= d_all
.end()) {
129 found
->second
.push_back(dnsRecord
);
132 vector
<DNSRecord
> dnsRecords
;
133 dnsRecords
.push_back(dnsRecord
);
134 d_all
.insert({key
, dnsRecords
});
138 pdns::ZoneMD::Result
ZoneData::getByAXFR(const RecZoneToCache::Config
& config
, pdns::ZoneMD
& zonemd
)
140 ComboAddress primary
= ComboAddress(config
.d_sources
.at(0), 53);
141 uint16_t axfrTimeout
= config
.d_timeout
;
142 size_t maxReceivedBytes
= config
.d_maxReceivedBytes
;
143 const TSIGTriplet tsigTriplet
= config
.d_tt
;
144 ComboAddress local
= config
.d_local
;
145 if (local
== ComboAddress()) {
146 local
= pdns::getQueryLocalAddress(primary
.sin4
.sin_family
, 0);
149 AXFRRetriever
axfr(primary
, d_zone
, tsigTriplet
, &local
, maxReceivedBytes
, axfrTimeout
);
151 vector
<DNSRecord
> chunk
;
152 time_t axfrStart
= time(nullptr);
153 time_t axfrNow
= time(nullptr);
155 // coverity[store_truncates_time_t]
156 while (axfr
.getChunk(nop
, &chunk
, (axfrStart
+ axfrTimeout
- axfrNow
)) != 0) {
157 for (auto& dnsRecord
: chunk
) {
158 if (config
.d_zonemd
!= pdns::ZoneMD::Config::Ignore
) {
159 zonemd
.readRecord(dnsRecord
);
161 parseDRForCache(dnsRecord
);
163 axfrNow
= time(nullptr);
164 if (axfrNow
< axfrStart
|| axfrNow
- axfrStart
> axfrTimeout
) {
165 throw std::runtime_error("Total AXFR time for zoneToCache exceeded!");
168 if (config
.d_zonemd
!= pdns::ZoneMD::Config::Ignore
) {
169 bool validationDone
= false;
170 bool validationSuccess
= false;
171 zonemd
.verify(validationDone
, validationSuccess
);
172 d_log
->info(Logr::Info
, "ZONEMD digest validation", "validationDone", Logging::Loggable(validationDone
),
173 "validationSuccess", Logging::Loggable(validationSuccess
));
174 if (!validationDone
) {
175 return pdns::ZoneMD::Result::NoValidationDone
;
177 if (!validationSuccess
) {
178 return pdns::ZoneMD::Result::ValidationFailure
;
181 return pdns::ZoneMD::Result::OK
;
184 static std::vector
<std::string
> getLinesFromFile(const std::string
& file
)
187 std::vector
<std::string
> lines
;
188 std::ifstream
stream(file
);
190 throw std::runtime_error("Cannot read file: " + file
);
193 while (std::getline(stream
, line
)) {
194 lines
.push_back(line
);
199 static std::vector
<std::string
> getURL(const RecZoneToCache::Config
& config
)
201 std::vector
<std::string
> lines
;
204 ComboAddress local
= config
.d_local
;
205 std::string reply
= miniCurl
.getURL(config
.d_sources
.at(0), nullptr, local
== ComboAddress() ? nullptr : &local
, static_cast<int>(config
.d_timeout
), false, true);
206 if (config
.d_maxReceivedBytes
> 0 && reply
.size() > config
.d_maxReceivedBytes
) {
207 // We should actually detect this *during* the GET
208 throw std::runtime_error("Retrieved data exceeds maxReceivedBytes");
210 std::istringstream
stream(reply
);
212 while (std::getline(stream
, line
)) {
213 lines
.push_back(line
);
216 throw std::runtime_error("url method configured but libcurl not compiled in");
221 pdns::ZoneMD::Result
ZoneData::processLines(const vector
<string
>& lines
, const RecZoneToCache::Config
& config
, pdns::ZoneMD
& zonemd
)
223 DNSResourceRecord drr
;
224 ZoneParserTNG
zpt(lines
, d_zone
, true);
225 zpt
.setMaxGenerateSteps(1);
226 zpt
.setMaxIncludes(0);
228 while (zpt
.get(drr
)) {
229 DNSRecord
dnsRecord(drr
);
230 if (config
.d_zonemd
!= pdns::ZoneMD::Config::Ignore
) {
231 zonemd
.readRecord(dnsRecord
);
233 parseDRForCache(dnsRecord
);
235 if (config
.d_zonemd
!= pdns::ZoneMD::Config::Ignore
) {
236 bool validationDone
= false;
237 bool validationSuccess
= false;
238 zonemd
.verify(validationDone
, validationSuccess
);
239 d_log
->info(Logr::Info
, "ZONEMD digest validation", "validationDone", Logging::Loggable(validationDone
),
240 "validationSuccess", Logging::Loggable(validationSuccess
));
241 if (!validationDone
) {
242 return pdns::ZoneMD::Result::NoValidationDone
;
244 if (!validationSuccess
) {
245 return pdns::ZoneMD::Result::ValidationFailure
;
248 return pdns::ZoneMD::Result::OK
;
251 vState
ZoneData::dnssecValidate(pdns::ZoneMD
& zonemd
, size_t& zonemdCount
) const
253 pdns::validation::ValidationContext validationContext
;
254 validationContext
.d_nsec3IterationsRemainingQuota
= std::numeric_limits
<decltype(validationContext
.d_nsec3IterationsRemainingQuota
)>::max();
257 SyncRes
resolver({d_now
, 0});
258 resolver
.setDoDNSSEC(true);
259 resolver
.setDNSSECValidationRequested(true);
261 dsmap_t dsmap
; // Actually a set
262 vState dsState
= resolver
.getDSRecords(d_zone
, dsmap
, false, 0, "");
263 if (dsState
!= vState::Secure
) {
268 sortedRecords_t records
;
269 if (zonemd
.getDNSKEYs().empty()) {
270 return vState::BogusUnableToGetDNSKEYs
;
272 for (const auto& key
: zonemd
.getDNSKEYs()) {
273 dnsKeys
.emplace(key
);
274 records
.emplace(key
);
278 vState dnsKeyState
= validateDNSKeysAgainstDS(d_now
, d_zone
, dsmap
, dnsKeys
, records
, zonemd
.getRRSIGs(), validKeys
, std::nullopt
, validationContext
);
279 if (dnsKeyState
!= vState::Secure
) {
283 if (validKeys
.empty()) {
284 return vState::BogusNoValidDNSKEY
;
287 auto zonemdRecords
= zonemd
.getZONEMDs();
288 zonemdCount
= zonemdRecords
.size();
290 // De we need to do a denial validation?
291 if (zonemdCount
== 0) {
292 const auto& nsecs
= zonemd
.getNSECs();
293 const auto& nsec3s
= zonemd
.getNSEC3s();
296 vState nsecValidationStatus
= vState::Indeterminate
;
298 if (!nsecs
.records
.empty() && !nsecs
.signatures
.empty()) {
299 // Valdidate the NSEC
300 nsecValidationStatus
= validateWithKeySet(d_now
, d_zone
, nsecs
.records
, nsecs
.signatures
, validKeys
, std::nullopt
, validationContext
);
301 csp
.emplace(std::pair(d_zone
, QType::NSEC
), nsecs
);
303 else if (!nsec3s
.records
.empty() && !nsec3s
.signatures
.empty()) {
304 // Validate NSEC3PARAMS
306 for (const auto& rec
: zonemd
.getNSEC3Params()) {
307 records
.emplace(rec
);
309 nsecValidationStatus
= validateWithKeySet(d_now
, d_zone
, records
, zonemd
.getRRSIGs(), validKeys
, std::nullopt
, validationContext
);
310 if (nsecValidationStatus
!= vState::Secure
) {
311 d_log
->info(Logr::Warning
, "NSEC3PARAMS records did not validate");
312 return nsecValidationStatus
;
314 // Valdidate the NSEC3
315 nsecValidationStatus
= validateWithKeySet(d_now
, zonemd
.getNSEC3Label(), nsec3s
.records
, nsec3s
.signatures
, validKeys
, std::nullopt
, validationContext
);
316 csp
.emplace(std::pair(zonemd
.getNSEC3Label(), QType::NSEC3
), nsec3s
);
319 d_log
->info(Logr::Warning
, "No NSEC(3) records and/or RRSIGS found to deny ZONEMD");
320 return vState::BogusInvalidDenial
;
323 if (nsecValidationStatus
!= vState::Secure
) {
324 d_log
->info(Logr::Warning
, "zone NSEC(3) record does not validate");
325 return nsecValidationStatus
;
328 auto denial
= getDenial(csp
, d_zone
, QType::ZONEMD
, false, false, validationContext
, std::nullopt
, true);
329 if (denial
== dState::NXQTYPE
) {
330 d_log
->info(Logr::Info
, "Validated denial of existence of ZONEMD record");
331 return vState::Secure
;
333 d_log
->info(Logr::Warning
, "No ZONEMD record, but NSEC(3) record does not deny it");
334 return vState::BogusInvalidDenial
;
337 // Collect the ZONEMD records and validate them using the validated DNSSKEYs
339 for (const auto& rec
: zonemdRecords
) {
340 records
.emplace(rec
);
342 return validateWithKeySet(d_now
, d_zone
, records
, zonemd
.getRRSIGs(), validKeys
, std::nullopt
, validationContext
);
345 void ZoneData::ZoneToCache(const RecZoneToCache::Config
& config
)
347 if (config
.d_sources
.size() > 1) {
348 d_log
->info(Logr::Warning
, "Multiple sources not yet supported, using first");
351 if (config
.d_dnssec
== pdns::ZoneMD::Config::Require
&& (g_dnssecmode
== DNSSECMode::Off
|| g_dnssecmode
== DNSSECMode::ProcessNoValidate
)) {
352 throw PDNSException("ZONEMD DNSSEC validation failure: DNSSEC validation is switched off but required by ZoneToCache");
355 // First scan all records collecting info about delegations and sigs
356 // A this moment, we ignore NSEC and NSEC3 records. It is not clear to me yet under which conditions
357 // they could be entered in into the (neg)cache.
359 auto zonemd
= pdns::ZoneMD(DNSName(config
.d_zone
));
360 pdns::ZoneMD::Result result
= pdns::ZoneMD::Result::OK
;
361 if (config
.d_method
== "axfr") {
362 d_log
->info(Logr::Info
, "Getting zone by AXFR");
363 result
= getByAXFR(config
, zonemd
);
366 vector
<string
> lines
;
367 if (config
.d_method
== "url") {
368 d_log
->info(Logr::Info
, "Getting zone by URL");
369 lines
= getURL(config
);
371 else if (config
.d_method
== "file") {
372 d_log
->info(Logr::Info
, "Getting zone from file");
373 lines
= getLinesFromFile(config
.d_sources
.at(0));
375 result
= processLines(lines
, config
, zonemd
);
378 // Validate DNSKEYs and ZONEMD, rest of records are validated on-demand by SyncRes
379 if (config
.d_dnssec
== pdns::ZoneMD::Config::Require
|| (g_dnssecmode
!= DNSSECMode::Off
&& g_dnssecmode
!= DNSSECMode::ProcessNoValidate
&& config
.d_dnssec
!= pdns::ZoneMD::Config::Ignore
)) {
380 size_t zonemdCount
= 0;
381 auto validationStatus
= dnssecValidate(zonemd
, zonemdCount
);
382 d_log
->info(Logr::Info
, "ZONEMD record related DNSSEC validation", "validationStatus", Logging::Loggable(validationStatus
),
383 "zonemdCount", Logging::Loggable(zonemdCount
));
384 if (config
.d_dnssec
== pdns::ZoneMD::Config::Require
&& validationStatus
!= vState::Secure
) {
385 throw PDNSException("ZONEMD required DNSSEC validation failed");
387 if (validationStatus
!= vState::Secure
&& validationStatus
!= vState::Insecure
) {
388 throw PDNSException("ZONEMD record DNSSEC validation failed");
392 if (config
.d_zonemd
== pdns::ZoneMD::Config::Require
&& result
!= pdns::ZoneMD::Result::OK
) {
393 // We do not accept NoValidationDone in this case
394 throw PDNSException("ZONEMD digest validation failure");
397 if (config
.d_zonemd
== pdns::ZoneMD::Config::Validate
&& result
== pdns::ZoneMD::Result::ValidationFailure
) {
398 throw PDNSException("ZONEMD digest validation failure");
402 // Rerun, now inserting the rrsets into the cache with associated sigs
403 d_now
= time(nullptr);
404 for (const auto& [key
, v
] : d_all
) {
405 const auto& [qname
, qtype
] = key
;
412 vector
<shared_ptr
<const RRSIGRecordContent
>> sigsrr
;
413 auto iter
= d_sigs
.find(key
);
414 if (iter
!= d_sigs
.end()) {
415 sigsrr
= iter
->second
;
417 bool auth
= isRRSetAuth(qname
, qtype
);
418 // Same decision as updateCacheFromRecords() (we do not test for NSEC since we skip those completely)
419 if (auth
|| (qtype
== QType::NS
|| qtype
== QType::A
|| qtype
== QType::AAAA
|| qtype
== QType::DS
)) {
420 g_recCache
->replace(d_now
, qname
, qtype
, v
, sigsrr
,
421 std::vector
<std::shared_ptr
<DNSRecord
>>(), auth
, d_zone
);
429 void RecZoneToCache::maintainStates(const map
<DNSName
, Config
>& configs
, map
<DNSName
, State
>& states
, uint64_t mygeneration
)
431 // Delete states that have no config
432 for (auto it
= states
.begin(); it
!= states
.end();) {
433 if (configs
.find(it
->first
) == configs
.end()) {
434 it
= states
.erase(it
);
440 // Reset states for which the config generation changed and create new states for new configs
441 for (const auto& config
: configs
) {
442 auto state
= states
.find(config
.first
);
443 if (state
!= states
.end()) {
444 if (state
->second
.d_generation
!= mygeneration
) {
445 state
->second
= {0, 0, mygeneration
};
449 states
.emplace(config
.first
, State
{0, 0, mygeneration
});
454 void RecZoneToCache::ZoneToCache(const RecZoneToCache::Config
& config
, RecZoneToCache::State
& state
)
456 if (state
.d_waittime
== 0 && state
.d_lastrun
> 0) {
460 if (state
.d_lastrun
> 0 && state
.d_lastrun
+ state
.d_waittime
> time(nullptr)) {
463 auto log
= g_slog
->withName("ztc")->withValues("zone", Logging::Loggable(config
.d_zone
));
465 state
.d_waittime
= config
.d_retryOnError
;
467 ZoneData
data(log
, config
.d_zone
);
468 data
.ZoneToCache(config
);
469 state
.d_waittime
= config
.d_refreshPeriod
;
470 log
->info(Logr::Info
, "Loaded zone into cache", "refresh", Logging::Loggable(state
.d_waittime
));
472 catch (const PDNSException
& e
) {
473 log
->error(Logr::Error
, e
.reason
, "Unable to load zone into cache, will retry", "exception", Logging::Loggable("PDNSException"), "refresh", Logging::Loggable(state
.d_waittime
));
475 catch (const std::runtime_error
& e
) {
476 log
->error(Logr::Error
, e
.what(), "Unable to load zone into cache, will retry", "exception", Logging::Loggable("std::runtime_error"), "refresh", Logging::Loggable(state
.d_waittime
));
479 log
->info(Logr::Error
, "Unable to load zone into cache, will retry", "refresh", Logging::Loggable(state
.d_waittime
));
481 state
.d_lastrun
= time(nullptr);