]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/auth-primarycommunicator.cc
Meson: Separate test files from common files
[thirdparty/pdns.git] / pdns / auth-primarycommunicator.cc
CommitLineData
3696224d 1/*
12471842
PL
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 */
870a0fe4
AT
22#ifdef HAVE_CONFIG_H
23#include "config.h"
24#endif
bf269e28 25#include "auth-caches.hh"
9931f46f 26#include "auth-zonecache.hh"
3696224d 27#include "utility.hh"
dc6aa7f5 28#include <cerrno>
3696224d
BH
29#include "communicator.hh"
30#include <set>
31#include <boost/utility.hpp>
fa8fd4d2 32
3696224d
BH
33#include "dnsbackend.hh"
34#include "ueberbackend.hh"
35#include "packethandler.hh"
8db49a64 36#include "nameserver.hh"
3696224d
BH
37#include "resolver.hh"
38#include "logger.hh"
39#include "dns.hh"
40#include "arguments.hh"
3696224d 41#include "packetcache.hh"
7014a23d 42#include "base64.hh"
3696224d 43#include "namespaces.hh"
20829585 44#include "query-local-address.hh"
3696224d 45
abcd36a1 46void CommunicatorClass::queueNotifyDomain(const DomainInfo& di, UeberBackend* B)
3696224d 47{
a01b2204 48 bool hasQueuedItem = false;
1fd89ec2
PD
49 set<string> ips;
50 set<DNSName> nsset;
66157226 51 DNSZoneRecord rr;
eac85b18
KM
52 FindNS fns;
53
410f8bd3 54 try {
a01b2204
KM
55 if (d_onlyNotify.size()) {
56 B->lookup(QType(QType::NS), di.zone, di.id);
57 while (B->get(rr))
58 nsset.insert(getRR<NSRecordContent>(rr.dr)->getNS());
59
60 for (const auto& ns : nsset) {
61 vector<string> nsips = fns.lookup(ns, B);
62 if (nsips.empty())
63 g_log << Logger::Warning << "Unable to queue notification of domain '" << di.zone << "' to nameserver '" << ns << "': nameserver does not resolve!" << endl;
64 else
65 for (const auto& nsip : nsips) {
66 const ComboAddress caIp(nsip, 53);
67 if (!d_preventSelfNotification || !AddressIsUs(caIp)) {
68 if (!d_onlyNotify.match(&caIp))
69 g_log << Logger::Notice << "Skipped notification of domain '" << di.zone << "' to " << ns << " because " << caIp << " does not match only-notify." << endl;
70 else
71 ips.insert(caIp.toStringWithPort());
72 }
99844905 73 }
a01b2204 74 }
eac85b18 75
a01b2204
KM
76 for (const auto& ip : ips) {
77 g_log << Logger::Notice << "Queued notification of domain '" << di.zone << "' to " << ip << endl;
7bd9b307 78 d_nq.add(di.zone, ip, d_delayNotifications);
a01b2204
KM
79 hasQueuedItem = true;
80 }
99844905 81 }
3696224d 82 }
a01b2204 83 catch (PDNSException& ae) {
410f8bd3
PD
84 g_log << Logger::Error << "Error looking up name servers for " << di.zone << ", cannot notify: " << ae.reason << endl;
85 return;
86 }
a01b2204 87 catch (std::exception& e) {
410f8bd3
PD
88 g_log << Logger::Error << "Error looking up name servers for " << di.zone << ", cannot notify: " << e.what() << endl;
89 return;
90 }
91
24d3239e 92 set<string> alsoNotify(d_alsoNotify);
abcd36a1 93 B->alsoNotifies(di.zone, &alsoNotify);
eac85b18 94
a01b2204 95 for (const auto& j : alsoNotify) {
79a454ef 96 try {
d7f67000 97 const ComboAddress caIp(j, 53);
a01b2204 98 g_log << Logger::Notice << "Queued also-notification of domain '" << di.zone << "' to " << caIp.toStringWithPort() << endl;
79a454ef
KM
99 if (!ips.count(caIp.toStringWithPort())) {
100 ips.insert(caIp.toStringWithPort());
7bd9b307 101 d_nq.add(di.zone, caIp.toStringWithPort(), d_delayNotifications);
79a454ef 102 }
a01b2204 103 hasQueuedItem = true;
79a454ef 104 }
a01b2204
KM
105 catch (PDNSException& e) {
106 g_log << Logger::Warning << "Unparseable IP in ALSO-NOTIFY metadata of domain '" << di.zone << "'. Warning: " << e.reason << endl;
24d3239e 107 }
3696224d 108 }
eac85b18 109
088c3342 110 if (!hasQueuedItem)
a01b2204 111 g_log << Logger::Warning << "Request to queue notification for domain '" << di.zone << "' was processed, but no valid nameservers or ALSO-NOTIFYs found. Not notifying!" << endl;
3696224d
BH
112}
113
a01b2204 114bool CommunicatorClass::notifyDomain(const DNSName& domain, UeberBackend* B)
3696224d
BH
115{
116 DomainInfo di;
a01b2204
KM
117 if (!B->getDomainInfo(domain, di)) {
118 g_log << Logger::Warning << "No such domain '" << domain << "' in our database" << endl;
3696224d
BH
119 return false;
120 }
3497eb18 121 queueNotifyDomain(di, B);
f159e137
KM
122 // call backend and tell them we sent out the notification - even though that is premature
123 if (di.serial != di.notified_serial)
124 di.backend->setNotified(di.id, di.serial);
3696224d 125
de42d2df 126 return true;
3696224d
BH
127}
128
2d00c43d
BH
129void NotificationQueue::dump()
130{
a01b2204
KM
131 cerr << "Waiting for notification responses: " << endl;
132 for (NotificationRequest& nr : d_nqueue) {
133 cerr << nr.domain << ", " << nr.ip << endl;
2d00c43d
BH
134 }
135}
3696224d 136
ddeea7a6
KM
137void CommunicatorClass::getUpdatedProducers(UeberBackend* B, vector<DomainInfo>& domains, const std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
138{
139 std::string metaHash;
140 std::string mapHash;
141 for (auto& ch : catalogHashes) {
ddeea7a6
KM
142 if (!catalogs.count(ch.first)) {
143 g_log << Logger::Warning << "orphaned member zones found with catalog '" << ch.first << "'" << endl;
144 continue;
145 }
146
147 if (!B->getDomainMetadata(ch.first, "CATALOG-HASH", metaHash)) {
148 metaHash.clear();
149 }
150
151 mapHash = Base64Encode(ch.second.digest());
152 if (mapHash != metaHash) {
153 DomainInfo di;
154 if (B->getDomainInfo(ch.first, di)) {
155 if (di.kind != DomainInfo::Producer) {
156 g_log << Logger::Warning << "zone '" << di.zone << "' is no producer zone" << endl;
157 continue;
158 }
159
160 B->setDomainMetadata(di.zone, "CATALOG-HASH", mapHash);
161
98d26417 162 g_log << Logger::Warning << "new CATALOG-HASH '" << mapHash << "' for zone '" << di.zone << "'" << endl;
ddeea7a6
KM
163
164 SOAData sd;
165 if (!B->getSOAUncached(di.zone, sd)) {
166 g_log << Logger::Warning << "SOA lookup failed for producer zone '" << di.zone << "'" << endl;
167 continue;
168 }
169
170 DNSResourceRecord rr;
171 makeIncreasedSOARecord(sd, "EPOCH", "", rr);
172 di.backend->startTransaction(sd.qname, -1);
173 if (!di.backend->replaceRRSet(di.id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
174 di.backend->abortTransaction();
175 throw PDNSException("backend hosting producer zone '" + sd.qname.toLogString() + "' does not support editing records");
176 }
177 di.backend->commitTransaction();
178
179 domains.emplace_back(di);
180 }
181 }
182 }
183}
184
d525b58b 185void CommunicatorClass::primaryUpdateCheck(PacketHandler* P)
3696224d 186{
a01b2204 187 if (!::arg().mustDo("primary"))
ddeea7a6 188 return;
3696224d 189
a01b2204 190 UeberBackend* B = P->getBackend();
3696224d 191 vector<DomainInfo> cmdomains;
ddeea7a6
KM
192 std::unordered_set<DNSName> catalogs;
193 CatalogHashMap catalogHashes;
d525b58b 194 B->getUpdatedPrimaries(cmdomains, catalogs, catalogHashes);
ddeea7a6
KM
195 getUpdatedProducers(B, cmdomains, catalogs, catalogHashes);
196
a01b2204 197 if (cmdomains.empty()) {
ddeea7a6 198 g_log << Logger::Info << "no primary or producer domains need notifications" << endl;
3696224d
BH
199 }
200 else {
ddeea7a6 201 g_log << Logger::Info << cmdomains.size() << " domain" << addS(cmdomains.size()) << " for which we are primary or consumer need" << addS(cmdomains.size()) << " notifications" << endl;
3696224d
BH
202 }
203
a01b2204 204 for (auto& di : cmdomains) {
bf269e28 205 purgeAuthCachesExact(di.zone);
9931f46f 206 g_zoneCache.add(di.zone, di.id);
abcd36a1
CH
207 queueNotifyDomain(di, B);
208 di.backend->setNotified(di.id, di.serial);
3696224d
BH
209 }
210}
211
de42d2df 212time_t CommunicatorClass::doNotifications(PacketHandler* P)
3696224d 213{
de42d2df 214 UeberBackend* B = P->getBackend();
3696224d 215 ComboAddress from;
3696224d 216 char buffer[1500];
a683e8bd 217 int sock;
d529011f 218 set<int> fds = {d_nsock4, d_nsock6};
b9ec63fa 219
a71bee29 220 // receive incoming notifications on the nonblocking socket and take them off the list
de42d2df 221 while (waitForMultiData(fds, 0, 0, &sock) > 0) {
bd6223db
FM
222 Utility::socklen_t fromlen = sizeof(from);
223 const auto size = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&from, &fromlen);
224 if (size < 0) {
0c01dd7c 225 break;
bd6223db 226 }
27c0050c 227 DNSPacket p(true);
3696224d
BH
228
229 p.setRemote(&from);
230
de42d2df
FM
231 if (p.parse(buffer, (size_t)size) < 0) {
232 g_log << Logger::Warning << "Unable to parse SOA notification answer from " << p.getRemote() << endl;
3696224d
BH
233 continue;
234 }
235
bd6223db 236 if (p.d.rcode) {
de42d2df 237 g_log << Logger::Warning << "Received unsuccessful notification report for '" << p.qdomain << "' from " << from.toStringWithPort() << ", error: " << RCode::to_s(p.d.rcode) << endl;
bd6223db 238 }
33966bfb 239
792548c4 240 if (d_nq.removeIf(from, p.d.id, p.qdomain)) {
de42d2df 241 g_log << Logger::Notice << "Removed from notification list: '" << p.qdomain << "' to " << from.toStringWithPort() << " " << (p.d.rcode ? RCode::to_s(p.d.rcode) : "(was acknowledged)") << endl;
bd6223db 242 }
2d00c43d 243 else {
de42d2df
FM
244 g_log << Logger::Warning << "Received spurious notify answer for '" << p.qdomain << "' from " << from.toStringWithPort() << endl;
245 // d_nq.dump();
2d00c43d 246 }
3696224d
BH
247 }
248
249 // send out possible new notifications
675fa24c
PD
250 DNSName domain;
251 string ip;
de42d2df 252 uint16_t id = 0;
3696224d
BH
253
254 bool purged;
de42d2df
FM
255 while (d_nq.getOne(domain, ip, &id, purged)) {
256 if (!purged) {
3696224d 257 try {
0c01dd7c 258 ComboAddress remote(ip, 53); // default to 53
de42d2df
FM
259 if ((d_nsock6 < 0 && remote.sin4.sin_family == AF_INET6) || (d_nsock4 < 0 && remote.sin4.sin_family == AF_INET)) {
260 g_log << Logger::Warning << "Unable to notify " << remote.toStringWithPort() << " for domain '" << domain << "', address family is disabled. Is an IPv" << (remote.sin4.sin_family == AF_INET ? "4" : "6") << " address set in query-local-address?" << endl;
792548c4 261 d_nq.removeIf(remote, id, domain); // Remove, we'll never be able to notify
de42d2df 262 continue; // don't try to notify what we can't!
9d36589a 263 }
bd6223db 264 if (d_preventSelfNotification && AddressIsUs(remote)) {
232f0877 265 continue;
bd6223db 266 }
8db49a64 267
3497eb18 268 sendNotification(remote.sin4.sin_family == AF_INET ? d_nsock4 : d_nsock6, domain, remote, id, B);
4957a608 269 drillHole(domain, ip);
3696224d 270 }
de42d2df
FM
271 catch (ResolverException& re) {
272 g_log << Logger::Warning << "Error trying to resolve '" << ip << "' for notifying '" << domain << "' to server: " << re.reason << endl;
3696224d
BH
273 }
274 }
bd6223db 275 else {
de42d2df 276 g_log << Logger::Warning << "Notification for " << domain << " to " << ip << " failed after retries" << endl;
bd6223db 277 }
3696224d
BH
278 }
279
280 return d_nq.earliest();
281}
282
a01b2204 283void CommunicatorClass::sendNotification(int sock, const DNSName& domain, const ComboAddress& remote, uint16_t id, UeberBackend* B)
a71bee29 284{
7014a23d 285 vector<string> meta;
6fe866b4
AT
286 DNSName tsigkeyname;
287 DNSName tsigalgorithm;
7014a23d
AT
288 string tsigsecret64;
289 string tsigsecret;
290
3497eb18 291 if (::arg().mustDo("send-signed-notify") && B->getDomainMetadata(domain, "TSIG-ALLOW-AXFR", meta) && meta.size() > 0) {
9d423514 292 tsigkeyname = DNSName(meta[0]);
7014a23d
AT
293 }
294
a71bee29
PD
295 vector<uint8_t> packet;
296 DNSPacketWriter pw(packet, domain, QType::SOA, 1, Opcode::Notify);
297 pw.getHeader()->id = id;
de42d2df 298 pw.getHeader()->aa = true;
a71bee29 299
7014a23d 300 if (tsigkeyname.empty() == false) {
40361bf2
KM
301 if (!B->getTSIGKey(tsigkeyname, tsigalgorithm, tsigsecret64)) {
302 g_log << Logger::Error << "TSIG key '" << tsigkeyname << "' for domain '" << domain << "' not found" << endl;
fba65bff
PL
303 return;
304 }
7014a23d 305 TSIGRecordContent trc;
9d423514
AT
306 if (tsigalgorithm.toStringNoDot() == "hmac-md5")
307 trc.d_algoName = DNSName(tsigalgorithm.toStringNoDot() + ".sig-alg.reg.int.");
7014a23d
AT
308 else
309 trc.d_algoName = tsigalgorithm;
4646277d 310 trc.d_time = time(nullptr);
7014a23d 311 trc.d_fudge = 300;
a01b2204
KM
312 trc.d_origID = ntohs(id);
313 trc.d_eRcode = 0;
fba65bff 314 if (B64Decode(tsigsecret64, tsigsecret) == -1) {
a01b2204 315 g_log << Logger::Error << "Unable to Base-64 decode TSIG key '" << tsigkeyname << "' for domain '" << domain << "'" << endl;
fba65bff
PL
316 return;
317 }
ea3816cf 318 addTSIG(pw, trc, tsigkeyname, tsigsecret, "", false);
7014a23d
AT
319 }
320
a01b2204
KM
321 if (sendto(sock, &packet[0], packet.size(), 0, (struct sockaddr*)(&remote), remote.getSocklen()) < 0) {
322 throw ResolverException("Unable to send notify to " + remote.toStringWithPort() + ": " + stringerror());
a71bee29
PD
323 }
324}
325
a01b2204 326void CommunicatorClass::drillHole(const DNSName& domain, const string& ip)
3696224d 327{
a01b2204 328 (*d_holes.lock())[pair(domain, ip)] = time(nullptr);
3696224d
BH
329}
330
a01b2204 331bool CommunicatorClass::justNotified(const DNSName& domain, const string& ip)
3696224d 332{
01e96795 333 auto holes = d_holes.lock();
a01b2204 334 auto it = holes->find(pair(domain, ip));
01e96795
RG
335 if (it == holes->end()) {
336 // no hole
3696224d 337 return false;
01e96795 338 }
3696224d 339
a01b2204 340 if (it->second > time(nullptr) - 900) {
01e96795 341 // recent hole
3696224d 342 return true;
01e96795 343 }
3696224d 344
de42d2df 345 // do we want to purge this? XXX FIXME
3696224d
BH
346 return false;
347}
348
0c01dd7c 349void CommunicatorClass::makeNotifySockets()
3696224d 350{
a01b2204 351 if (pdns::isQueryLocalAddressFamilyEnabled(AF_INET)) {
20829585 352 d_nsock4 = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET, 0), true, ::arg().mustDo("non-local-bind"));
a01b2204
KM
353 }
354 else {
9d36589a
PL
355 d_nsock4 = -1;
356 }
a01b2204 357 if (pdns::isQueryLocalAddressFamilyEnabled(AF_INET6)) {
20829585 358 d_nsock6 = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET6, 0), true, ::arg().mustDo("non-local-bind"));
a01b2204
KM
359 }
360 else {
0c01dd7c 361 d_nsock6 = -1;
9d36589a 362 }
3696224d
BH
363}
364
a01b2204 365void CommunicatorClass::notify(const DNSName& domain, const string& ip)
3696224d
BH
366{
367 d_nq.add(domain, ip);
3696224d 368}