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