]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/mastercommunicator.cc
Merge pull request #14021 from Habbie/auth-lua-join-whitespace
[thirdparty/pdns.git] / pdns / mastercommunicator.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 #include "auth-caches.hh"
26 #include "utility.hh"
27 #include <errno.h>
28 #include "communicator.hh"
29 #include <set>
30 #include <boost/utility.hpp>
31
32 #include "dnsbackend.hh"
33 #include "ueberbackend.hh"
34 #include "packethandler.hh"
35 #include "nameserver.hh"
36 #include "resolver.hh"
37 #include "logger.hh"
38 #include "dns.hh"
39 #include "arguments.hh"
40 #include "packetcache.hh"
41 #include "base64.hh"
42 #include "namespaces.hh"
43
44
45 void CommunicatorClass::queueNotifyDomain(const DomainInfo& di, UeberBackend* B)
46 {
47 bool hasQueuedItem=false;
48 set<string> nsset, ips;
49 DNSZoneRecord rr;
50 FindNS fns;
51
52
53 try {
54 if (d_onlyNotify.size()) {
55 B->lookup(QType(QType::NS), di.zone);
56 while(B->get(rr))
57 nsset.insert(getRR<NSRecordContent>(rr.dr)->getNS().toString());
58
59 for(set<string>::const_iterator j=nsset.begin();j!=nsset.end();++j) {
60 vector<string> nsips=fns.lookup(DNSName(*j), B);
61 if(nsips.empty())
62 g_log<<Logger::Warning<<"Unable to queue notification of domain '"<<di.zone<<"': nameservers do not resolve!"<<endl;
63 else
64 for(vector<string>::const_iterator k=nsips.begin();k!=nsips.end();++k) {
65 const ComboAddress caIp(*k, 53);
66 if(!d_preventSelfNotification || !AddressIsUs(caIp)) {
67 if(!d_onlyNotify.match(&caIp))
68 g_log<<Logger::Info<<"Skipped notification of domain '"<<di.zone<<"' to "<<*j<<" because it does not match only-notify."<<endl;
69 else
70 ips.insert(caIp.toStringWithPort());
71 }
72 }
73 }
74
75 for(set<string>::const_iterator j=ips.begin();j!=ips.end();++j) {
76 g_log<<Logger::Warning<<"Queued notification of domain '"<<di.zone<<"' to "<<*j<<endl;
77 d_nq.add(di.zone,*j);
78 hasQueuedItem=true;
79 }
80 }
81 }
82 catch (PDNSException &ae) {
83 g_log << Logger::Error << "Error looking up name servers for " << di.zone << ", cannot notify: " << ae.reason << endl;
84 return;
85 }
86 catch (std::exception &e) {
87 g_log << Logger::Error << "Error looking up name servers for " << di.zone << ", cannot notify: " << e.what() << endl;
88 return;
89 }
90
91
92 set<string> alsoNotify(d_alsoNotify);
93 B->alsoNotifies(di.zone, &alsoNotify);
94
95 for(set<string>::const_iterator j=alsoNotify.begin();j!=alsoNotify.end();++j) {
96 try {
97 const ComboAddress caIp(*j, 53);
98 g_log<<Logger::Warning<<"Queued also-notification of domain '"<<di.zone<<"' to "<<caIp.toStringWithPort()<<endl;
99 if (!ips.count(caIp.toStringWithPort())) {
100 ips.insert(caIp.toStringWithPort());
101 d_nq.add(di.zone, caIp.toStringWithPort());
102 }
103 hasQueuedItem=true;
104 }
105 catch(PDNSException &e) {
106 g_log<<Logger::Warning<<"Unparseable IP in ALSO-NOTIFY metadata of domain '"<<di.zone<<"'. Warning: "<<e.reason<<endl;
107 }
108 }
109
110 if (!hasQueuedItem)
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;
112 }
113
114
115 bool CommunicatorClass::notifyDomain(const DNSName &domain)
116 {
117 DomainInfo di;
118 UeberBackend B;
119 if(!B.getDomainInfo(domain, di)) {
120 g_log<<Logger::Error<<"No such domain '"<<domain<<"' in our database"<<endl;
121 return false;
122 }
123 queueNotifyDomain(di, &B);
124 // call backend and tell them we sent out the notification - even though that is premature
125 di.backend->setNotified(di.id, di.serial);
126
127 return true;
128 }
129
130 void NotificationQueue::dump()
131 {
132 cerr<<"Waiting for notification responses: "<<endl;
133 for(NotificationRequest& nr : d_nqueue) {
134 cerr<<nr.domain<<", "<<nr.ip<<endl;
135 }
136 }
137
138 void CommunicatorClass::masterUpdateCheck(PacketHandler *P)
139 {
140 if(!::arg().mustDo("master"))
141 return;
142
143 UeberBackend *B=P->getBackend();
144 vector<DomainInfo> cmdomains;
145 B->getUpdatedMasters(&cmdomains);
146
147 if(cmdomains.empty()) {
148 if(d_masterschanged)
149 g_log<<Logger::Warning<<"No master domains need notifications"<<endl;
150 d_masterschanged=false;
151 }
152 else {
153 d_masterschanged=true;
154 g_log<<Logger::Error<<cmdomains.size()<<" domain"<<(cmdomains.size()>1 ? "s" : "")<<" for which we are master need"<<
155 (cmdomains.size()>1 ? "" : "s")<<
156 " notifications"<<endl;
157 }
158
159 // figure out A records of everybody needing notification
160 // do this via the FindNS class, d_fns
161
162 for(auto& di : cmdomains) {
163 purgeAuthCachesExact(di.zone);
164 queueNotifyDomain(di, B);
165 di.backend->setNotified(di.id, di.serial);
166 }
167 }
168
169 time_t CommunicatorClass::doNotifications()
170 {
171 ComboAddress from;
172 Utility::socklen_t fromlen;
173 char buffer[1500];
174 int sock;
175 ssize_t size;
176 set<int> fds = {d_nsock4, d_nsock6};
177
178 // receive incoming notifications on the nonblocking socket and take them off the list
179 while(waitForMultiData(fds, 0, 0, &sock) > 0) {
180 fromlen=sizeof(from);
181 size=recvfrom(sock,buffer,sizeof(buffer),0,(struct sockaddr *)&from,&fromlen);
182 if(size < 0)
183 break;
184 DNSPacket p(true);
185
186 p.setRemote(&from);
187
188 if(p.parse(buffer,(size_t)size)<0) {
189 g_log<<Logger::Warning<<"Unable to parse SOA notification answer from "<<p.getRemote()<<endl;
190 continue;
191 }
192
193 if(p.d.rcode)
194 g_log<<Logger::Warning<<"Received unsuccessful notification report for '"<<p.qdomain<<"' from "<<from.toStringWithPort()<<", error: "<<RCode::to_s(p.d.rcode)<<endl;
195
196 if(d_nq.removeIf(from.toStringWithPort(), p.d.id, p.qdomain))
197 g_log<<Logger::Warning<<"Removed from notification list: '"<<p.qdomain<<"' to "<<from.toStringWithPort()<<" "<< (p.d.rcode ? RCode::to_s(p.d.rcode) : "(was acknowledged)")<<endl;
198 else {
199 g_log<<Logger::Warning<<"Received spurious notify answer for '"<<p.qdomain<<"' from "<< from.toStringWithPort()<<endl;
200 //d_nq.dump();
201 }
202 }
203
204 // send out possible new notifications
205 DNSName domain;
206 string ip;
207 uint16_t id;
208
209 bool purged;
210 while(d_nq.getOne(domain, ip, &id, purged)) {
211 if(!purged) {
212 try {
213 ComboAddress remote(ip, 53); // default to 53
214 if((d_nsock6 < 0 && remote.sin4.sin_family == AF_INET6) ||
215 (d_nsock4 < 0 && remote.sin4.sin_family == AF_INET)) {
216 g_log<<Logger::Warning<<"Unable to notify "<<remote.toStringWithPort()<<" for domain '"<<domain<<"', address family is disabled. Is query-local-address"<<(remote.sin4.sin_family == AF_INET ? "" : "6")<<" unset?"<<endl;
217 d_nq.removeIf(remote.toStringWithPort(), id, domain); // Remove, we'll never be able to notify
218 continue; // don't try to notify what we can't!
219 }
220 if(d_preventSelfNotification && AddressIsUs(remote))
221 continue;
222
223 sendNotification(remote.sin4.sin_family == AF_INET ? d_nsock4 : d_nsock6, domain, remote, id);
224 drillHole(domain, ip);
225 }
226 catch(ResolverException &re) {
227 g_log<<Logger::Error<<"Error trying to resolve '"<<ip<<"' for notifying '"<<domain<<"' to server: "<<re.reason<<endl;
228 }
229 }
230 else
231 g_log<<Logger::Error<<"Notification for "<<domain<<" to "<<ip<<" failed after retries"<<endl;
232 }
233
234 return d_nq.earliest();
235 }
236
237 void CommunicatorClass::sendNotification(int sock, const DNSName& domain, const ComboAddress& remote, uint16_t id)
238 {
239 UeberBackend B;
240 vector<string> meta;
241 DNSName tsigkeyname;
242 DNSName tsigalgorithm;
243 string tsigsecret64;
244 string tsigsecret;
245
246 if (::arg().mustDo("send-signed-notify") && B.getDomainMetadata(domain, "TSIG-ALLOW-AXFR", meta) && meta.size() > 0) {
247 tsigkeyname = DNSName(meta[0]);
248 }
249
250 vector<uint8_t> packet;
251 DNSPacketWriter pw(packet, domain, QType::SOA, 1, Opcode::Notify);
252 pw.getHeader()->id = id;
253 pw.getHeader()->aa = true;
254
255 if (tsigkeyname.empty() == false) {
256 if (!B.getTSIGKey(tsigkeyname, &tsigalgorithm, &tsigsecret64)) {
257 g_log<<Logger::Error<<"TSIG key '"<<tsigkeyname<<"' for domain '"<<domain<<"' not found"<<endl;
258 return;
259 }
260 TSIGRecordContent trc;
261 if (tsigalgorithm.toStringNoDot() == "hmac-md5")
262 trc.d_algoName = DNSName(tsigalgorithm.toStringNoDot() + ".sig-alg.reg.int.");
263 else
264 trc.d_algoName = tsigalgorithm;
265 trc.d_time = time(0);
266 trc.d_fudge = 300;
267 trc.d_origID=ntohs(id);
268 trc.d_eRcode=0;
269 if (B64Decode(tsigsecret64, tsigsecret) == -1) {
270 g_log<<Logger::Error<<"Unable to Base-64 decode TSIG key '"<<tsigkeyname<<"' for domain '"<<domain<<"'"<<endl;
271 return;
272 }
273 addTSIG(pw, trc, tsigkeyname, tsigsecret, "", false);
274 }
275
276 if(sendto(sock, &packet[0], packet.size(), 0, (struct sockaddr*)(&remote), remote.getSocklen()) < 0) {
277 throw ResolverException("Unable to send notify to "+remote.toStringWithPort()+": "+stringerror());
278 }
279 }
280
281 void CommunicatorClass::drillHole(const DNSName &domain, const string &ip)
282 {
283 Lock l(&d_holelock);
284 d_holes[make_pair(domain,ip)]=time(0);
285 }
286
287 bool CommunicatorClass::justNotified(const DNSName &domain, const string &ip)
288 {
289 Lock l(&d_holelock);
290 if(d_holes.find(make_pair(domain,ip))==d_holes.end()) // no hole
291 return false;
292
293 if(d_holes[make_pair(domain,ip)]>time(0)-900) // recent hole
294 return true;
295
296 // do we want to purge this? XXX FIXME
297 return false;
298 }
299
300 void CommunicatorClass::makeNotifySockets()
301 {
302 if(!::arg()["query-local-address"].empty()) {
303 d_nsock4 = makeQuerySocket(ComboAddress(::arg()["query-local-address"]), true, ::arg().mustDo("non-local-bind"));
304 } else {
305 d_nsock4 = -1;
306 }
307 if(!::arg()["query-local-address6"].empty()) {
308 d_nsock6 = makeQuerySocket(ComboAddress(::arg()["query-local-address6"]), true, ::arg().mustDo("non-local-bind"));
309 } else {
310 d_nsock6 = -1;
311 }
312 }
313
314 void CommunicatorClass::notify(const DNSName &domain, const string &ip)
315 {
316 d_nq.add(domain, ip);
317 d_any_sem.post();
318 }