]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/auth-secondarycommunicator.cc
rec: allow exception to proxy protocal usage for specific listen addresses
[thirdparty/pdns.git] / pdns / auth-secondarycommunicator.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
26 #include "utility.hh"
27 #include "dnssecinfra.hh"
28 #include "dnsseckeeper.hh"
29 #include "base32.hh"
30 #include <cerrno>
31 #include "communicator.hh"
32 #include <set>
33 #include <boost/utility.hpp>
34 #include "dnsbackend.hh"
35 #include "ueberbackend.hh"
36 #include "packethandler.hh"
37 #include "axfr-retriever.hh"
38 #include "logger.hh"
39 #include "dns.hh"
40 #include "arguments.hh"
41 #include "auth-caches.hh"
42
43 #include "base64.hh"
44 #include "inflighter.cc"
45 #include "namespaces.hh"
46 #include "auth-main.hh"
47 #include "query-local-address.hh"
48
49 #include "ixfr.hh"
50
51 void CommunicatorClass::addSuckRequest(const DNSName& domain, const ComboAddress& primary, SuckRequest::RequestPriority priority, bool force)
52 {
53 auto data = d_data.lock();
54 SuckRequest sr;
55 sr.domain = domain;
56 sr.primary = primary;
57 sr.force = force;
58 sr.priorityAndOrder.first = priority;
59 sr.priorityAndOrder.second = data->d_sorthelper++;
60 pair<UniQueue::iterator, bool> res;
61
62 res = data->d_suckdomains.insert(sr);
63 if (res.second) {
64 d_suck_sem.post();
65 }
66 else {
67 data->d_suckdomains.modify(res.first, [priorityAndOrder = sr.priorityAndOrder](SuckRequest& so) {
68 if (priorityAndOrder.first < so.priorityAndOrder.first) {
69 so.priorityAndOrder = priorityAndOrder;
70 }
71 });
72 }
73 }
74
75 struct ZoneStatus
76 {
77 bool isDnssecZone{false};
78 bool isPresigned{false};
79 bool isNSEC3{false};
80 bool optOutFlag{false};
81 NSEC3PARAMRecordContent ns3pr;
82
83 bool isNarrow{false};
84 unsigned int soa_serial{0};
85 set<DNSName> nsset, qnames, secured;
86 uint32_t domain_id;
87 int numDeltas{0};
88 };
89
90 static bool catalogDiff(const DomainInfo& di, vector<CatalogInfo>& fromXFR, vector<CatalogInfo>& fromDB, const string& logPrefix)
91 {
92 extern CommunicatorClass Communicator;
93
94 bool doTransaction{true};
95 bool inTransaction{false};
96 CatalogInfo ciCreate, ciRemove;
97 std::unordered_map<DNSName, bool> clearCache;
98 vector<CatalogInfo> retrieve;
99
100 try {
101 sort(fromXFR.begin(), fromXFR.end());
102 sort(fromDB.begin(), fromDB.end());
103
104 auto xfr = fromXFR.cbegin();
105 auto db = fromDB.cbegin();
106
107 while (xfr != fromXFR.end() || db != fromDB.end()) {
108 bool create{false};
109 bool remove{false};
110
111 if (xfr != fromXFR.end() && (db == fromDB.end() || *xfr < *db)) { // create
112 ciCreate = *xfr;
113 create = true;
114 ++xfr;
115 }
116 else if (db != fromDB.end() && (xfr == fromXFR.end() || *db < *xfr)) { // remove
117 ciRemove = *db;
118 remove = true;
119 ++db;
120 }
121 else {
122 CatalogInfo ciXFR = *xfr;
123 CatalogInfo ciDB = *db;
124 if (ciDB.d_unique.empty() || ciXFR.d_unique == ciDB.d_unique) { // update
125 bool doOptions{false};
126
127 if (ciDB.d_unique.empty()) { // set unique
128 g_log << Logger::Warning << logPrefix << "set unique, zone '" << ciXFR.d_zone << "' is now a member" << endl;
129 ciDB.d_unique = ciXFR.d_unique;
130 doOptions = true;
131 }
132
133 if (ciXFR.d_coo != ciDB.d_coo) { // update coo
134 g_log << Logger::Warning << logPrefix << "update coo for zone '" << ciXFR.d_zone << "' to '" << ciXFR.d_coo << "'" << endl;
135 ciDB.d_coo = ciXFR.d_coo;
136 doOptions = true;
137 }
138
139 if (ciXFR.d_group != ciDB.d_group) { // update group
140 g_log << Logger::Warning << logPrefix << "update group for zone '" << ciXFR.d_zone << "' to '" << boost::join(ciXFR.d_group, ", ") << "'" << endl;
141 ciDB.d_group = ciXFR.d_group;
142 doOptions = true;
143 }
144
145 if (doOptions) { // update zone options
146 if (doTransaction && (inTransaction = di.backend->startTransaction(di.zone))) {
147 g_log << Logger::Warning << logPrefix << "backend transaction started" << endl;
148 doTransaction = false;
149 }
150
151 g_log << Logger::Warning << logPrefix << "update options for zone '" << ciXFR.d_zone << "'" << endl;
152 di.backend->setOptions(ciXFR.d_zone, ciDB.toJson());
153 }
154
155 if (di.primaries != ciDB.d_primaries) { // update primaries
156 if (doTransaction && (inTransaction = di.backend->startTransaction(di.zone))) {
157 g_log << Logger::Warning << logPrefix << "backend transaction started" << endl;
158 doTransaction = false;
159 }
160
161 vector<string> primaries;
162 for (const auto& primary : di.primaries) {
163 primaries.push_back(primary.toStringWithPortExcept(53));
164 }
165 g_log << Logger::Warning << logPrefix << "update primaries for zone '" << ciXFR.d_zone << "' to '" << boost::join(primaries, ", ") << "'" << endl;
166 di.backend->setPrimaries(ciXFR.d_zone, di.primaries);
167
168 retrieve.emplace_back(ciXFR);
169 }
170 }
171 else { // reset
172 ciCreate = *xfr;
173 ciRemove = *db;
174 create = true;
175 remove = true;
176 }
177 ++xfr;
178 ++db;
179 }
180
181 DomainInfo d;
182 if (create && remove) {
183 g_log << Logger::Warning << logPrefix << "zone '" << ciCreate.d_zone << "' state reset" << endl;
184 }
185 else if (create && di.backend->getDomainInfo(ciCreate.d_zone, d)) { // detect clash
186 CatalogInfo ci;
187 ci.fromJson(d.options, CatalogInfo::CatalogType::Consumer);
188
189 if (di.zone != d.catalog && di.zone == ci.d_coo) {
190 if (ciCreate.d_unique == ci.d_unique) {
191 g_log << Logger::Warning << logPrefix << "zone '" << d.zone << "' owner change without state reset, old catalog '" << d.catalog << "', new catalog '" << di.zone << "'" << endl;
192
193 if (doTransaction && (inTransaction = di.backend->startTransaction(di.zone))) {
194 g_log << Logger::Warning << logPrefix << "backend transaction started" << endl;
195 doTransaction = false;
196 }
197
198 di.backend->setPrimaries(ciCreate.d_zone, di.primaries);
199 di.backend->setOptions(ciCreate.d_zone, ciCreate.toJson());
200 di.backend->setCatalog(ciCreate.d_zone, di.zone);
201
202 retrieve.emplace_back(ciCreate);
203 continue;
204 }
205 g_log << Logger::Warning << logPrefix << "zone '" << d.zone << "' owner change with state reset, old catalog '" << d.catalog << "', new catalog '" << di.zone << "'" << endl;
206
207 ciRemove.d_zone = d.zone;
208 remove = true;
209 }
210 else {
211 g_log << Logger::Warning << logPrefix << "zone '" << d.zone << "' already exists";
212 if (!d.catalog.empty()) {
213 g_log << " in catalog '" << d.catalog;
214 }
215 g_log << "', create skipped" << endl;
216 continue;
217 }
218 }
219
220 if (remove) { // delete zone
221 if (doTransaction && (inTransaction = di.backend->startTransaction(di.zone))) {
222 g_log << Logger::Warning << logPrefix << "backend transaction started" << endl;
223 doTransaction = false;
224 }
225
226 g_log << Logger::Warning << logPrefix << "delete zone '" << ciRemove.d_zone << "'" << endl;
227 di.backend->deleteDomain(ciRemove.d_zone);
228
229 if (!create) {
230 clearCache[ciRemove.d_zone] = false;
231 }
232 }
233
234 if (create) { // create zone
235 if (doTransaction && (inTransaction = di.backend->startTransaction(di.zone))) {
236 g_log << Logger::Warning << logPrefix << "backend transaction started" << endl;
237 doTransaction = false;
238 }
239
240 g_log << Logger::Warning << logPrefix << "create zone '" << ciCreate.d_zone << "'" << endl;
241 di.backend->createDomain(ciCreate.d_zone, DomainInfo::Secondary, ciCreate.d_primaries, "");
242
243 di.backend->setPrimaries(ciCreate.d_zone, di.primaries);
244 di.backend->setOptions(ciCreate.d_zone, ciCreate.toJson());
245 di.backend->setCatalog(ciCreate.d_zone, di.zone);
246
247 clearCache[ciCreate.d_zone] = true;
248 retrieve.emplace_back(ciCreate);
249 }
250 }
251
252 if (inTransaction && di.backend->commitTransaction()) {
253 g_log << Logger::Warning << logPrefix << "backend transaction committed" << endl;
254 }
255
256 // Update zonecache and clear all caches
257 DomainInfo d;
258 for (const auto& zone : clearCache) {
259 if (g_zoneCache.isEnabled()) {
260 if (zone.second) {
261 if (di.backend->getDomainInfo(zone.first, d)) {
262 g_zoneCache.add(zone.first, d.id);
263 }
264 else {
265 g_log << Logger::Error << logPrefix << "new zone '" << zone.first << "' does not exists and was not inserted in the zone-cache" << endl;
266 }
267 }
268 else {
269 g_zoneCache.remove(zone.first);
270 }
271 }
272
273 DNSSECKeeper::clearCaches(zone.first);
274 purgeAuthCaches(zone.first.toString() + "$");
275 }
276
277 // retrieve new and updated zones with new primaries
278 auto primaries = di.primaries;
279 if (!primaries.empty()) {
280 for (auto& ret : retrieve) {
281 shuffle(primaries.begin(), primaries.end(), pdns::dns_random_engine());
282 const auto& primary = primaries.front();
283 Communicator.addSuckRequest(ret.d_zone, primary, SuckRequest::Notify);
284 }
285 }
286
287 return true;
288 }
289 catch (DBException& re) {
290 g_log << Logger::Error << logPrefix << "DBException " << re.reason << endl;
291 }
292 catch (PDNSException& pe) {
293 g_log << Logger::Error << logPrefix << "PDNSException " << pe.reason << endl;
294 }
295 catch (std::exception& re) {
296 g_log << Logger::Error << logPrefix << "std::exception " << re.what() << endl;
297 }
298
299 if (di.backend && inTransaction) {
300 g_log << Logger::Info << logPrefix << "aborting possible open transaction" << endl;
301 di.backend->abortTransaction();
302 }
303
304 return false;
305 }
306
307 static bool catalogProcess(const DomainInfo& di, vector<DNSResourceRecord>& rrs, string logPrefix)
308 {
309 logPrefix += "Catalog-Zone ";
310
311 vector<CatalogInfo> fromXFR, fromDB;
312 std::unordered_set<DNSName> dupcheck;
313
314 // From XFR
315 bool hasSOA{false};
316 bool zoneInvalid{false};
317 int hasVersion{0};
318
319 CatalogInfo ci;
320
321 vector<DNSResourceRecord> ret;
322
323 const auto compare = [](const DNSResourceRecord& a, const DNSResourceRecord& b) { return a.qname == b.qname ? a.qtype < b.qtype : a.qname.canonCompare(b.qname); };
324 sort(rrs.begin(), rrs.end(), compare);
325
326 DNSName rel;
327 DNSName unique;
328 for (auto& rr : rrs) {
329 if (di.zone == rr.qname) {
330 if (rr.qtype == QType::SOA) {
331 hasSOA = true;
332 continue;
333 }
334 if (rr.qtype == QType::NS) {
335 continue;
336 }
337 }
338
339 else if (rr.qname == DNSName("version") + di.zone && rr.qtype == QType::TXT) {
340 if (hasVersion) {
341 g_log << Logger::Warning << logPrefix << "zone '" << di.zone << "', multiple version records found, aborting" << endl;
342 return false;
343 }
344
345 if (rr.content == "\"1\"") {
346 hasVersion = 1;
347 }
348 else if (rr.content == "\"2\"") {
349 hasVersion = 2;
350 }
351 else {
352 g_log << Logger::Warning << logPrefix << "zone '" << di.zone << "', unsupported catalog zone schema version " << rr.content << ", aborting" << endl;
353 return false;
354 }
355 }
356
357 else if (rr.qname.isPartOf(DNSName("zones") + di.zone)) {
358 if (rel.empty() && !hasVersion) {
359 g_log << Logger::Warning << logPrefix << "zone '" << di.zone << "', catalog zone schema version missing, aborting" << endl;
360 return false;
361 }
362
363 rel = rr.qname.makeRelative(DNSName("zones") + di.zone);
364
365 if (rel.countLabels() == 1 && rr.qtype == QType::PTR) {
366 if (!unique.empty()) {
367 if (rel != unique) {
368 fromXFR.emplace_back(ci);
369 }
370 else {
371 g_log << Logger::Warning << logPrefix << "zone '" << di.zone << "', duplicate unique '" << unique << "'" << endl;
372 zoneInvalid = true;
373 }
374 }
375
376 unique = rel;
377
378 ci = {};
379 ci.setType(CatalogInfo::CatalogType::Consumer);
380 ci.d_zone = DNSName(rr.content);
381 ci.d_unique = unique;
382
383 if (!dupcheck.insert(ci.d_zone).second) {
384 g_log << Logger::Warning << logPrefix << "zone '" << di.zone << "', duplicate member zone'" << ci.d_zone << "'" << endl;
385 zoneInvalid = true;
386 }
387 }
388
389 else if (hasVersion == 2) {
390 if (rel == (DNSName("coo") + unique) && rr.qtype == QType::PTR) {
391 if (!ci.d_coo.empty()) {
392 g_log << Logger::Warning << logPrefix << "zone '" << di.zone << "', duplicate COO for unique '" << unique << "'" << endl;
393 zoneInvalid = true;
394 }
395 else {
396 ci.d_coo = DNSName(rr.content);
397 }
398 }
399 else if (rel == (DNSName("group") + unique) && rr.qtype == QType::TXT) {
400 std::string content = rr.content;
401 if (content.length() >= 2 && content.at(0) == '\"' && content.at(content.length() - 1) == '\"') { // TXT pain
402 content = content.substr(1, content.length() - 2);
403 }
404 ci.d_group.insert(content);
405 }
406 }
407 }
408 rr.disabled = true;
409 }
410 if (!ci.d_zone.empty()) {
411 fromXFR.emplace_back(ci);
412 }
413
414 if (!hasSOA || !hasVersion || zoneInvalid) {
415 g_log << Logger::Warning << logPrefix << "zone '" << di.zone << "' is invalid, skip updates" << endl;
416 return false;
417 }
418
419 // Get catalog ifo from db
420 if (!di.backend->getCatalogMembers(di.zone, fromDB, CatalogInfo::CatalogType::Consumer)) {
421 return false;
422 }
423
424 // Process
425 return catalogDiff(di, fromXFR, fromDB, logPrefix);
426 }
427
428 void CommunicatorClass::ixfrSuck(const DNSName& domain, const TSIGTriplet& tt, const ComboAddress& laddr, const ComboAddress& remote, ZoneStatus& zs, vector<DNSRecord>* axfr)
429 {
430 string logPrefix = "IXFR-in zone '" + domain.toLogString() + "', primary '" + remote.toString() + "', ";
431
432 UeberBackend B; // fresh UeberBackend
433
434 DomainInfo di;
435 di.backend = nullptr;
436 // bool transaction=false;
437 try {
438 DNSSECKeeper dk(&B); // reuse our UeberBackend copy for DNSSECKeeper
439
440 bool wrongDomainKind = false;
441 // this checks three error conditions, and sets wrongDomainKind if we hit the third & had an error
442 if (!B.getDomainInfo(domain, di) || !di.backend || (wrongDomainKind = true, di.kind != DomainInfo::Secondary)) { // di.backend and B are mostly identical
443 if (wrongDomainKind)
444 g_log << Logger::Warning << logPrefix << "can't determine backend, not configured as secondary" << endl;
445 else
446 g_log << Logger::Warning << logPrefix << "can't determine backend" << endl;
447 return;
448 }
449
450 uint16_t xfrTimeout = ::arg().asNum("axfr-fetch-timeout");
451 soatimes st;
452 memset(&st, 0, sizeof(st));
453 st.serial = di.serial;
454
455 DNSRecord drsoa;
456 drsoa.setContent(std::make_shared<SOARecordContent>(g_rootdnsname, g_rootdnsname, st));
457 auto deltas = getIXFRDeltas(remote, domain, drsoa, xfrTimeout, false, tt, laddr.sin4.sin_family ? &laddr : nullptr, ((size_t)::arg().asNum("xfr-max-received-mbytes")) * 1024 * 1024);
458 zs.numDeltas = deltas.size();
459 // cout<<"Got "<<deltas.size()<<" deltas from serial "<<di.serial<<", applying.."<<endl;
460
461 for (const auto& d : deltas) {
462 const auto& remove = d.first;
463 const auto& add = d.second;
464 // cout<<"Delta sizes: "<<remove.size()<<", "<<add.size()<<endl;
465
466 if (remove.empty()) { // we got passed an AXFR!
467 *axfr = add;
468 return;
469 }
470
471 // our hammer is 'replaceRRSet(domain_id, qname, qt, vector<DNSResourceRecord>& rrset)
472 // which thinks in terms of RRSETs
473 // however, IXFR does not, and removes and adds *records* (bummer)
474 // this means that we must group updates by {qname,qtype}, retrieve the RRSET, apply
475 // the add/remove updates, and replaceRRSet the whole thing.
476
477 map<pair<DNSName, uint16_t>, pair<vector<DNSRecord>, vector<DNSRecord>>> grouped;
478
479 for (const auto& x : remove)
480 grouped[{x.d_name, x.d_type}].first.push_back(x);
481 for (const auto& x : add)
482 grouped[{x.d_name, x.d_type}].second.push_back(x);
483
484 di.backend->startTransaction(domain, -1);
485 for (const auto& g : grouped) {
486 vector<DNSRecord> rrset;
487 {
488 DNSZoneRecord zrr;
489 di.backend->lookup(QType(g.first.second), g.first.first + domain, di.id);
490 while (di.backend->get(zrr)) {
491 zrr.dr.d_name.makeUsRelative(domain);
492 rrset.push_back(zrr.dr);
493 }
494 }
495 // O(N^2)!
496 rrset.erase(remove_if(rrset.begin(), rrset.end(),
497 [&g](const DNSRecord& dr) {
498 return count(g.second.first.cbegin(),
499 g.second.first.cend(), dr);
500 }),
501 rrset.end());
502 // the DNSRecord== operator compares on name, type, class and lowercase content representation
503
504 for (const auto& x : g.second.second) {
505 rrset.push_back(x);
506 }
507
508 vector<DNSResourceRecord> replacement;
509 for (const auto& dr : rrset) {
510 auto rr = DNSResourceRecord::fromWire(dr);
511 rr.qname += domain;
512 rr.domain_id = di.id;
513 if (dr.d_type == QType::SOA) {
514 // cout<<"New SOA: "<<x.d_content->getZoneRepresentation()<<endl;
515 auto sr = getRR<SOARecordContent>(dr);
516 zs.soa_serial = sr->d_st.serial;
517 }
518
519 replacement.push_back(rr);
520 }
521
522 di.backend->replaceRRSet(di.id, g.first.first + domain, QType(g.first.second), replacement);
523 }
524 di.backend->commitTransaction();
525 }
526 }
527 catch (std::exception& p) {
528 g_log << Logger::Error << logPrefix << "got exception (std::exception): " << p.what() << endl;
529 throw;
530 }
531 catch (PDNSException& p) {
532 g_log << Logger::Error << logPrefix << "got exception (PDNSException): " << p.reason << endl;
533 throw;
534 }
535 }
536
537 static bool processRecordForZS(const DNSName& domain, bool& firstNSEC3, DNSResourceRecord& rr, ZoneStatus& zs)
538 {
539 switch (rr.qtype.getCode()) {
540 case QType::NSEC3PARAM:
541 zs.ns3pr = NSEC3PARAMRecordContent(rr.content);
542 zs.isDnssecZone = zs.isNSEC3 = true;
543 zs.isNarrow = false;
544 return false;
545 case QType::NSEC3: {
546 NSEC3RecordContent ns3rc(rr.content);
547 if (firstNSEC3) {
548 zs.isDnssecZone = zs.isPresigned = true;
549 firstNSEC3 = false;
550 }
551 else if (zs.optOutFlag != (ns3rc.d_flags & 1))
552 throw PDNSException("Zones with a mixture of Opt-Out NSEC3 RRs and non-Opt-Out NSEC3 RRs are not supported.");
553 zs.optOutFlag = ns3rc.d_flags & 1;
554 if (ns3rc.isSet(QType::NS) && !(rr.qname == domain)) {
555 DNSName hashPart = rr.qname.makeRelative(domain);
556 zs.secured.insert(hashPart);
557 }
558 return false;
559 }
560
561 case QType::NSEC:
562 zs.isDnssecZone = zs.isPresigned = true;
563 return false;
564
565 case QType::NS:
566 if (rr.qname != domain)
567 zs.nsset.insert(rr.qname);
568 break;
569 }
570
571 zs.qnames.insert(rr.qname);
572
573 rr.domain_id = zs.domain_id;
574 return true;
575 }
576
577 /* So this code does a number of things.
578 1) It will AXFR a domain from a primary
579 The code can retrieve the current serial number in the database itself.
580 It may attempt an IXFR
581 2) It will filter the zone through a lua *filter* script
582 3) The code walks through the zone records do determine DNSSEC status (secured, nsec/nsec3, optout)
583 4) It inserts the zone into the database
584 With the right 'ordername' fields
585 5) It updates the Empty Non Terminals
586 */
587
588 static vector<DNSResourceRecord> doAxfr(const ComboAddress& raddr, const DNSName& domain, const TSIGTriplet& tt, const ComboAddress& laddr, unique_ptr<AuthLua4>& pdl, ZoneStatus& zs)
589 {
590 uint16_t axfr_timeout = ::arg().asNum("axfr-fetch-timeout");
591 vector<DNSResourceRecord> rrs;
592 AXFRRetriever retriever(raddr, domain, tt, (laddr.sin4.sin_family == 0) ? nullptr : &laddr, ((size_t)::arg().asNum("xfr-max-received-mbytes")) * 1024 * 1024, axfr_timeout);
593 Resolver::res_t recs;
594 bool first = true;
595 bool firstNSEC3{true};
596 bool soa_received{false};
597 string logPrefix = "AXFR-in zone '" + domain.toLogString() + "', primary '" + raddr.toString() + "', ";
598 while (retriever.getChunk(recs, nullptr, axfr_timeout)) {
599 if (first) {
600 g_log << Logger::Notice << logPrefix << "retrieval started" << endl;
601 first = false;
602 }
603
604 for (auto& rec : recs) {
605 rec.qname.makeUsLowerCase();
606 if (rec.qtype.getCode() == QType::OPT || rec.qtype.getCode() == QType::TSIG) // ignore EDNS0 & TSIG
607 continue;
608
609 if (!rec.qname.isPartOf(domain)) {
610 g_log << Logger::Warning << logPrefix << "primary tried to sneak in out-of-zone data '" << rec.qname << "'|" << rec.qtype.toString() << ", ignoring" << endl;
611 continue;
612 }
613
614 vector<DNSResourceRecord> out;
615 if (!pdl || !pdl->axfrfilter(raddr, domain, rec, out)) {
616 out.push_back(rec); // if axfrfilter didn't do anything, we put our record in 'out' ourselves
617 }
618
619 for (auto& rr : out) {
620 if (!rr.qname.isPartOf(domain)) {
621 g_log << Logger::Error << logPrefix << "axfrfilter() filter tried to sneak in out-of-zone data '" << rr.qname << "'|" << rr.qtype.toString() << ", ignoring" << endl;
622 continue;
623 }
624 if (!processRecordForZS(domain, firstNSEC3, rr, zs))
625 continue;
626 if (rr.qtype.getCode() == QType::SOA) {
627 if (soa_received)
628 continue; // skip the last SOA
629 SOAData sd;
630 fillSOAData(rr.content, sd);
631 zs.soa_serial = sd.serial;
632 soa_received = true;
633 }
634
635 rrs.push_back(rr);
636 }
637 }
638 }
639 return rrs;
640 }
641
642 void CommunicatorClass::suck(const DNSName& domain, const ComboAddress& remote, bool force)
643 {
644 {
645 auto data = d_data.lock();
646 if (data->d_inprogress.count(domain)) {
647 return;
648 }
649 data->d_inprogress.insert(domain);
650 }
651 RemoveSentinel rs(domain, this); // this removes us from d_inprogress when we go out of scope
652
653 string logPrefix = "XFR-in zone: '" + domain.toLogString() + "', primary: '" + remote.toString() + "', ";
654
655 g_log << Logger::Notice << logPrefix << "initiating transfer" << endl;
656 UeberBackend B; // fresh UeberBackend
657
658 DomainInfo di;
659 di.backend = nullptr;
660 bool transaction = false;
661 try {
662 DNSSECKeeper dk(&B); // reuse our UeberBackend copy for DNSSECKeeper
663 bool wrongDomainKind = false;
664 // this checks three error conditions & sets wrongDomainKind if we hit the third
665 if (!B.getDomainInfo(domain, di) || !di.backend || (wrongDomainKind = true, !force && !di.isSecondaryType())) { // di.backend and B are mostly identical
666 if (wrongDomainKind)
667 g_log << Logger::Warning << logPrefix << "can't determine backend, not configured as secondary" << endl;
668 else
669 g_log << Logger::Warning << logPrefix << "can't determine backend" << endl;
670 return;
671 }
672 ZoneStatus zs;
673 zs.domain_id = di.id;
674
675 TSIGTriplet tt;
676 if (dk.getTSIGForAccess(domain, remote, &tt.name)) {
677 string tsigsecret64;
678 if (B.getTSIGKey(tt.name, tt.algo, tsigsecret64)) {
679 if (B64Decode(tsigsecret64, tt.secret)) {
680 g_log << Logger::Error << logPrefix << "unable to Base-64 decode TSIG key '" << tt.name << "' or zone not found" << endl;
681 return;
682 }
683 }
684 else {
685 g_log << Logger::Warning << logPrefix << "TSIG key '" << tt.name << "' for zone not found" << endl;
686 return;
687 }
688 }
689
690 unique_ptr<AuthLua4> pdl{nullptr};
691 vector<string> scripts;
692 string script = ::arg()["lua-axfr-script"];
693 if (B.getDomainMetadata(domain, "LUA-AXFR-SCRIPT", scripts) && !scripts.empty()) {
694 if (pdns_iequals(scripts[0], "NONE")) {
695 script.clear();
696 }
697 else {
698 script = scripts[0];
699 }
700 }
701 if (!script.empty()) {
702 try {
703 pdl = make_unique<AuthLua4>();
704 pdl->loadFile(script);
705 g_log << Logger::Info << logPrefix << "loaded Lua script '" << script << "'" << endl;
706 }
707 catch (std::exception& e) {
708 g_log << Logger::Error << logPrefix << "failed to load Lua script '" << script << "': " << e.what() << endl;
709 return;
710 }
711 }
712
713 vector<string> localaddr;
714 ComboAddress laddr;
715
716 if (B.getDomainMetadata(domain, "AXFR-SOURCE", localaddr) && !localaddr.empty()) {
717 try {
718 laddr = ComboAddress(localaddr[0]);
719 g_log << Logger::Info << logPrefix << "xfr source set to " << localaddr[0] << endl;
720 }
721 catch (std::exception& e) {
722 g_log << Logger::Error << logPrefix << "failed to set xfr source '" << localaddr[0] << "': " << e.what() << endl;
723 return;
724 }
725 }
726 else {
727 if (!pdns::isQueryLocalAddressFamilyEnabled(remote.sin4.sin_family)) {
728 bool isV6 = remote.sin4.sin_family == AF_INET6;
729 g_log << Logger::Warning << logPrefix << "unable to xfr, address family (IPv" << (isV6 ? "6" : "4") << " is not enabled for outgoing traffic (query-local-address)" << endl;
730 return;
731 }
732 laddr = pdns::getQueryLocalAddress(remote.sin4.sin_family, 0);
733 }
734
735 bool hadDnssecZone = false;
736 bool hadPresigned = false;
737 bool hadNSEC3 = false;
738 NSEC3PARAMRecordContent hadNs3pr;
739 bool hadNarrow = false;
740
741 vector<DNSResourceRecord> rrs;
742 if (dk.isSecuredZone(domain, false)) {
743 hadDnssecZone = true;
744 hadPresigned = dk.isPresigned(domain, false);
745 if (dk.getNSEC3PARAM(domain, &zs.ns3pr, &zs.isNarrow, false)) {
746 hadNSEC3 = true;
747 hadNs3pr = zs.ns3pr;
748 hadNarrow = zs.isNarrow;
749 }
750 }
751 else if (di.serial) {
752 vector<string> meta;
753 B.getDomainMetadata(domain, "IXFR", meta);
754 if (!meta.empty() && meta[0] == "1") {
755 logPrefix = "I" + logPrefix; // XFR -> IXFR
756 vector<DNSRecord> axfr;
757 g_log << Logger::Notice << logPrefix << "starting IXFR" << endl;
758 ixfrSuck(domain, tt, laddr, remote, zs, &axfr);
759 if (!axfr.empty()) {
760 g_log << Logger::Notice << logPrefix << "IXFR turned into an AXFR" << endl;
761 logPrefix[0] = 'A'; // IXFR -> AXFR
762 bool firstNSEC3 = true;
763 rrs.reserve(axfr.size());
764 for (const auto& dr : axfr) {
765 auto rr = DNSResourceRecord::fromWire(dr);
766 (rr.qname += domain).makeUsLowerCase();
767 rr.domain_id = zs.domain_id;
768 if (!processRecordForZS(domain, firstNSEC3, rr, zs))
769 continue;
770 if (dr.d_type == QType::SOA) {
771 auto sd = getRR<SOARecordContent>(dr);
772 zs.soa_serial = sd->d_st.serial;
773 }
774 rrs.push_back(rr);
775 }
776 }
777 else {
778 g_log << Logger::Warning << logPrefix << "got " << zs.numDeltas << " delta" << addS(zs.numDeltas) << ", zone committed with serial " << zs.soa_serial << endl;
779 purgeAuthCaches(domain.toString() + "$");
780 return;
781 }
782 }
783 }
784
785 if (rrs.empty()) {
786 g_log << Logger::Notice << logPrefix << "starting AXFR" << endl;
787 rrs = doAxfr(remote, domain, tt, laddr, pdl, zs);
788 logPrefix = "A" + logPrefix; // XFR -> AXFR
789 g_log << Logger::Notice << logPrefix << "retrieval finished" << endl;
790 }
791
792 if (di.kind == DomainInfo::Consumer) {
793 if (!catalogProcess(di, rrs, logPrefix)) {
794 g_log << Logger::Warning << logPrefix << "Catalog-Zone update failed, only import records" << endl;
795 }
796 }
797
798 if (zs.isNSEC3) {
799 zs.ns3pr.d_flags = zs.optOutFlag ? 1 : 0;
800 }
801
802 if (!zs.isPresigned) {
803 DNSSECKeeper::keyset_t keys = dk.getKeys(domain, false);
804 if (!keys.empty()) {
805 zs.isDnssecZone = true;
806 zs.isNSEC3 = hadNSEC3;
807 zs.ns3pr = hadNs3pr;
808 zs.optOutFlag = (hadNs3pr.d_flags & 1);
809 zs.isNarrow = hadNarrow;
810 }
811 }
812
813 if (zs.isDnssecZone) {
814 if (!zs.isNSEC3)
815 g_log << Logger::Debug << logPrefix << "adding NSEC ordering information" << endl;
816 else if (!zs.isNarrow)
817 g_log << Logger::Debug << logPrefix << "adding NSEC3 hashed ordering information" << endl;
818 else
819 g_log << Logger::Debug << logPrefix << "zone is narrow, only setting 'auth' fields" << endl;
820 }
821
822 transaction = di.backend->startTransaction(domain, zs.domain_id);
823 g_log << Logger::Info << logPrefix << "storage transaction started" << endl;
824
825 // update the presigned flag and NSEC3PARAM
826 if (zs.isDnssecZone) {
827 // update presigned if there was a change
828 if (zs.isPresigned && !hadPresigned) {
829 // zone is now presigned
830 dk.setPresigned(domain);
831 }
832 else if (hadPresigned && !zs.isPresigned) {
833 // zone is no longer presigned
834 dk.unsetPresigned(domain);
835 }
836 // update NSEC3PARAM
837 if (zs.isNSEC3) {
838 // zone is NSEC3, only update if there was a change
839 if (!hadNSEC3 || (hadNarrow != zs.isNarrow) || (zs.ns3pr.d_algorithm != hadNs3pr.d_algorithm) || (zs.ns3pr.d_flags != hadNs3pr.d_flags) || (zs.ns3pr.d_iterations != hadNs3pr.d_iterations) || (zs.ns3pr.d_salt != hadNs3pr.d_salt)) {
840 dk.setNSEC3PARAM(domain, zs.ns3pr, zs.isNarrow);
841 }
842 }
843 else if (hadNSEC3) {
844 // zone is no longer NSEC3
845 dk.unsetNSEC3PARAM(domain);
846 }
847 }
848 else if (hadDnssecZone) {
849 // zone is no longer signed
850 if (hadPresigned) {
851 // remove presigned
852 dk.unsetPresigned(domain);
853 }
854 if (hadNSEC3) {
855 // unset NSEC3PARAM
856 dk.unsetNSEC3PARAM(domain);
857 }
858 }
859
860 bool doent = true;
861 uint32_t maxent = ::arg().asNum("max-ent-entries");
862 DNSName shorter, ordername;
863 set<DNSName> rrterm;
864 map<DNSName, bool> nonterm;
865
866 for (DNSResourceRecord& rr : rrs) {
867 if (!zs.isPresigned) {
868 if (rr.qtype.getCode() == QType::RRSIG)
869 continue;
870 if (zs.isDnssecZone && rr.qtype.getCode() == QType::DNSKEY && !::arg().mustDo("direct-dnskey"))
871 continue;
872 }
873
874 // Figure out auth and ents
875 rr.auth = true;
876 shorter = rr.qname;
877 rrterm.clear();
878 do {
879 if (doent) {
880 if (!zs.qnames.count(shorter))
881 rrterm.insert(shorter);
882 }
883 if (zs.nsset.count(shorter) && rr.qtype.getCode() != QType::DS)
884 rr.auth = false;
885
886 if (shorter == domain) // stop at apex
887 break;
888 } while (shorter.chopOff());
889
890 // Insert ents
891 if (doent && !rrterm.empty()) {
892 bool auth;
893 if (!rr.auth && rr.qtype.getCode() == QType::NS) {
894 if (zs.isNSEC3)
895 ordername = DNSName(toBase32Hex(hashQNameWithSalt(zs.ns3pr, rr.qname)));
896 auth = (!zs.isNSEC3 || !zs.optOutFlag || zs.secured.count(ordername));
897 }
898 else
899 auth = rr.auth;
900
901 for (const auto& nt : rrterm) {
902 if (!nonterm.count(nt))
903 nonterm.insert(pair<DNSName, bool>(nt, auth));
904 else if (auth)
905 nonterm[nt] = true;
906 }
907
908 if (nonterm.size() > maxent) {
909 g_log << Logger::Warning << logPrefix << "zone has too many empty non terminals" << endl;
910 nonterm.clear();
911 doent = false;
912 }
913 }
914
915 // RRSIG is always auth, even inside a delegation
916 if (rr.qtype.getCode() == QType::RRSIG)
917 rr.auth = true;
918
919 // Add ordername and insert record
920 if (zs.isDnssecZone && rr.qtype.getCode() != QType::RRSIG) {
921 if (zs.isNSEC3) {
922 // NSEC3
923 ordername = DNSName(toBase32Hex(hashQNameWithSalt(zs.ns3pr, rr.qname)));
924 if (!zs.isNarrow && (rr.auth || (rr.qtype.getCode() == QType::NS && (!zs.optOutFlag || zs.secured.count(ordername))))) {
925 di.backend->feedRecord(rr, ordername, true);
926 }
927 else
928 di.backend->feedRecord(rr, DNSName());
929 }
930 else {
931 // NSEC
932 if (rr.auth || rr.qtype.getCode() == QType::NS) {
933 ordername = rr.qname.makeRelative(domain);
934 di.backend->feedRecord(rr, ordername);
935 }
936 else
937 di.backend->feedRecord(rr, DNSName());
938 }
939 }
940 else
941 di.backend->feedRecord(rr, DNSName());
942 }
943
944 // Insert empty non-terminals
945 if (doent && !nonterm.empty()) {
946 if (zs.isNSEC3) {
947 di.backend->feedEnts3(zs.domain_id, domain, nonterm, zs.ns3pr, zs.isNarrow);
948 }
949 else
950 di.backend->feedEnts(zs.domain_id, nonterm);
951 }
952
953 di.backend->commitTransaction();
954 transaction = false;
955 di.backend->setFresh(zs.domain_id);
956 purgeAuthCaches(domain.toString() + "$");
957
958 g_log << Logger::Warning << logPrefix << "zone committed with serial " << zs.soa_serial << endl;
959
960 // Send secondary re-notifications
961 bool doNotify;
962 vector<string> meta;
963 if (B.getDomainMetadata(domain, "SLAVE-RENOTIFY", meta) && !meta.empty()) {
964 doNotify = (meta.front() == "1");
965 }
966 else {
967 doNotify = (::arg().mustDo("secondary-do-renotify"));
968 }
969 if (doNotify) {
970 notifyDomain(domain, &B);
971 }
972 }
973 catch (DBException& re) {
974 g_log << Logger::Error << logPrefix << "unable to feed record: " << re.reason << endl;
975 if (di.backend && transaction) {
976 g_log << Logger::Info << logPrefix << "aborting possible open transaction" << endl;
977 di.backend->abortTransaction();
978 }
979 }
980 catch (const MOADNSException& mde) {
981 g_log << Logger::Error << logPrefix << "unable to parse record (MOADNSException): " << mde.what() << endl;
982 if (di.backend && transaction) {
983 g_log << Logger::Info << logPrefix << "aborting possible open transaction" << endl;
984 di.backend->abortTransaction();
985 }
986 }
987 catch (std::exception& re) {
988 g_log << Logger::Error << logPrefix << "unable to xfr zone (std::exception): " << re.what() << endl;
989 if (di.backend && transaction) {
990 g_log << Logger::Info << logPrefix << "aborting possible open transaction" << endl;
991 di.backend->abortTransaction();
992 }
993 }
994 catch (ResolverException& re) {
995 {
996 auto data = d_data.lock();
997 // The AXFR probably failed due to a problem on the primary server. If SOA-checks against this primary
998 // still succeed, we would constantly try to AXFR the zone. To avoid this, we add the zone to the list of
999 // failed secondary-checks. This will suspend secondary-checks (and subsequent AXFR) for this zone for some time.
1000 uint64_t newCount = 1;
1001 time_t now = time(nullptr);
1002 const auto failedEntry = data->d_failedSecondaryRefresh.find(domain);
1003 if (failedEntry != data->d_failedSecondaryRefresh.end()) {
1004 newCount = data->d_failedSecondaryRefresh[domain].first + 1;
1005 }
1006 time_t nextCheck = now + std::min(newCount * d_tickinterval, (uint64_t)::arg().asNum("default-ttl"));
1007 data->d_failedSecondaryRefresh[domain] = {newCount, nextCheck};
1008 g_log << Logger::Warning << logPrefix << "unable to xfr zone (ResolverException): " << re.reason << " (This was attempt number " << newCount << ". Excluding zone from secondary-checks until " << nextCheck << ")" << endl;
1009 }
1010 if (di.backend && transaction) {
1011 g_log << Logger::Info << "aborting possible open transaction" << endl;
1012 di.backend->abortTransaction();
1013 }
1014 }
1015 catch (PDNSException& ae) {
1016 g_log << Logger::Error << logPrefix << "unable to xfr zone (PDNSException): " << ae.reason << endl;
1017 if (di.backend && transaction) {
1018 g_log << Logger::Info << logPrefix << "aborting possible open transaction" << endl;
1019 di.backend->abortTransaction();
1020 }
1021 }
1022 }
1023 namespace
1024 {
1025 struct DomainNotificationInfo
1026 {
1027 DomainInfo di;
1028 bool dnssecOk;
1029 ComboAddress localaddr;
1030 DNSName tsigkeyname, tsigalgname;
1031 string tsigsecret;
1032 };
1033 }
1034
1035 struct SecondarySenderReceiver
1036 {
1037 typedef std::tuple<DNSName, ComboAddress, uint16_t> Identifier;
1038
1039 struct Answer
1040 {
1041 uint32_t theirSerial;
1042 uint32_t theirInception;
1043 uint32_t theirExpire;
1044 };
1045
1046 map<uint32_t, Answer> d_freshness;
1047
1048 void deliverTimeout(const Identifier& /* i */)
1049 {
1050 }
1051
1052 Identifier send(DomainNotificationInfo& dni)
1053 {
1054 shuffle(dni.di.primaries.begin(), dni.di.primaries.end(), pdns::dns_random_engine());
1055 try {
1056 return {dni.di.zone,
1057 *dni.di.primaries.begin(),
1058 d_resolver.sendResolve(*dni.di.primaries.begin(),
1059 dni.localaddr,
1060 dni.di.zone,
1061 QType::SOA,
1062 nullptr,
1063 dni.dnssecOk, dni.tsigkeyname, dni.tsigalgname, dni.tsigsecret)};
1064 }
1065 catch (PDNSException& e) {
1066 throw runtime_error("While attempting to query freshness of '" + dni.di.zone.toLogString() + "': " + e.reason);
1067 }
1068 }
1069
1070 bool receive(Identifier& id, Answer& a)
1071 {
1072 return d_resolver.tryGetSOASerial(&(std::get<0>(id)), &(std::get<1>(id)), &a.theirSerial, &a.theirInception, &a.theirExpire, &(std::get<2>(id)));
1073 }
1074
1075 void deliverAnswer(const DomainNotificationInfo& dni, const Answer& a, unsigned int /* usec */)
1076 {
1077 d_freshness[dni.di.id] = a;
1078 }
1079
1080 Resolver d_resolver;
1081 };
1082
1083 void CommunicatorClass::addSecondaryCheckRequest(const DomainInfo& di, const ComboAddress& remote)
1084 {
1085 auto data = d_data.lock();
1086 DomainInfo ours = di;
1087 ours.backend = nullptr;
1088
1089 // When adding a check, if the remote addr from which notification was
1090 // received is a primary, clear all other primaries so we can be sure the
1091 // query goes to that one.
1092 for (const auto& primary : di.primaries) {
1093 if (ComboAddress::addressOnlyEqual()(remote, primary)) {
1094 ours.primaries.clear();
1095 ours.primaries.push_back(primary);
1096 break;
1097 }
1098 }
1099 data->d_tocheck.erase(di);
1100 data->d_tocheck.insert(ours);
1101 d_any_sem.post(); // kick the loop!
1102 }
1103
1104 void CommunicatorClass::addTryAutoPrimaryRequest(const DNSPacket& p)
1105 {
1106 const DNSPacket& ours = p;
1107 auto data = d_data.lock();
1108 if (data->d_potentialautoprimaries.insert(ours).second) {
1109 d_any_sem.post(); // kick the loop!
1110 }
1111 }
1112
1113 void CommunicatorClass::secondaryRefresh(PacketHandler* P)
1114 {
1115 // not unless we are secondary
1116 if (!::arg().mustDo("secondary"))
1117 return;
1118
1119 UeberBackend* B = P->getBackend();
1120 vector<DomainInfo> rdomains;
1121 vector<DomainNotificationInfo> sdomains;
1122 set<DNSPacket, Data::cmp> trysuperdomains;
1123 {
1124 auto data = d_data.lock();
1125 set<DomainInfo> requeue;
1126 rdomains.reserve(data->d_tocheck.size());
1127 for (const auto& di : data->d_tocheck) {
1128 if (data->d_inprogress.count(di.zone)) {
1129 g_log << Logger::Debug << "Got NOTIFY for " << di.zone << " while AXFR in progress, requeueing SOA check" << endl;
1130 requeue.insert(di);
1131 }
1132 else {
1133 // We received a NOTIFY for a zone. This means at least one of the zone's primary server is working.
1134 // Therefore we delete the zone from the list of failed secondary-checks to allow immediate checking.
1135 const auto wasFailedDomain = data->d_failedSecondaryRefresh.find(di.zone);
1136 if (wasFailedDomain != data->d_failedSecondaryRefresh.end()) {
1137 g_log << Logger::Debug << "Got NOTIFY for " << di.zone << ", removing zone from list of failed secondary-checks and going to check SOA serial" << endl;
1138 data->d_failedSecondaryRefresh.erase(di.zone);
1139 }
1140 else {
1141 g_log << Logger::Debug << "Got NOTIFY for " << di.zone << ", going to check SOA serial" << endl;
1142 }
1143 rdomains.push_back(di);
1144 }
1145 }
1146 data->d_tocheck.swap(requeue);
1147
1148 trysuperdomains = std::move(data->d_potentialautoprimaries);
1149 data->d_potentialautoprimaries.clear();
1150 }
1151
1152 for (const DNSPacket& dp : trysuperdomains) {
1153 // get the TSIG key name
1154 TSIGRecordContent trc;
1155 DNSName tsigkeyname;
1156 dp.getTSIGDetails(&trc, &tsigkeyname);
1157 P->tryAutoPrimarySynchronous(dp, tsigkeyname); // FIXME could use some error logging
1158 }
1159 if (rdomains.empty()) { // if we have priority domains, check them first
1160 B->getUnfreshSecondaryInfos(&rdomains);
1161 }
1162 sdomains.reserve(rdomains.size());
1163 DNSSECKeeper dk(B); // NOW HEAR THIS! This DK uses our B backend, so no interleaved access!
1164 bool checkSignatures = ::arg().mustDo("secondary-check-signature-freshness") && dk.doesDNSSEC();
1165 {
1166 auto data = d_data.lock();
1167 domains_by_name_t& nameindex = boost::multi_index::get<IDTag>(data->d_suckdomains);
1168 time_t now = time(nullptr);
1169
1170 for (DomainInfo& di : rdomains) {
1171 const auto failed = data->d_failedSecondaryRefresh.find(di.zone);
1172 if (failed != data->d_failedSecondaryRefresh.end() && now < failed->second.second) {
1173 // If the domain has failed before and the time before the next check has not expired, skip this domain
1174 g_log << Logger::Debug << "Zone '" << di.zone << "' is on the list of failed SOA checks. Skipping SOA checks until " << failed->second.second << endl;
1175 continue;
1176 }
1177 std::vector<std::string> localaddr;
1178 SuckRequest sr;
1179 sr.domain = di.zone;
1180 if (di.primaries.empty()) // secondary domains w/o primaries are ignored
1181 continue;
1182 // remove unfresh domains already queued for AXFR, no sense polling them again
1183 sr.primary = *di.primaries.begin();
1184 if (nameindex.count(sr)) { // this does NOT however protect us against AXFRs already in progress!
1185 continue;
1186 }
1187 if (data->d_inprogress.count(sr.domain)) { // this does
1188 continue;
1189 }
1190
1191 DomainNotificationInfo dni;
1192 dni.di = di;
1193 dni.dnssecOk = checkSignatures;
1194
1195 if (dk.getTSIGForAccess(di.zone, sr.primary, &dni.tsigkeyname)) {
1196 string secret64;
1197 if (!B->getTSIGKey(dni.tsigkeyname, dni.tsigalgname, secret64)) {
1198 g_log << Logger::Warning << "TSIG key '" << dni.tsigkeyname << "' for domain '" << di.zone << "' not found, can not AXFR." << endl;
1199 continue;
1200 }
1201 if (B64Decode(secret64, dni.tsigsecret) == -1) {
1202 g_log << Logger::Error << "Unable to Base-64 decode TSIG key '" << dni.tsigkeyname << "' for domain '" << di.zone << "', can not AXFR." << endl;
1203 continue;
1204 }
1205 }
1206
1207 localaddr.clear();
1208 // check for AXFR-SOURCE
1209 if (B->getDomainMetadata(di.zone, "AXFR-SOURCE", localaddr) && !localaddr.empty()) {
1210 try {
1211 dni.localaddr = ComboAddress(localaddr[0]);
1212 g_log << Logger::Info << "Freshness check source (AXFR-SOURCE) for domain '" << di.zone << "' set to " << localaddr[0] << endl;
1213 }
1214 catch (std::exception& e) {
1215 g_log << Logger::Error << "Failed to load freshness check source '" << localaddr[0] << "' for '" << di.zone << "': " << e.what() << endl;
1216 return;
1217 }
1218 }
1219 else {
1220 dni.localaddr.sin4.sin_family = 0;
1221 }
1222
1223 sdomains.push_back(std::move(dni));
1224 }
1225 }
1226 if (sdomains.empty()) {
1227 if (d_secondarieschanged) {
1228 auto data = d_data.lock();
1229 g_log << Logger::Info << "No new unfresh secondary domains, " << data->d_suckdomains.size() << " queued for AXFR already, " << data->d_inprogress.size() << " in progress" << endl;
1230 }
1231 d_secondarieschanged = !rdomains.empty();
1232 return;
1233 }
1234 else {
1235 auto data = d_data.lock();
1236 g_log << Logger::Info << sdomains.size() << " secondary domain" << (sdomains.size() > 1 ? "s" : "") << " need" << (sdomains.size() > 1 ? "" : "s") << " checking, " << data->d_suckdomains.size() << " queued for AXFR" << endl;
1237 }
1238
1239 SecondarySenderReceiver ssr;
1240
1241 Inflighter<vector<DomainNotificationInfo>, SecondarySenderReceiver> ifl(sdomains, ssr);
1242
1243 ifl.d_maxInFlight = 200;
1244
1245 for (;;) {
1246 try {
1247 ifl.run();
1248 break;
1249 }
1250 catch (std::exception& e) {
1251 g_log << Logger::Error << "While checking domain freshness: " << e.what() << endl;
1252 }
1253 catch (PDNSException& re) {
1254 g_log << Logger::Error << "While checking domain freshness: " << re.reason << endl;
1255 }
1256 }
1257
1258 if (ifl.getTimeouts()) {
1259 g_log << Logger::Warning << "Received serial number updates for " << ssr.d_freshness.size() << " zone" << addS(ssr.d_freshness.size()) << ", had " << ifl.getTimeouts() << " timeout" << addS(ifl.getTimeouts()) << endl;
1260 }
1261 else {
1262 g_log << Logger::Info << "Received serial number updates for " << ssr.d_freshness.size() << " zone" << addS(ssr.d_freshness.size()) << endl;
1263 }
1264
1265 time_t now = time(nullptr);
1266 for (auto& val : sdomains) {
1267 DomainInfo& di(val.di);
1268 // If our di comes from packethandler (caused by incoming NOTIFY), di.backend will not be filled out,
1269 // and di.serial will not either.
1270 // Conversely, if our di came from getUnfreshSecondaryInfos, di.backend and di.serial are valid.
1271 if (!di.backend) {
1272 // Do not overwrite received DI just to make sure it exists in backend:
1273 // di.primaries should contain the picked primary (as first entry)!
1274 DomainInfo tempdi;
1275 if (!B->getDomainInfo(di.zone, tempdi, false)) {
1276 g_log << Logger::Info << "Ignore domain " << di.zone << " since it has been removed from our backend" << endl;
1277 continue;
1278 }
1279 // Backend for di still doesn't exist and this might cause us to
1280 // SEGFAULT on the setFresh command later on
1281 di.backend = tempdi.backend;
1282 }
1283
1284 if (!ssr.d_freshness.count(di.id)) { // If we don't have an answer for the domain
1285 uint64_t newCount = 1;
1286 auto data = d_data.lock();
1287 const auto failedEntry = data->d_failedSecondaryRefresh.find(di.zone);
1288 if (failedEntry != data->d_failedSecondaryRefresh.end())
1289 newCount = data->d_failedSecondaryRefresh[di.zone].first + 1;
1290 time_t nextCheck = now + std::min(newCount * d_tickinterval, (uint64_t)::arg().asNum("default-ttl"));
1291 data->d_failedSecondaryRefresh[di.zone] = {newCount, nextCheck};
1292 if (newCount == 1) {
1293 g_log << Logger::Warning << "Unable to retrieve SOA for " << di.zone << ", this was the first time. NOTE: For every subsequent failed SOA check the domain will be suspended from freshness checks for 'num-errors x " << d_tickinterval << " seconds', with a maximum of " << (uint64_t)::arg().asNum("default-ttl") << " seconds. Skipping SOA checks until " << nextCheck << endl;
1294 }
1295 else if (newCount % 10 == 0) {
1296 g_log << Logger::Notice << "Unable to retrieve SOA for " << di.zone << ", this was the " << std::to_string(newCount) << "th time. Skipping SOA checks until " << nextCheck << endl;
1297 }
1298 // Make sure we recheck SOA for notifies
1299 if (di.receivedNotify) {
1300 di.backend->setStale(di.id);
1301 }
1302 continue;
1303 }
1304
1305 {
1306 auto data = d_data.lock();
1307 const auto wasFailedDomain = data->d_failedSecondaryRefresh.find(di.zone);
1308 if (wasFailedDomain != data->d_failedSecondaryRefresh.end())
1309 data->d_failedSecondaryRefresh.erase(di.zone);
1310 }
1311
1312 bool hasSOA = false;
1313 SOAData sd;
1314 try {
1315 // Use UeberBackend cache for SOA. Cache gets cleared after AXFR/IXFR.
1316 B->lookup(QType(QType::SOA), di.zone, di.id, nullptr);
1317 DNSZoneRecord zr;
1318 hasSOA = B->get(zr);
1319 if (hasSOA) {
1320 fillSOAData(zr, sd);
1321 while (B->get(zr))
1322 ;
1323 }
1324 }
1325 catch (...) {
1326 }
1327
1328 uint32_t theirserial = ssr.d_freshness[di.id].theirSerial;
1329 uint32_t ourserial = sd.serial;
1330 const ComboAddress remote = *di.primaries.begin();
1331
1332 if (hasSOA && rfc1982LessThan(theirserial, ourserial) && !::arg().mustDo("axfr-lower-serial")) {
1333 g_log << Logger::Warning << "Domain '" << di.zone << "' more recent than primary " << remote.toStringWithPortExcept(53) << ", our serial " << ourserial << " > their serial " << theirserial << endl;
1334 di.backend->setFresh(di.id);
1335 }
1336 else if (hasSOA && theirserial == ourserial) {
1337 uint32_t maxExpire = 0, maxInception = 0;
1338 if (checkSignatures && dk.isPresigned(di.zone)) {
1339 B->lookup(QType(QType::RRSIG), di.zone, di.id); // can't use DK before we are done with this lookup!
1340 DNSZoneRecord zr;
1341 while (B->get(zr)) {
1342 auto rrsig = getRR<RRSIGRecordContent>(zr.dr);
1343 if (rrsig->d_type == QType::SOA) {
1344 maxInception = std::max(maxInception, rrsig->d_siginception);
1345 maxExpire = std::max(maxExpire, rrsig->d_sigexpire);
1346 }
1347 }
1348 }
1349
1350 SuckRequest::RequestPriority prio = SuckRequest::SignaturesRefresh;
1351 if (di.receivedNotify) {
1352 prio = SuckRequest::Notify;
1353 }
1354
1355 if (!maxInception && !ssr.d_freshness[di.id].theirInception) {
1356 g_log << Logger::Info << "Domain '" << di.zone << "' is fresh (no DNSSEC), serial is " << ourserial << " (checked primary " << remote.toStringWithPortExcept(53) << ")" << endl;
1357 di.backend->setFresh(di.id);
1358 }
1359 else if (maxInception == ssr.d_freshness[di.id].theirInception && maxExpire == ssr.d_freshness[di.id].theirExpire) {
1360 g_log << Logger::Info << "Domain '" << di.zone << "' is fresh and SOA RRSIGs match, serial is " << ourserial << " (checked primary " << remote.toStringWithPortExcept(53) << ")" << endl;
1361 di.backend->setFresh(di.id);
1362 }
1363 else if (maxExpire >= now && !ssr.d_freshness[di.id].theirInception) {
1364 g_log << Logger::Info << "Domain '" << di.zone << "' is fresh, primary " << remote.toStringWithPortExcept(53) << " is no longer signed but (some) signatures are still valid, serial is " << ourserial << endl;
1365 di.backend->setFresh(di.id);
1366 }
1367 else if (maxInception && !ssr.d_freshness[di.id].theirInception) {
1368 g_log << Logger::Notice << "Domain '" << di.zone << "' is stale, primary " << remote.toStringWithPortExcept(53) << " is no longer signed and all signatures have expired, serial is " << ourserial << endl;
1369 addSuckRequest(di.zone, remote, prio);
1370 }
1371 else if (dk.doesDNSSEC() && !maxInception && ssr.d_freshness[di.id].theirInception) {
1372 g_log << Logger::Notice << "Domain '" << di.zone << "' is stale, primary " << remote.toStringWithPortExcept(53) << " has signed, serial is " << ourserial << endl;
1373 addSuckRequest(di.zone, remote, prio);
1374 }
1375 else {
1376 g_log << Logger::Notice << "Domain '" << di.zone << "' is fresh, but RRSIGs differ on primary " << remote.toStringWithPortExcept(53) << ", so DNSSEC is stale, serial is " << ourserial << endl;
1377 addSuckRequest(di.zone, remote, prio);
1378 }
1379 }
1380 else {
1381 SuckRequest::RequestPriority prio = SuckRequest::SerialRefresh;
1382 if (di.receivedNotify) {
1383 prio = SuckRequest::Notify;
1384 }
1385
1386 if (hasSOA) {
1387 g_log << Logger::Notice << "Domain '" << di.zone << "' is stale, primary " << remote.toStringWithPortExcept(53) << " serial " << theirserial << ", our serial " << ourserial << endl;
1388 }
1389 else {
1390 g_log << Logger::Notice << "Domain '" << di.zone << "' is empty, primary " << remote.toStringWithPortExcept(53) << " serial " << theirserial << endl;
1391 }
1392 addSuckRequest(di.zone, remote, prio);
1393 }
1394 }
1395 }
1396
1397 vector<pair<DNSName, ComboAddress>> CommunicatorClass::getSuckRequests()
1398 {
1399 vector<pair<DNSName, ComboAddress>> ret;
1400 auto data = d_data.lock();
1401 ret.reserve(data->d_suckdomains.size());
1402 for (auto const& d : data->d_suckdomains) {
1403 ret.emplace_back(d.domain, d.primary);
1404 }
1405 return ret;
1406 }
1407
1408 size_t CommunicatorClass::getSuckRequestsWaiting()
1409 {
1410 return d_data.lock()->d_suckdomains.size();
1411 }