]>
Commit | Line | Data |
---|---|---|
3696224d BH |
1 | /* |
2 | PowerDNS Versatile Database Driven Nameserver | |
cd189f24 | 3 | Copyright (C) 2002-2016 PowerDNS.COM BV |
3696224d BH |
4 | |
5 | This program is free software; you can redistribute it and/or modify | |
68dae32c KM |
6 | it under the terms of the GNU General Public License version 2 as |
7 | published by the Free Software Foundation; | |
3696224d | 8 | |
f782fe38 MH |
9 | Additionally, the license of this program contains a special |
10 | exception which allows to distribute the program in binary form when | |
11 | it is linked against OpenSSL. | |
12 | ||
3696224d BH |
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 St, Fifth Floor, Boston, MA 02110-1301 USA | |
21 | */ | |
870a0fe4 AT |
22 | #ifdef HAVE_CONFIG_H |
23 | #include "config.h" | |
24 | #endif | |
3696224d BH |
25 | #include "packetcache.hh" |
26 | #include "utility.hh" | |
d7652f3a BH |
27 | #include "dnssecinfra.hh" |
28 | #include "dnsseckeeper.hh" | |
29 | #include "base32.hh" | |
3696224d BH |
30 | #include <errno.h> |
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 "resolver.hh" | |
38 | #include "logger.hh" | |
39 | #include "dns.hh" | |
40 | #include "arguments.hh" | |
3696224d | 41 | #include "packetcache.hh" |
fa8fd4d2 | 42 | |
29b92d6f | 43 | #include "base64.hh" |
3696224d | 44 | #include "inflighter.cc" |
5704e107 | 45 | #include "lua-auth.hh" |
3696224d | 46 | #include "namespaces.hh" |
7108e055 | 47 | #include "common_startup.hh" |
3e7dcee6 | 48 | |
cd189f24 | 49 | #include "ixfr.hh" |
e23622a7 | 50 | using boost::scoped_ptr; |
3696224d | 51 | |
fab71044 | 52 | |
d3ee36f2 | 53 | void CommunicatorClass::addSuckRequest(const DNSName &domain, const string &master) |
3696224d BH |
54 | { |
55 | Lock l(&d_lock); | |
3696224d BH |
56 | SuckRequest sr; |
57 | sr.domain = domain; | |
58 | sr.master = master; | |
dbcb3066 | 59 | pair<UniQueue::iterator, bool> res; |
a71bee29 PD |
60 | |
61 | res=d_suckdomains.push_back(sr); | |
dbcb3066 | 62 | if(res.second) { |
7f3d870e | 63 | d_suck_sem.post(); |
dbcb3066 | 64 | } |
3e7dcee6 | 65 | |
3696224d BH |
66 | } |
67 | ||
d3ee36f2 | 68 | struct ZoneStatus |
69 | { | |
70 | bool isDnssecZone{false}; | |
71 | bool isPresigned{false}; | |
72 | bool isNSEC3 {false}; | |
73 | bool optOutFlag {false}; | |
74 | NSEC3PARAMRecordContent ns3pr; | |
75 | ||
76 | bool isNarrow{false}; | |
77 | unsigned int soa_serial{0}; | |
78 | set<DNSName> nsset, qnames, secured; | |
79 | uint32_t domain_id; | |
3e7dcee6 | 80 | int numDeltas{0}; |
d3ee36f2 | 81 | }; |
82 | ||
83 | ||
3e7dcee6 | 84 | void CommunicatorClass::ixfrSuck(const DNSName &domain, const TSIGTriplet& tt, const ComboAddress& laddr, const ComboAddress& remote, scoped_ptr<AuthLua>& pdl, |
85 | ZoneStatus& zs, vector<DNSRecord>* axfr) | |
3696224d | 86 | { |
cd189f24 | 87 | UeberBackend B; // fresh UeberBackend |
88 | ||
89 | DomainInfo di; | |
90 | di.backend=0; | |
91 | // bool transaction=false; | |
92 | try { | |
93 | DNSSECKeeper dk (&B); // reuse our UeberBackend copy for DNSSECKeeper | |
94 | ||
95 | if(!B.getDomainInfo(domain, di) || !di.backend) { // di.backend and B are mostly identical | |
96 | L<<Logger::Error<<"Can't determine backend for domain '"<<domain<<"'"<<endl; | |
97 | return; | |
98 | } | |
cd189f24 | 99 | |
100 | soatimes st; | |
101 | memset(&st, 0, sizeof(st)); | |
d3ee36f2 | 102 | st.serial=di.serial; |
cd189f24 | 103 | |
104 | DNSRecord dr; | |
12c06211 | 105 | dr.d_content = std::make_shared<SOARecordContent>(g_rootdnsname, g_rootdnsname, st); |
db8f9152 | 106 | auto deltas = getIXFRDeltas(remote, domain, dr, tt, laddr.sin4.sin_family ? &laddr : 0, ((size_t) ::arg().asNum("xfr-max-received-mbytes")) * 1024 * 1024); |
3e7dcee6 | 107 | zs.numDeltas=deltas.size(); |
108 | // cout<<"Got "<<deltas.size()<<" deltas from serial "<<di.serial<<", applying.."<<endl; | |
cd189f24 | 109 | |
110 | for(const auto& d : deltas) { | |
3e7dcee6 | 111 | const auto& remove = d.first; |
112 | const auto& add = d.second; | |
113 | // cout<<"Delta sizes: "<<remove.size()<<", "<<add.size()<<endl; | |
cd189f24 | 114 | |
3e7dcee6 | 115 | if(remove.empty()) { // we got passed an AXFR! |
116 | *axfr = add; | |
117 | return; | |
118 | } | |
119 | ||
120 | ||
cd189f24 | 121 | // our hammer is 'replaceRRSet(domain_id, qname, qt, vector<DNSResourceRecord>& rrset) |
3e7dcee6 | 122 | // which thinks in terms of RRSETs |
123 | // however, IXFR does not, and removes and adds *records* (bummer) | |
124 | // this means that we must group updates by {qname,qtype}, retrieve the RRSET, apply | |
125 | // the add/remove updates, and replaceRRSet the whole thing. | |
cd189f24 | 126 | |
3e7dcee6 | 127 | |
128 | map<pair<DNSName,uint16_t>, pair<vector<DNSRecord>, vector<DNSRecord> > > grouped; | |
129 | ||
130 | for(const auto& x: remove) | |
131 | grouped[{x.d_name, x.d_type}].first.push_back(x); | |
132 | for(const auto& x: add) | |
133 | grouped[{x.d_name, x.d_type}].second.push_back(x); | |
cd189f24 | 134 | |
135 | di.backend->startTransaction(domain, -1); | |
3e7dcee6 | 136 | for(const auto g : grouped) { |
137 | DNSResourceRecord rr; | |
138 | vector<DNSRecord> rrset; | |
139 | B.lookup(QType(g.first.second), g.first.first, 0, di.id); | |
140 | while(B.get(rr)) { | |
141 | rrset.push_back(DNSRecord{rr}); | |
142 | } | |
143 | // O(N^2)! | |
144 | rrset.erase(remove_if(rrset.begin(), rrset.end(), | |
145 | [&g](const DNSRecord& dr) { | |
146 | return count(g.second.first.cbegin(), | |
147 | g.second.first.cend(), dr); | |
148 | }), rrset.end()); | |
149 | // the DNSRecord== operator compares on name, type, class and lowercase content representation | |
150 | ||
151 | for(const auto& x : g.second.second) { | |
152 | rrset.push_back(x); | |
153 | } | |
cd189f24 | 154 | |
3e7dcee6 | 155 | vector<DNSResourceRecord> replacement; |
156 | for(const auto& x : rrset) { | |
157 | DNSResourceRecord dr(x); | |
158 | dr.qname += domain; | |
159 | dr.domain_id = di.id; | |
160 | if(x.d_type == QType::SOA) { | |
161 | // cout<<"New SOA: "<<x.d_content->getZoneRepresentation()<<endl; | |
162 | auto sr = getRR<SOARecordContent>(x); | |
163 | zs.soa_serial=sr->d_st.serial; | |
164 | } | |
165 | ||
166 | replacement.push_back(dr); | |
167 | } | |
d3ee36f2 | 168 | |
3e7dcee6 | 169 | di.backend->replaceRRSet(di.id, g.first.first+domain, QType(g.first.second), replacement); |
170 | } | |
cd189f24 | 171 | di.backend->commitTransaction(); |
172 | } | |
cd189f24 | 173 | } |
174 | catch(std::exception& p) { | |
3e7dcee6 | 175 | L<<Logger::Error<<"Got exception during IXFR: "<<p.what()<<endl; |
176 | throw; | |
cd189f24 | 177 | } |
178 | catch(PDNSException& p) { | |
3e7dcee6 | 179 | L<<Logger::Error<<"Got exception during IXFR: "<<p.reason<<endl; |
180 | throw; | |
181 | } | |
182 | } | |
cd189f24 | 183 | |
3e7dcee6 | 184 | |
185 | static bool processRecordForZS(const DNSName& domain, bool& firstNSEC3, DNSResourceRecord& rr, ZoneStatus& zs) | |
186 | { | |
187 | switch(rr.qtype.getCode()) { | |
188 | case QType::NSEC3PARAM: | |
189 | zs.ns3pr = NSEC3PARAMRecordContent(rr.content); | |
190 | zs.isDnssecZone = zs.isNSEC3 = true; | |
191 | zs.isNarrow = false; | |
e4fb8488 | 192 | return false; |
3e7dcee6 | 193 | case QType::NSEC3: { |
194 | NSEC3RecordContent ns3rc(rr.content); | |
195 | if (firstNSEC3) { | |
196 | zs.isDnssecZone = zs.isPresigned = true; | |
197 | firstNSEC3 = false; | |
198 | } else if (zs.optOutFlag != (ns3rc.d_flags & 1)) | |
199 | throw PDNSException("Zones with a mixture of Opt-Out NSEC3 RRs and non-Opt-Out NSEC3 RRs are not supported."); | |
200 | zs.optOutFlag = ns3rc.d_flags & 1; | |
201 | if (ns3rc.d_set.count(QType::NS) && !(rr.qname==domain)) { | |
202 | DNSName hashPart = DNSName(toLower(rr.qname.makeRelative(domain).toString())); | |
203 | zs.secured.insert(hashPart); | |
204 | } | |
e4fb8488 | 205 | return false; |
3e7dcee6 | 206 | } |
207 | ||
208 | case QType::NSEC: | |
209 | zs.isDnssecZone = zs.isPresigned = true; | |
e4fb8488 | 210 | return false; |
3e7dcee6 | 211 | |
212 | case QType::NS: | |
213 | if(rr.qname!=domain) | |
214 | zs.nsset.insert(rr.qname); | |
bd9602fa | 215 | break; |
3e7dcee6 | 216 | } |
217 | ||
218 | zs.qnames.insert(rr.qname); | |
219 | ||
220 | rr.domain_id=zs.domain_id; | |
221 | return true; | |
cd189f24 | 222 | } |
223 | ||
d3ee36f2 | 224 | /* So this code does a number of things. |
225 | 1) It will AXFR a domain from a master | |
226 | The code can retrieve the current serial number in the database itself. | |
227 | It may attempt an IXFR | |
228 | 2) It will filter the zone through a lua *filter* script | |
229 | 3) The code walks through the zone records do determine DNSSEC status (secured, nsec/nsec3, optout) | |
230 | 4) It inserts the zone into the database | |
231 | With the right 'ordername' fields | |
232 | 5) It updates the Empty Non Terminals | |
233 | */ | |
cd189f24 | 234 | |
d3ee36f2 | 235 | vector<DNSResourceRecord> doAxfr(const ComboAddress& raddr, const DNSName& domain, const TSIGTriplet& tt, const ComboAddress& laddr, scoped_ptr<AuthLua>& pdl, ZoneStatus& zs) |
236 | { | |
237 | vector<DNSResourceRecord> rrs; | |
db8f9152 | 238 | AXFRRetriever retriever(raddr, domain, tt, (laddr.sin4.sin_family == 0) ? NULL : &laddr, ((size_t) ::arg().asNum("xfr-max-received-mbytes")) * 1024 * 1024); |
d3ee36f2 | 239 | Resolver::res_t recs; |
240 | bool first=true; | |
241 | bool firstNSEC3{true}; | |
242 | bool soa_received {false}; | |
243 | while(retriever.getChunk(recs)) { | |
244 | if(first) { | |
245 | L<<Logger::Error<<"AXFR started for '"<<domain<<"'"<<endl; | |
246 | first=false; | |
247 | } | |
248 | ||
249 | for(Resolver::res_t::iterator i=recs.begin();i!=recs.end();++i) { | |
250 | if(i->qtype.getCode() == QType::OPT || i->qtype.getCode() == QType::TSIG) // ignore EDNS0 & TSIG | |
251 | continue; | |
252 | ||
253 | if(!i->qname.isPartOf(domain)) { | |
254 | L<<Logger::Error<<"Remote "<<raddr.toStringWithPort()<<" tried to sneak in out-of-zone data '"<<i->qname<<"'|"<<i->qtype.getName()<<" during AXFR of zone '"<<domain<<"', ignoring"<<endl; | |
255 | continue; | |
256 | } | |
257 | ||
258 | vector<DNSResourceRecord> out; | |
259 | if(!pdl || !pdl->axfrfilter(raddr, domain, *i, out)) { | |
3e7dcee6 | 260 | out.push_back(*i); // if axfrfilter didn't do anything, we put our record in 'out' ourselves |
d3ee36f2 | 261 | } |
262 | ||
263 | for(DNSResourceRecord& rr : out) { | |
3ce97f4a | 264 | if(!processRecordForZS(domain, firstNSEC3, rr, zs)) |
265 | continue; | |
3e7dcee6 | 266 | if(rr.qtype.getCode() == QType::SOA) { |
d3ee36f2 | 267 | if(soa_received) |
268 | continue; //skip the last SOA | |
269 | SOAData sd; | |
270 | fillSOAData(rr.content,sd); | |
271 | zs.soa_serial = sd.serial; | |
272 | soa_received = true; | |
d3ee36f2 | 273 | } |
274 | ||
d3ee36f2 | 275 | rrs.push_back(rr); |
3e7dcee6 | 276 | |
d3ee36f2 | 277 | } |
278 | } | |
279 | } | |
280 | return rrs; | |
281 | } | |
282 | ||
3e7dcee6 | 283 | |
d3ee36f2 | 284 | void CommunicatorClass::suck(const DNSName &domain, const string &remote) |
cd189f24 | 285 | { |
3e7dcee6 | 286 | { |
287 | Lock l(&d_lock); | |
288 | if(d_inprogress.count(domain)) { | |
289 | return; | |
290 | } | |
291 | d_inprogress.insert(domain); | |
292 | } | |
293 | RemoveSentinel rs(domain, this); // this removes us from d_inprogress when we go out of scope | |
294 | ||
f43c4448 | 295 | L<<Logger::Error<<"Initiating transfer of '"<<domain<<"' from remote '"<<remote<<"'"<<endl; |
295c4a00 | 296 | UeberBackend B; // fresh UeberBackend |
3696224d BH |
297 | |
298 | DomainInfo di; | |
299 | di.backend=0; | |
376ec278 | 300 | bool transaction=false; |
3696224d | 301 | try { |
295c4a00 | 302 | DNSSECKeeper dk (&B); // reuse our UeberBackend copy for DNSSECKeeper |
a1282cdd | 303 | |
295c4a00 | 304 | if(!B.getDomainInfo(domain, di) || !di.backend) { // di.backend and B are mostly identical |
f43c4448 | 305 | L<<Logger::Error<<"Can't determine backend for domain '"<<domain<<"'"<<endl; |
3696224d BH |
306 | return; |
307 | } | |
d3ee36f2 | 308 | ZoneStatus zs; |
309 | zs.domain_id=di.id; | |
53568203 | 310 | |
98c9ec39 | 311 | TSIGTriplet tt; |
312 | if(dk.getTSIGForAccess(domain, remote, &tt.name)) { | |
29b92d6f | 313 | string tsigsecret64; |
98c9ec39 | 314 | if(B.getTSIGKey(tt.name, &tt.algo, &tsigsecret64)) { |
315 | if(B64Decode(tsigsecret64, tt.secret)) { | |
316 | L<<Logger::Error<<"Unable to Base-64 decode TSIG key '"<<tt.name<<"' for domain '"<<domain<<"' not found"<<endl; | |
317 | return; | |
318 | } | |
53568203 | 319 | } else { |
98c9ec39 | 320 | L<<Logger::Error<<"TSIG key '"<<tt.name<<"' for domain '"<<domain<<"' not found"<<endl; |
53568203 | 321 | return; |
a1467662 | 322 | } |
29b92d6f | 323 | } |
53568203 KM |
324 | |
325 | ||
5704e107 | 326 | scoped_ptr<AuthLua> pdl; |
e23622a7 | 327 | vector<string> scripts; |
295c4a00 | 328 | if(B.getDomainMetadata(domain, "LUA-AXFR-SCRIPT", scripts) && !scripts.empty()) { |
b2aefbf9 | 329 | try { |
5704e107 | 330 | pdl.reset(new AuthLua(scripts[0])); |
f43c4448 | 331 | L<<Logger::Info<<"Loaded Lua script '"<<scripts[0]<<"' to edit the incoming AXFR of '"<<domain<<"'"<<endl; |
b2aefbf9 BH |
332 | } |
333 | catch(std::exception& e) { | |
f43c4448 | 334 | L<<Logger::Error<<"Failed to load Lua editing script '"<<scripts[0]<<"' for incoming AXFR of '"<<domain<<"': "<<e.what()<<endl; |
b2aefbf9 BH |
335 | return; |
336 | } | |
e23622a7 | 337 | } |
53568203 | 338 | |
fc396d56 BH |
339 | vector<string> localaddr; |
340 | ComboAddress laddr; | |
edb693f0 | 341 | ComboAddress raddr(remote, 53); |
295c4a00 | 342 | if(B.getDomainMetadata(domain, "AXFR-SOURCE", localaddr) && !localaddr.empty()) { |
fc396d56 BH |
343 | try { |
344 | laddr = ComboAddress(localaddr[0]); | |
f43c4448 | 345 | L<<Logger::Info<<"AXFR source for domain '"<<domain<<"' set to "<<localaddr[0]<<endl; |
fc396d56 BH |
346 | } |
347 | catch(std::exception& e) { | |
f43c4448 | 348 | L<<Logger::Error<<"Failed to load AXFR source '"<<localaddr[0]<<"' for incoming AXFR of '"<<domain<<"': "<<e.what()<<endl; |
fc396d56 BH |
349 | return; |
350 | } | |
edb693f0 | 351 | } else { |
352 | if(raddr.sin4.sin_family == AF_INET) | |
353 | laddr=ComboAddress(::arg()["query-local-address"]); | |
354 | else if(!::arg()["query-local-address6"].empty()) | |
355 | laddr=ComboAddress(::arg()["query-local-address6"]); | |
356 | else | |
357 | laddr.sin4.sin_family = 0; | |
8c949c52 | 358 | } |
fc396d56 | 359 | |
53568203 KM |
360 | bool hadDnssecZone = false; |
361 | bool hadPresigned = false; | |
362 | bool hadNSEC3 = false; | |
d3ee36f2 | 363 | NSEC3PARAMRecordContent hadNs3pr; |
364 | bool hadNarrow=false; | |
365 | ||
edb693f0 | 366 | |
3e7dcee6 | 367 | vector<DNSResourceRecord> rrs; |
53568203 KM |
368 | if(dk.isSecuredZone(domain)) { |
369 | hadDnssecZone=true; | |
370 | hadPresigned=dk.isPresigned(domain); | |
d3ee36f2 | 371 | if (dk.getNSEC3PARAM(domain, &zs.ns3pr, &zs.isNarrow)) { |
53568203 | 372 | hadNSEC3 = true; |
d3ee36f2 | 373 | hadNs3pr = zs.ns3pr; |
374 | hadNarrow = zs.isNarrow; | |
53568203 KM |
375 | } |
376 | } | |
d3ee36f2 | 377 | else if(di.serial) { |
378 | vector<string> meta; | |
379 | B.getDomainMetadata(domain, "IXFR", meta); | |
3e7dcee6 | 380 | if(!meta.empty() && meta[0]=="1") { |
381 | vector<DNSRecord> axfr; | |
edb693f0 | 382 | L<<Logger::Warning<<"Starting IXFR of '"<<domain<<"' from remote "<<raddr.toStringWithPort()<<endl; |
3e7dcee6 | 383 | ixfrSuck(domain, tt, laddr, raddr, pdl, zs, &axfr); |
384 | if(!axfr.empty()) { | |
385 | L<<Logger::Warning<<"IXFR of '"<<domain<<"' from remote '"<<raddr.toStringWithPort()<<"' turned into an AXFR"<<endl; | |
386 | bool firstNSEC3=true; | |
387 | rrs.reserve(axfr.size()); | |
388 | for(const auto& dr : axfr) { | |
389 | DNSResourceRecord rr(dr); | |
390 | rr.qname += domain; | |
391 | rr.domain_id = zs.domain_id; | |
3ce97f4a | 392 | if(!processRecordForZS(domain, firstNSEC3, rr, zs)) |
393 | continue; | |
3e7dcee6 | 394 | if(dr.d_type == QType::SOA) { |
395 | auto sd = getRR<SOARecordContent>(dr); | |
396 | zs.soa_serial = sd->d_st.serial; | |
397 | } | |
398 | rrs.push_back(rr); | |
399 | } | |
400 | } | |
401 | else { | |
402 | L<<Logger::Warning<<"Done with IXFR of '"<<domain<<"' from remote '"<<remote<<"', got "<<zs.numDeltas<<" delta"<<addS(zs.numDeltas)<<", serial now "<<zs.soa_serial<<endl; | |
403 | return; | |
404 | } | |
405 | } | |
3696224d | 406 | } |
68fa524b | 407 | |
3e7dcee6 | 408 | if(rrs.empty()) { |
409 | L<<Logger::Warning<<"Starting AXFR of '"<<domain<<"' from remote "<<raddr.toStringWithPort()<<endl; | |
410 | rrs = doAxfr(raddr, domain, tt, laddr, pdl, zs); | |
411 | L<<Logger::Warning<<"AXFR of '"<<domain<<"' from remote "<<raddr.toStringWithPort()<<" done"<<endl; | |
412 | } | |
d3ee36f2 | 413 | |
414 | if(zs.isNSEC3) { | |
415 | zs.ns3pr.d_flags = zs.optOutFlag ? 1 : 0; | |
53568203 | 416 | } |
ff473027 | 417 | |
d3ee36f2 | 418 | if(!zs.isPresigned) { |
53568203 KM |
419 | DNSSECKeeper::keyset_t keys = dk.getKeys(domain); |
420 | if(!keys.empty()) { | |
d3ee36f2 | 421 | zs.isDnssecZone = true; |
422 | zs.isNSEC3 = hadNSEC3; | |
423 | zs.ns3pr = hadNs3pr; | |
424 | zs.optOutFlag = (hadNs3pr.d_flags & 1); | |
425 | zs.isNarrow = hadNarrow; | |
53568203 KM |
426 | } |
427 | } | |
428 | ||
d3ee36f2 | 429 | if(zs.isDnssecZone) { |
430 | if(!zs.isNSEC3) | |
f9cf6d92 | 431 | L<<Logger::Info<<"Adding NSEC ordering information"<<endl; |
d3ee36f2 | 432 | else if(!zs.isNarrow) |
f43c4448 | 433 | L<<Logger::Info<<"Adding NSEC3 hashed ordering information for '"<<domain<<"'"<<endl; |
f9cf6d92 KM |
434 | else |
435 | L<<Logger::Info<<"Erasing NSEC3 ordering since we are narrow, only setting 'auth' fields"<<endl; | |
436 | } | |
437 | ||
b5baefaf | 438 | |
d3ee36f2 | 439 | transaction=di.backend->startTransaction(domain, zs.domain_id); |
3e7dcee6 | 440 | L<<Logger::Error<<"Backend transaction started for '"<<domain<<"' storage"<<endl; |
f9cf6d92 | 441 | |
7f2aa497 | 442 | // update the presigned flag and NSEC3PARAM |
d3ee36f2 | 443 | if (zs.isDnssecZone) { |
53568203 | 444 | // update presigned if there was a change |
d3ee36f2 | 445 | if (zs.isPresigned && !hadPresigned) { |
7f2aa497 KM |
446 | // zone is now presigned |
447 | dk.setPresigned(domain); | |
d3ee36f2 | 448 | } else if (hadPresigned && !zs.isPresigned) { |
53568203 KM |
449 | // zone is no longer presigned |
450 | dk.unsetPresigned(domain); | |
7f2aa497 | 451 | } |
53568203 | 452 | // update NSEC3PARAM |
d3ee36f2 | 453 | if (zs.isNSEC3) { |
53568203 | 454 | // zone is NSEC3, only update if there was a change |
d3ee36f2 | 455 | if (!hadNSEC3 || (hadNarrow != zs.isNarrow) || |
456 | (zs.ns3pr.d_algorithm != hadNs3pr.d_algorithm) || | |
457 | (zs.ns3pr.d_flags != hadNs3pr.d_flags) || | |
458 | (zs.ns3pr.d_iterations != hadNs3pr.d_iterations) || | |
459 | (zs.ns3pr.d_salt != hadNs3pr.d_salt)) { | |
460 | dk.setNSEC3PARAM(domain, zs.ns3pr, zs.isNarrow); | |
7f2aa497 | 461 | } |
53568203 KM |
462 | } else if (hadNSEC3 ) { |
463 | // zone is no longer NSEC3 | |
464 | dk.unsetNSEC3PARAM(domain); | |
465 | } | |
466 | } else if (hadDnssecZone) { | |
467 | // zone is no longer signed | |
468 | if (hadPresigned) { | |
469 | // remove presigned | |
470 | dk.unsetPresigned(domain); | |
471 | } | |
472 | if (hadNSEC3) { | |
473 | // unset NSEC3PARAM | |
474 | dk.unsetNSEC3PARAM(domain); | |
7f2aa497 | 475 | } |
7f2aa497 KM |
476 | } |
477 | ||
f9cf6d92 | 478 | bool doent=true; |
b5baefaf | 479 | uint32_t maxent = ::arg().asNum("max-ent-entries"); |
7abbc40f PD |
480 | string ordername; |
481 | DNSName shorter; | |
482 | set<DNSName> rrterm; | |
483 | map<DNSName,bool> nonterm; | |
b5baefaf | 484 | |
53568203 | 485 | |
ef7cd021 | 486 | for(DNSResourceRecord& rr : rrs) { |
d3ee36f2 | 487 | if(!zs.isPresigned) { |
53568203 KM |
488 | if (rr.qtype.getCode() == QType::RRSIG) |
489 | continue; | |
d3ee36f2 | 490 | if(zs.isDnssecZone && rr.qtype.getCode() == QType::DNSKEY && !::arg().mustDo("direct-dnskey")) |
53568203 KM |
491 | continue; |
492 | } | |
493 | ||
f9cf6d92 KM |
494 | // Figure out auth and ents |
495 | rr.auth=true; | |
496 | shorter=rr.qname; | |
497 | rrterm.clear(); | |
498 | do { | |
499 | if(doent) { | |
d3ee36f2 | 500 | if (!zs.qnames.count(shorter)) |
f9cf6d92 | 501 | rrterm.insert(shorter); |
d7652f3a | 502 | } |
d3ee36f2 | 503 | if(zs.nsset.count(shorter) && rr.qtype.getCode() != QType::DS) |
f9cf6d92 | 504 | rr.auth=false; |
e5c7239c | 505 | |
e325f20c | 506 | if (shorter==domain) // stop at apex |
f9cf6d92 | 507 | break; |
7abbc40f | 508 | }while(shorter.chopOff()); |
b8adb30d | 509 | |
e5c7239c KM |
510 | // Insert ents |
511 | if(doent && !rrterm.empty()) { | |
512 | bool auth; | |
513 | if (!rr.auth && rr.qtype.getCode() == QType::NS) { | |
d3ee36f2 | 514 | if (zs.isNSEC3) |
515 | ordername=toBase32Hex(hashQNameWithSalt(zs.ns3pr, rr.qname)); | |
516 | auth=(!zs.isNSEC3 || !zs.optOutFlag || zs.secured.count(DNSName(ordername))); | |
e5c7239c KM |
517 | } else |
518 | auth=rr.auth; | |
519 | ||
7abbc40f | 520 | for(const auto &nt: rrterm){ |
e5c7239c | 521 | if (!nonterm.count(nt)) |
7abbc40f | 522 | nonterm.insert(pair<DNSName, bool>(nt, auth)); |
e5c7239c KM |
523 | else if (auth) |
524 | nonterm[nt]=true; | |
525 | } | |
526 | ||
f9cf6d92 | 527 | if(nonterm.size() > maxent) { |
f43c4448 | 528 | L<<Logger::Error<<"AXFR zone "<<domain<<" has too many empty non terminals."<<endl; |
f9cf6d92 KM |
529 | nonterm.clear(); |
530 | doent=false; | |
27045410 | 531 | } |
d7652f3a | 532 | } |
f9cf6d92 KM |
533 | |
534 | // RRSIG is always auth, even inside a delegation | |
535 | if (rr.qtype.getCode() == QType::RRSIG) | |
536 | rr.auth=true; | |
537 | ||
538 | // Add ordername and insert record | |
d3ee36f2 | 539 | if (zs.isDnssecZone && rr.qtype.getCode() != QType::RRSIG) { |
540 | if (zs.isNSEC3) { | |
f9cf6d92 | 541 | // NSEC3 |
d3ee36f2 | 542 | ordername=toBase32Hex(hashQNameWithSalt(zs.ns3pr, rr.qname)); |
543 | if(!zs.isNarrow && (rr.auth || (rr.qtype.getCode() == QType::NS && (!zs.optOutFlag || zs.secured.count(DNSName(ordername)))))) { | |
f9cf6d92 KM |
544 | di.backend->feedRecord(rr, &ordername); |
545 | } else | |
546 | di.backend->feedRecord(rr); | |
547 | } else { | |
548 | // NSEC | |
549 | if (rr.auth || rr.qtype.getCode() == QType::NS) { | |
897e94f3 | 550 | ordername=rr.qname.makeRelative(domain).makeLowerCase().labelReverse().toString(" ", false); |
f9cf6d92 KM |
551 | di.backend->feedRecord(rr, &ordername); |
552 | } else | |
553 | di.backend->feedRecord(rr); | |
554 | } | |
555 | } else | |
556 | di.backend->feedRecord(rr); | |
d7652f3a | 557 | } |
b5baefaf | 558 | |
f9cf6d92 KM |
559 | // Insert empty non-terminals |
560 | if(doent && !nonterm.empty()) { | |
d3ee36f2 | 561 | if (zs.isNSEC3) { |
562 | di.backend->feedEnts3(zs.domain_id, domain, nonterm, zs.ns3pr, zs.isNarrow); | |
f9cf6d92 | 563 | } else |
d3ee36f2 | 564 | di.backend->feedEnts(zs.domain_id, nonterm); |
b5baefaf PD |
565 | } |
566 | ||
14b7e03b | 567 | di.backend->commitTransaction(); |
376ec278 | 568 | transaction = false; |
d3ee36f2 | 569 | di.backend->setFresh(zs.domain_id); |
7abbc40f | 570 | PC.purge(domain.toString()+"$"); |
14b7e03b | 571 | |
a1282cdd | 572 | |
d3ee36f2 | 573 | L<<Logger::Error<<"AXFR done for '"<<domain<<"', zone committed with serial number "<<zs.soa_serial<<endl; |
8de9c054 BH |
574 | if(::arg().mustDo("slave-renotify")) |
575 | notifyDomain(domain); | |
3696224d BH |
576 | } |
577 | catch(DBException &re) { | |
290a083d | 578 | L<<Logger::Error<<"Unable to feed record during incoming AXFR of '" << domain<<"': "<<re.reason<<endl; |
376ec278 | 579 | if(di.backend && transaction) { |
3696224d BH |
580 | L<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl; |
581 | di.backend->abortTransaction(); | |
582 | } | |
583 | } | |
75ccb5b9 | 584 | catch(MOADNSException &re) { |
290a083d | 585 | L<<Logger::Error<<"Unable to parse record during incoming AXFR of '"<<domain<<"' (MOADNSException): "<<re.what()<<endl; |
376ec278 | 586 | if(di.backend && transaction) { |
2714db17 BH |
587 | L<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl; |
588 | di.backend->abortTransaction(); | |
589 | } | |
590 | } | |
591 | catch(std::exception &re) { | |
290a083d | 592 | L<<Logger::Error<<"Unable to parse record during incoming AXFR of '"<<domain<<"' (std::exception): "<<re.what()<<endl; |
376ec278 | 593 | if(di.backend && transaction) { |
75ccb5b9 BH |
594 | L<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl; |
595 | di.backend->abortTransaction(); | |
596 | } | |
597 | } | |
3696224d | 598 | catch(ResolverException &re) { |
290a083d | 599 | L<<Logger::Error<<"Unable to AXFR zone '"<<domain<<"' from remote '"<<remote<<"' (resolver): "<<re.reason<<endl; |
376ec278 | 600 | if(di.backend && transaction) { |
3696224d | 601 | L<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl; |
7f450125 PD |
602 | di.backend->abortTransaction(); |
603 | } | |
604 | } | |
3f81d239 | 605 | catch(PDNSException &ae) { |
290a083d | 606 | L<<Logger::Error<<"Unable to AXFR zone '"<<domain<<"' from remote '"<<remote<<"' (PDNSException): "<<ae.reason<<endl; |
376ec278 | 607 | if(di.backend && transaction) { |
7f450125 | 608 | L<<Logger::Error<<"Aborting possible open transaction for domain '"<<domain<<"' AXFR"<<endl; |
3696224d BH |
609 | di.backend->abortTransaction(); |
610 | } | |
611 | } | |
612 | } | |
7597b3cf | 613 | namespace { |
3696224d | 614 | struct QueryInfo |
7597b3cf BH |
615 | { |
616 | struct timeval query_ttd; | |
617 | uint16_t id; | |
618 | }; | |
619 | ||
620 | struct DomainNotificationInfo | |
621 | { | |
622 | DomainInfo di; | |
623 | bool dnssecOk; | |
95302209 | 624 | ComboAddress localaddr; |
561434a6 PD |
625 | DNSName tsigkeyname, tsigalgname; |
626 | string tsigsecret; | |
7597b3cf BH |
627 | }; |
628 | } | |
629 | ||
3696224d BH |
630 | |
631 | struct SlaveSenderReceiver | |
632 | { | |
561434a6 | 633 | typedef pair<DNSName, uint16_t> Identifier; |
53568203 | 634 | |
54b2edb4 BH |
635 | struct Answer { |
636 | uint32_t theirSerial; | |
637 | uint32_t theirInception; | |
638 | uint32_t theirExpire; | |
639 | }; | |
53568203 | 640 | |
54b2edb4 | 641 | map<uint32_t, Answer> d_freshness; |
53568203 | 642 | |
3696224d BH |
643 | SlaveSenderReceiver() |
644 | { | |
3696224d | 645 | } |
53568203 | 646 | |
c0a5fc34 | 647 | void deliverTimeout(const Identifier& i) |
0c01dd7c BH |
648 | { |
649 | } | |
53568203 | 650 | |
7597b3cf | 651 | Identifier send(DomainNotificationInfo& dni) |
3696224d | 652 | { |
7597b3cf | 653 | random_shuffle(dni.di.masters.begin(), dni.di.masters.end()); |
0c01dd7c | 654 | try { |
7597b3cf | 655 | ComboAddress remote(*dni.di.masters.begin()); |
95302209 | 656 | if (dni.localaddr.sin4.sin_family == 0) { |
53568203 KM |
657 | return make_pair(dni.di.zone, |
658 | d_resolver.sendResolve(ComboAddress(*dni.di.masters.begin(), 53), | |
7abbc40f | 659 | dni.di.zone, |
53568203 | 660 | QType::SOA, |
95302209 AT |
661 | dni.dnssecOk, dni.tsigkeyname, dni.tsigalgname, dni.tsigsecret) |
662 | ); | |
663 | } else { | |
664 | return make_pair(dni.di.zone, | |
665 | d_resolver.sendResolve(ComboAddress(*dni.di.masters.begin(), 53), dni.localaddr, | |
7abbc40f | 666 | dni.di.zone, |
95302209 AT |
667 | QType::SOA, |
668 | dni.dnssecOk, dni.tsigkeyname, dni.tsigalgname, dni.tsigsecret) | |
669 | ); | |
670 | } | |
0c01dd7c | 671 | } |
3f81d239 | 672 | catch(PDNSException& e) { |
7abbc40f | 673 | throw runtime_error("While attempting to query freshness of '"+dni.di.zone.toString()+"': "+e.reason); |
0c01dd7c | 674 | } |
3696224d | 675 | } |
68dae32c | 676 | |
3696224d BH |
677 | bool receive(Identifier& id, Answer& a) |
678 | { | |
54b2edb4 | 679 | if(d_resolver.tryGetSOASerial(&id.first, &a.theirSerial, &a.theirInception, &a.theirExpire, &id.second)) { |
3696224d BH |
680 | return 1; |
681 | } | |
682 | return 0; | |
683 | } | |
68dae32c | 684 | |
7597b3cf | 685 | void deliverAnswer(DomainNotificationInfo& dni, const Answer& a, unsigned int usec) |
3696224d | 686 | { |
7597b3cf | 687 | d_freshness[dni.di.id]=a; |
3696224d | 688 | } |
68dae32c | 689 | |
3696224d | 690 | Resolver d_resolver; |
3696224d BH |
691 | }; |
692 | ||
7f3d870e BH |
693 | void CommunicatorClass::addSlaveCheckRequest(const DomainInfo& di, const ComboAddress& remote) |
694 | { | |
695 | Lock l(&d_lock); | |
90c9a70e BH |
696 | DomainInfo ours = di; |
697 | ours.backend = 0; | |
698 | d_tocheck.insert(ours); | |
7f3d870e BH |
699 | d_any_sem.post(); // kick the loop! |
700 | } | |
701 | ||
7108e055 PD |
702 | void CommunicatorClass::addTrySuperMasterRequest(DNSPacket *p) |
703 | { | |
704 | Lock l(&d_lock); | |
705 | DNSPacket ours = *p; | |
706 | d_potentialsupermasters.push_back(ours); | |
707 | d_any_sem.post(); // kick the loop! | |
708 | } | |
709 | ||
3696224d BH |
710 | void CommunicatorClass::slaveRefresh(PacketHandler *P) |
711 | { | |
56a4f068 AT |
712 | // not unless we are slave |
713 | if (!::arg().mustDo("slave")) return; | |
714 | ||
3971cf53 | 715 | UeberBackend *B=P->getBackend(); |
16e654fa | 716 | vector<DomainInfo> rdomains; |
3e7dcee6 | 717 | vector<DomainNotificationInfo> sdomains; |
7108e055 | 718 | vector<DNSPacket> trysuperdomains; |
68dae32c | 719 | |
7f3d870e BH |
720 | { |
721 | Lock l(&d_lock); | |
722 | rdomains.insert(rdomains.end(), d_tocheck.begin(), d_tocheck.end()); | |
723 | d_tocheck.clear(); | |
7108e055 PD |
724 | trysuperdomains.insert(trysuperdomains.end(), d_potentialsupermasters.begin(), d_potentialsupermasters.end()); |
725 | d_potentialsupermasters.clear(); | |
7f3d870e | 726 | } |
68dae32c | 727 | |
ef7cd021 | 728 | for(DNSPacket& dp : trysuperdomains) { |
7731e32c AT |
729 | // get the TSIG key name |
730 | TSIGRecordContent trc; | |
731 | DNSName tsigkeyname; | |
732 | string message; | |
733 | dp.getTSIGDetails(&trc, &tsigkeyname, &message); | |
7108e055 | 734 | int res; |
7731e32c | 735 | res=P->trySuperMasterSynchronous(&dp, tsigkeyname); |
7108e055 PD |
736 | if(res>=0) { |
737 | DNSPacket *r=dp.replyPacket(); | |
738 | r->setRcode(res); | |
739 | r->setOpcode(Opcode::Notify); | |
740 | N->send(r); | |
741 | delete r; | |
742 | } | |
743 | } | |
3e7dcee6 | 744 | if(rdomains.empty()) { // if we have priority domains, check them first |
7f3d870e | 745 | B->getUnfreshSlaveInfos(&rdomains); |
3e7dcee6 | 746 | } |
936eb34a | 747 | DNSSECKeeper dk(B); // NOW HEAR THIS! This DK uses our B backend, so no interleaved access! |
dbcb3066 BH |
748 | { |
749 | Lock l(&d_lock); | |
dbcb3066 BH |
750 | domains_by_name_t& nameindex=boost::multi_index::get<IDTag>(d_suckdomains); |
751 | ||
ef7cd021 | 752 | for(DomainInfo& di : rdomains) { |
95302209 | 753 | std::vector<std::string> localaddr; |
dbcb3066 BH |
754 | SuckRequest sr; |
755 | sr.domain=di.zone; | |
756 | if(di.masters.empty()) // slave domains w/o masters are ignored | |
757 | continue; | |
758 | // remove unfresh domains already queued for AXFR, no sense polling them again | |
759 | sr.master=*di.masters.begin(); | |
3e7dcee6 | 760 | if(nameindex.count(sr)) { // this does NOT however protect us against AXFRs already in progress! |
dbcb3066 | 761 | continue; |
a71bee29 | 762 | } |
3e7dcee6 | 763 | if(d_inprogress.count(sr.domain)) // this does |
764 | continue; | |
765 | ||
7597b3cf BH |
766 | DomainNotificationInfo dni; |
767 | dni.di=di; | |
768 | dni.dnssecOk = dk.isPresigned(di.zone); | |
68dae32c | 769 | |
7597b3cf BH |
770 | if(dk.getTSIGForAccess(di.zone, sr.master, &dni.tsigkeyname)) { |
771 | string secret64; | |
772 | B->getTSIGKey(dni.tsigkeyname, &dni.tsigalgname, &secret64); | |
773 | B64Decode(secret64, dni.tsigsecret); | |
774 | } | |
95302209 AT |
775 | |
776 | localaddr.clear(); | |
777 | // check for AXFR-SOURCE | |
778 | if(B->getDomainMetadata(di.zone, "AXFR-SOURCE", localaddr) && !localaddr.empty()) { | |
779 | try { | |
780 | dni.localaddr = ComboAddress(localaddr[0]); | |
781 | L<<Logger::Info<<"Freshness check source (AXFR-SOURCE) for domain '"<<di.zone<<"' set to "<<localaddr[0]<<endl; | |
782 | } | |
783 | catch(std::exception& e) { | |
784 | L<<Logger::Error<<"Failed to load freshness check source '"<<localaddr[0]<<"' for '"<<di.zone<<"': "<<e.what()<<endl; | |
785 | return; | |
786 | } | |
787 | } else { | |
788 | dni.localaddr.sin4.sin_family = 0; | |
789 | } | |
790 | ||
7597b3cf | 791 | sdomains.push_back(dni); |
dbcb3066 | 792 | } |
dbcb3066 | 793 | } |
3696224d BH |
794 | if(sdomains.empty()) |
795 | { | |
dbcb3066 BH |
796 | if(d_slaveschanged) { |
797 | Lock l(&d_lock); | |
3e7dcee6 | 798 | L<<Logger::Warning<<"No new unfresh slave domains, "<<d_suckdomains.size()<<" queued for AXFR already, "<<d_inprogress.size()<<" in progress"<<endl; |
dbcb3066 BH |
799 | } |
800 | d_slaveschanged = !rdomains.empty(); | |
3696224d BH |
801 | return; |
802 | } | |
dbcb3066 BH |
803 | else { |
804 | Lock l(&d_lock); | |
3696224d BH |
805 | L<<Logger::Warning<<sdomains.size()<<" slave domain"<<(sdomains.size()>1 ? "s" : "")<<" need"<< |
806 | (sdomains.size()>1 ? "" : "s")<< | |
dbcb3066 BH |
807 | " checking, "<<d_suckdomains.size()<<" queued for AXFR"<<endl; |
808 | } | |
68dae32c | 809 | |
3696224d | 810 | SlaveSenderReceiver ssr; |
68dae32c | 811 | |
7597b3cf | 812 | Inflighter<vector<DomainNotificationInfo>, SlaveSenderReceiver> ifl(sdomains, ssr); |
68dae32c | 813 | |
3696224d BH |
814 | ifl.d_maxInFlight = 200; |
815 | ||
816 | for(;;) { | |
817 | try { | |
818 | ifl.run(); | |
819 | break; | |
820 | } | |
dbcb3066 | 821 | catch(std::exception& e) { |
3696224d BH |
822 | L<<Logger::Error<<"While checking domain freshness: " << e.what()<<endl; |
823 | } | |
68dae32c | 824 | catch(PDNSException &re) { |
3696224d BH |
825 | L<<Logger::Error<<"While checking domain freshness: " << re.reason<<endl; |
826 | } | |
827 | } | |
3e7dcee6 | 828 | L<<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; |
16e654fa | 829 | |
7597b3cf | 830 | typedef DomainNotificationInfo val_t; |
ef7cd021 | 831 | for(val_t& val : sdomains) { |
7597b3cf | 832 | DomainInfo& di(val.di); |
f9fa0e2d PD |
833 | // might've come from the packethandler |
834 | if(!di.backend && !B->getDomainInfo(di.zone, di)) { | |
f43c4448 | 835 | L<<Logger::Warning<<"Ignore domain "<< di.zone<<" since it has been removed from our backend"<<endl; |
232f0877 | 836 | continue; |
f9fa0e2d | 837 | } |
68dae32c | 838 | |
3e7dcee6 | 839 | if(!ssr.d_freshness.count(di.id)) // what does this mean? XXX |
3696224d | 840 | continue; |
54b2edb4 | 841 | uint32_t theirserial = ssr.d_freshness[di.id].theirSerial, ourserial = di.serial; |
68dae32c | 842 | |
da1a6059 | 843 | if(rfc1982LessThan(theirserial, ourserial) && ourserial != 0) { |
8e90e428 | 844 | L<<Logger::Error<<"Domain '"<<di.zone<<"' more recent than master, our serial " << ourserial << " > their serial "<< theirserial << endl; |
3696224d BH |
845 | di.backend->setFresh(di.id); |
846 | } | |
847 | else if(theirserial == ourserial) { | |
54b2edb4 | 848 | if(!dk.isPresigned(di.zone)) { |
1d3fae1b | 849 | L<<Logger::Info<<"Domain '"<< di.zone<<"' is fresh (not presigned, no RRSIG check)"<<endl; |
54b2edb4 BH |
850 | di.backend->setFresh(di.id); |
851 | } | |
852 | else { | |
936eb34a | 853 | B->lookup(QType(QType::RRSIG), di.zone); // can't use DK before we are done with this lookup! |
54b2edb4 BH |
854 | DNSResourceRecord rr; |
855 | uint32_t maxExpire=0, maxInception=0; | |
856 | while(B->get(rr)) { | |
857 | RRSIGRecordContent rrc(rr.content); | |
858 | if(rrc.d_type == QType::SOA) { | |
859 | maxInception = std::max(maxInception, rrc.d_siginception); | |
860 | maxExpire = std::max(maxExpire, rrc.d_sigexpire); | |
861 | } | |
862 | } | |
863 | if(maxInception == ssr.d_freshness[di.id].theirInception && maxExpire == ssr.d_freshness[di.id].theirExpire) { | |
f43c4448 | 864 | L<<Logger::Info<<"Domain '"<< di.zone<<"' is fresh and apex RRSIGs match"<<endl; |
54b2edb4 BH |
865 | di.backend->setFresh(di.id); |
866 | } | |
867 | else { | |
8e90e428 | 868 | L<<Logger::Warning<<"Domain '"<< di.zone<<"' is fresh, but RRSIGS differ, so DNSSEC stale"<<endl; |
d3ee36f2 | 869 | addSuckRequest(di.zone, *di.masters.begin()); |
54b2edb4 BH |
870 | } |
871 | } | |
3696224d BH |
872 | } |
873 | else { | |
8e90e428 | 874 | L<<Logger::Warning<<"Domain '"<< di.zone<<"' is stale, master serial "<<theirserial<<", our serial "<< ourserial <<endl; |
d3ee36f2 | 875 | addSuckRequest(di.zone, *di.masters.begin()); |
3696224d BH |
876 | } |
877 | } | |
68dae32c | 878 | } |
3696224d | 879 | |
bd53ea9d PD |
880 | // stub for PowerDNSLua linking |
881 | int directResolve(const std::string& qname, const QType& qtype, int qclass, vector<DNSResourceRecord>& ret) | |
882 | { | |
883 | return -1; | |
884 | } | |
885 | ||
886 |