]>
Commit | Line | Data |
---|---|---|
12c86877 BH |
1 | /* |
2 | PowerDNS Versatile Database Driven Nameserver | |
0c01dd7c | 3 | Copyright (C) 2002 - 2011 PowerDNS.COM BV |
12c86877 BH |
4 | |
5 | This program is free software; you can redistribute it and/or modify | |
a640a9d4 BH |
6 | it under the terms of the GNU General Public License version 2 as |
7 | published by the Free Software Foundation. | |
12c86877 | 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 | ||
12c86877 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 | |
06bd9ccf | 20 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
12c86877 | 21 | */ |
870a0fe4 AT |
22 | #ifdef HAVE_CONFIG_H |
23 | #include "config.h" | |
24 | #endif | |
731f58b8 | 25 | #include "utility.hh" |
12c86877 BH |
26 | #include "resolver.hh" |
27 | #include <pthread.h> | |
28 | #include <semaphore.h> | |
29 | #include <iostream> | |
30 | #include <errno.h> | |
31 | #include "misc.hh" | |
32 | #include <algorithm> | |
33 | #include <sstream> | |
3696224d | 34 | #include "dnsrecords.hh" |
12c86877 BH |
35 | #include <cstring> |
36 | #include <string> | |
37 | #include <vector> | |
b8e0f341 | 38 | #include <boost/algorithm/string.hpp> |
12c86877 BH |
39 | #include "dns.hh" |
40 | #include "qtype.hh" | |
ef03cc09 | 41 | |
5c409fa2 | 42 | #include "pdnsexception.hh" |
12c86877 | 43 | #include "arguments.hh" |
7597b3cf | 44 | #include "base64.hh" |
88c1bc50 BH |
45 | #include "dnswriter.hh" |
46 | #include "dnsparser.hh" | |
dd7da6cd | 47 | |
fa8fd4d2 | 48 | |
afbc704a | 49 | #include "dns_random.hh" |
ef03cc09 | 50 | #include <sys/poll.h> |
51 | #include "gss_context.hh" | |
eb4e3090 | 52 | #include "namespaces.hh" |
12c86877 | 53 | |
0c01dd7c | 54 | int makeQuerySocket(const ComboAddress& local, bool udpOrTCP) |
12c86877 | 55 | { |
0c01dd7c | 56 | ComboAddress ourLocal(local); |
12c86877 | 57 | |
0c01dd7c | 58 | int sock=socket(ourLocal.sin4.sin_family, udpOrTCP ? SOCK_DGRAM : SOCK_STREAM, 0); |
5e2be4cf | 59 | if(sock < 0) { |
883ad072 | 60 | if(errno == EAFNOSUPPORT && local.sin4.sin_family == AF_INET6) { |
0a462407 | 61 | return -1; |
883ad072 YG |
62 | } |
63 | unixDie("Creating local resolver socket for "+ourLocal.toString()); | |
5e2be4cf | 64 | } |
0c01dd7c | 65 | |
883ad072 | 66 | setCloseOnExec(sock); |
eabdf7e0 PD |
67 | if(udpOrTCP) { |
68 | // udp, try hard to bind an unpredictable port | |
0c01dd7c BH |
69 | int tries=10; |
70 | while(--tries) { | |
71 | ourLocal.sin4.sin_port = htons(10000+(dns_random(10000))); | |
72 | ||
73 | if (::bind(sock, (struct sockaddr *)&ourLocal, ourLocal.getSocklen()) >= 0) | |
74 | break; | |
75 | } | |
eabdf7e0 PD |
76 | // cerr<<"bound udp port "<<ourLocal.sin4.sin_port<<", "<<tries<<" tries left"<<endl; |
77 | ||
0c01dd7c | 78 | if(!tries) { |
3897b9e1 | 79 | closesocket(sock); |
3f81d239 | 80 | throw PDNSException("Resolver binding to local UDP socket on "+ourLocal.toString()+": "+stringerror()); |
0c01dd7c | 81 | } |
12c86877 | 82 | } |
0c01dd7c | 83 | else { |
eabdf7e0 PD |
84 | // tcp, let the kernel figure out the port |
85 | // cerr<<"letting kernel pick TCP port"<<endl; | |
0c01dd7c BH |
86 | ourLocal.sin4.sin_port = 0; |
87 | if(::bind(sock, (struct sockaddr *)&ourLocal, ourLocal.getSocklen()) < 0) | |
3f81d239 | 88 | throw PDNSException("Resolver binding to local TCP socket on "+ourLocal.toString()+": "+stringerror()); |
eab7dbda | 89 | } |
0c01dd7c | 90 | return sock; |
12c86877 BH |
91 | } |
92 | ||
93 | Resolver::Resolver() | |
0c01dd7c | 94 | try |
12c86877 | 95 | { |
95302209 | 96 | locals["default4"] = makeQuerySocket(ComboAddress(::arg()["query-local-address"]), true); |
0a2915e2 | 97 | if(!::arg()["query-local-address6"].empty()) |
95302209 | 98 | locals["default6"] = makeQuerySocket(ComboAddress(::arg()["query-local-address6"]), true); |
0c01dd7c | 99 | else |
95302209 | 100 | locals["default6"] = -1; |
12c86877 | 101 | } |
0c01dd7c | 102 | catch(...) { |
95302209 AT |
103 | if(locals["default4"]>=0) |
104 | close(locals["default4"]); | |
0c01dd7c | 105 | throw; |
12c86877 BH |
106 | } |
107 | ||
0c01dd7c | 108 | Resolver::~Resolver() |
12c86877 | 109 | { |
677aa52d AT |
110 | for(std::map<std::string,int>::iterator iter = locals.begin(); iter != locals.end(); iter++) { |
111 | if (iter->second >= 0) | |
4a1ab5ed | 112 | close(iter->second); |
677aa52d | 113 | } |
12c86877 BH |
114 | } |
115 | ||
95302209 | 116 | uint16_t Resolver::sendResolve(const ComboAddress& remote, const ComboAddress& local, |
561434a6 PD |
117 | const DNSName &domain, int type, bool dnssecOK, |
118 | const DNSName& tsigkeyname, const DNSName& tsigalgorithm, | |
7597b3cf | 119 | const string& tsigsecret) |
12c86877 | 120 | { |
f0136bd5 | 121 | uint16_t randomid; |
88c1bc50 BH |
122 | vector<uint8_t> packet; |
123 | DNSPacketWriter pw(packet, domain, type); | |
f0136bd5 | 124 | pw.getHeader()->id = randomid = dns_random(0xffff); |
7eb7ac3d | 125 | |
48afcf83 BH |
126 | if(dnssecOK) { |
127 | pw.addOpt(2800, 0, EDNSOpts::DNSSECOK); | |
128 | pw.commit(); | |
129 | } | |
7eb7ac3d | 130 | |
7597b3cf | 131 | if(!tsigkeyname.empty()) { |
842c8dd2 | 132 | // cerr<<"Adding TSIG to notification, key name: '"<<tsigkeyname<<"', algo: '"<<tsigalgorithm<<"', secret: "<<Base64Encode(tsigsecret)<<endl; |
7597b3cf | 133 | TSIGRecordContent trc; |
290a083d | 134 | if (tsigalgorithm == DNSName("hmac-md5")) |
135 | trc.d_algoName = tsigalgorithm + DNSName("sig-alg.reg.int"); | |
a56bc64d AT |
136 | else |
137 | trc.d_algoName = tsigalgorithm; | |
7597b3cf BH |
138 | trc.d_time = time(0); |
139 | trc.d_fudge = 300; | |
f0136bd5 | 140 | trc.d_origID=ntohs(randomid); |
7597b3cf BH |
141 | trc.d_eRcode=0; |
142 | addTSIG(pw, &trc, tsigkeyname, tsigsecret, "", false); | |
143 | } | |
7eb7ac3d | 144 | |
95302209 AT |
145 | int sock; |
146 | ||
147 | // choose socket based on local | |
148 | if (local.sin4.sin_family == 0) { | |
7eb7ac3d KM |
149 | // up to us. |
150 | sock = remote.sin4.sin_family == AF_INET ? locals["default4"] : locals["default6"]; | |
95302209 | 151 | } else { |
7eb7ac3d KM |
152 | std::string lstr = local.toString(); |
153 | std::map<std::string, int>::iterator lptr; | |
154 | // see if there is a local | |
155 | ||
156 | if ((lptr = locals.find(lstr)) != locals.end()) { | |
157 | sock = lptr->second; | |
158 | } else { | |
159 | // try to make socket | |
160 | sock = makeQuerySocket(local, true); | |
b841314c RG |
161 | if (sock < 0) |
162 | throw ResolverException("Unable to create socket to "+remote.toStringWithPort()+": "+stringerror()); | |
3897b9e1 | 163 | setNonBlocking( sock ); |
7eb7ac3d KM |
164 | locals[lstr] = sock; |
165 | } | |
95302209 | 166 | } |
7eb7ac3d | 167 | |
0c01dd7c BH |
168 | if(sendto(sock, &packet[0], packet.size(), 0, (struct sockaddr*)(&remote), remote.getSocklen()) < 0) { |
169 | throw ResolverException("Unable to ask query of "+remote.toStringWithPort()+": "+stringerror()); | |
170 | } | |
f0136bd5 | 171 | return randomid; |
0c01dd7c | 172 | } |
48afcf83 | 173 | |
561434a6 | 174 | uint16_t Resolver::sendResolve(const ComboAddress& remote, const DNSName &domain, |
95302209 | 175 | int type, bool dnssecOK, |
561434a6 | 176 | const DNSName& tsigkeyname, const DNSName& tsigalgorithm, |
95302209 AT |
177 | const string& tsigsecret) |
178 | { | |
4a1ab5ed AT |
179 | ComboAddress local; |
180 | local.sin4.sin_family = 0; | |
181 | return this->sendResolve(remote, local, domain, type, dnssecOK, tsigkeyname, tsigalgorithm, tsigsecret); | |
95302209 AT |
182 | } |
183 | ||
561434a6 | 184 | static int parseResult(MOADNSParser& mdp, const DNSName& origQname, uint16_t origQtype, uint16_t id, Resolver::res_t* result) |
0c01dd7c BH |
185 | { |
186 | result->clear(); | |
1813c5ff KM |
187 | |
188 | if(mdp.d_header.rcode) | |
ab4994df | 189 | return mdp.d_header.rcode; |
1813c5ff | 190 | |
561434a6 | 191 | if(origQname.countLabels()) { // not AXFR |
ab4994df | 192 | if(mdp.d_header.id != id) |
0c01dd7c | 193 | throw ResolverException("Remote nameserver replied with wrong id"); |
ab4994df BH |
194 | if(mdp.d_header.qdcount != 1) |
195 | throw ResolverException("resolver: received answer with wrong number of questions ("+itoa(mdp.d_header.qdcount)+")"); | |
561434a6 PD |
196 | if(mdp.d_qname != origQname) |
197 | throw ResolverException(string("resolver: received an answer to another question (")+mdp.d_qname.toString()+"!="+ origQname.toString()+".)"); | |
12c86877 | 198 | } |
1813c5ff | 199 | |
914353ca | 200 | vector<DNSResourceRecord> ret; |
0c01dd7c | 201 | DNSResourceRecord rr; |
914353ca | 202 | for(MOADNSParser::answers_t::const_iterator i=mdp.d_answers.begin(); i!=mdp.d_answers.end(); ++i) { |
f809c028 | 203 | rr.qname = i->first.d_name; |
0c01dd7c BH |
204 | rr.qtype = i->first.d_type; |
205 | rr.ttl = i->first.d_ttl; | |
1813c5ff | 206 | rr.content = i->first.d_content->getZoneRepresentation(true); |
0c01dd7c BH |
207 | result->push_back(rr); |
208 | } | |
1813c5ff | 209 | |
0c01dd7c | 210 | return 0; |
98e05fce | 211 | } |
12c86877 | 212 | |
561434a6 | 213 | bool Resolver::tryGetSOASerial(DNSName *domain, uint32_t *theirSerial, uint32_t *theirInception, uint32_t *theirExpire, uint16_t* id) |
98e05fce | 214 | { |
95302209 AT |
215 | struct pollfd *fds = new struct pollfd[locals.size()]; |
216 | size_t i = 0, k; | |
0c01dd7c | 217 | int sock; |
95302209 AT |
218 | |
219 | for(std::map<string,int>::iterator iter=locals.begin(); iter != locals.end(); iter++, i++) { | |
220 | fds[i].fd = iter->second; | |
221 | fds[i].events = POLLIN; | |
222 | } | |
223 | ||
224 | if (poll(fds, i, 250) < 1) { // wait for 0.25s | |
7eb7ac3d KM |
225 | delete [] fds; |
226 | return false; | |
95302209 AT |
227 | } |
228 | ||
229 | sock = -1; | |
230 | ||
231 | // determine who | |
232 | for(k=0;k<i;k++) { | |
233 | if ((fds[k].revents & POLLIN) == POLLIN) { | |
234 | sock = fds[k].fd; | |
235 | break; | |
236 | } | |
237 | } | |
238 | ||
239 | delete [] fds; | |
7eb7ac3d | 240 | |
95302209 | 241 | if (sock < 0) return false; // false alarm |
7eb7ac3d | 242 | |
3696224d BH |
243 | int err; |
244 | ComboAddress fromaddr; | |
245 | socklen_t addrlen=fromaddr.getSocklen(); | |
0c01dd7c BH |
246 | char buf[3000]; |
247 | err = recvfrom(sock, buf, sizeof(buf), 0,(struct sockaddr*)(&fromaddr), &addrlen); | |
3696224d BH |
248 | if(err < 0) { |
249 | if(errno == EAGAIN) | |
250 | return false; | |
95302209 | 251 | |
3696224d BH |
252 | throw ResolverException("recvfrom error waiting for answer: "+stringerror()); |
253 | } | |
95302209 | 254 | |
0c01dd7c | 255 | MOADNSParser mdp((char*)buf, err); |
3696224d | 256 | *id=mdp.d_header.id; |
561434a6 | 257 | *domain = mdp.d_qname; |
3696224d BH |
258 | |
259 | if(mdp.d_answers.empty()) | |
561434a6 | 260 | throw ResolverException("Query to '" + fromaddr.toStringWithPort() + "' for SOA of '" + domain->toString() + "' produced no results (RCode: " + RCode::to_s(mdp.d_header.rcode) + ")"); |
3696224d | 261 | |
48afcf83 | 262 | if(mdp.d_qtype != QType::SOA) |
561434a6 | 263 | throw ResolverException("Query to '" + fromaddr.toStringWithPort() + "' for SOA of '" + domain->toString() + "' returned wrong record type"); |
12c86877 | 264 | |
48afcf83 BH |
265 | *theirInception = *theirExpire = 0; |
266 | bool gotSOA=false; | |
ef7cd021 | 267 | for(const MOADNSParser::answers_t::value_type& drc : mdp.d_answers) { |
48afcf83 | 268 | if(drc.first.d_type == QType::SOA) { |
ba3c54cb RG |
269 | shared_ptr<SOARecordContent> src=getRR<SOARecordContent>(drc.first); |
270 | if (src) { | |
271 | *theirSerial=src->d_st.serial; | |
272 | gotSOA = true; | |
273 | } | |
48afcf83 BH |
274 | } |
275 | if(drc.first.d_type == QType::RRSIG) { | |
ba3c54cb RG |
276 | shared_ptr<RRSIGRecordContent> rrc=getRR<RRSIGRecordContent>(drc.first); |
277 | if(rrc && rrc->d_type == QType::SOA) { | |
232f0877 CH |
278 | *theirInception= std::max(*theirInception, rrc->d_siginception); |
279 | *theirExpire = std::max(*theirExpire, rrc->d_sigexpire); | |
965f9774 | 280 | } |
48afcf83 BH |
281 | } |
282 | } | |
283 | if(!gotSOA) | |
561434a6 | 284 | throw ResolverException("Query to '" + fromaddr.toString() + "' for SOA of '" + domain->toString() + "' did not return a SOA"); |
3696224d BH |
285 | return true; |
286 | } | |
d3491d5a | 287 | |
561434a6 | 288 | int Resolver::resolve(const string &ipport, const DNSName &domain, int type, Resolver::res_t* res, const ComboAddress &local) |
3696224d | 289 | { |
98e05fce | 290 | try { |
0c01dd7c | 291 | ComboAddress to(ipport, 53); |
a370ef31 | 292 | |
95302209 AT |
293 | int id = sendResolve(to, local, domain, type); |
294 | int sock; | |
295 | ||
296 | // choose socket based on local | |
297 | if (local.sin4.sin_family == 0) { | |
7eb7ac3d KM |
298 | // up to us. |
299 | sock = to.sin4.sin_family == AF_INET ? locals["default4"] : locals["default6"]; | |
95302209 | 300 | } else { |
7eb7ac3d KM |
301 | std::string lstr = local.toString(); |
302 | std::map<std::string, int>::iterator lptr; | |
303 | // see if there is a local | |
95302209 | 304 | |
7eb7ac3d KM |
305 | if ((lptr = locals.find(lstr)) != locals.end()) sock = lptr->second; |
306 | else throw ResolverException("sendResolve did not create socket for " + lstr); | |
95302209 AT |
307 | } |
308 | ||
7108e055 | 309 | int err=waitForData(sock, 0, 3000000); |
0c01dd7c BH |
310 | |
311 | if(!err) { | |
312 | throw ResolverException("Timeout waiting for answer"); | |
313 | } | |
314 | if(err < 0) | |
315 | throw ResolverException("Error waiting for answer: "+stringerror()); | |
316 | ||
317 | ComboAddress from; | |
318 | socklen_t addrlen = sizeof(from); | |
319 | char buffer[3000]; | |
320 | int len; | |
321 | ||
322 | if((len=recvfrom(sock, buffer, sizeof(buffer), 0,(struct sockaddr*)(&from), &addrlen)) < 0) | |
323 | throw ResolverException("recvfrom error waiting for answer: "+stringerror()); | |
324 | ||
ab4994df BH |
325 | MOADNSParser mdp(buffer, len); |
326 | return parseResult(mdp, domain, type, id, res); | |
98e05fce BH |
327 | } |
328 | catch(ResolverException &re) { | |
a370ef31 | 329 | throw ResolverException(re.reason+" from "+ipport); |
98e05fce | 330 | } |
0c01dd7c | 331 | return -1; |
98e05fce | 332 | } |
12c86877 | 333 | |
561434a6 | 334 | int Resolver::resolve(const string &ipport, const DNSName &domain, int type, Resolver::res_t* res) { |
4a1ab5ed AT |
335 | ComboAddress local; |
336 | local.sin4.sin_family = 0; | |
337 | return resolve(ipport, domain, type, res, local); | |
95302209 | 338 | } |
12c86877 | 339 | |
561434a6 | 340 | void Resolver::getSoaSerial(const string &ipport, const DNSName &domain, uint32_t *serial) |
0c01dd7c BH |
341 | { |
342 | vector<DNSResourceRecord> res; | |
561434a6 | 343 | int ret = resolve(ipport, domain, QType::SOA, &res); |
3696224d | 344 | |
0c01dd7c | 345 | if(ret || res.empty()) |
561434a6 | 346 | throw ResolverException("Query to '" + ipport + "' for SOA of '" + domain.toString() + "' produced no answers"); |
12c86877 | 347 | |
0c01dd7c | 348 | if(res[0].qtype.getCode() != QType::SOA) |
561434a6 | 349 | throw ResolverException("Query to '" + ipport + "' for SOA of '" + domain.toString() + "' produced a "+res[0].qtype.getName()+" record"); |
0c01dd7c BH |
350 | |
351 | vector<string>parts; | |
352 | stringtok(parts, res[0].content); | |
353 | if(parts.size()<3) | |
561434a6 | 354 | throw ResolverException("Query to '" + ipport + "' for SOA of '" + domain.toString() + "' produced an unparseable response"); |
3696224d | 355 | |
335da0ba | 356 | *serial=pdns_stou(parts[2]); |
12c86877 BH |
357 | } |
358 | ||
fc396d56 | 359 | AXFRRetriever::AXFRRetriever(const ComboAddress& remote, |
98c9ec39 | 360 | const DNSName& domain, |
361 | const TSIGTriplet& tt, | |
362 | const ComboAddress* laddr) | |
363 | : d_tt(tt), d_tsigPos(0), d_nonSignedMessages(0) | |
12c86877 | 364 | { |
0c01dd7c | 365 | ComboAddress local; |
fc396d56 | 366 | if (laddr != NULL) { |
4a1ab5ed | 367 | local = (ComboAddress) (*laddr); |
fc396d56 | 368 | } else { |
4a1ab5ed AT |
369 | if(remote.sin4.sin_family == AF_INET) |
370 | local=ComboAddress(::arg()["query-local-address"]); | |
371 | else if(!::arg()["query-local-address6"].empty()) | |
372 | local=ComboAddress(::arg()["query-local-address6"]); | |
373 | else | |
374 | local=ComboAddress("::"); | |
fc396d56 | 375 | } |
3ba05fa7 BH |
376 | d_sock = -1; |
377 | try { | |
378 | d_sock = makeQuerySocket(local, false); // make a TCP socket | |
b841314c RG |
379 | if (d_sock < 0) |
380 | throw ResolverException("Error creating socket for AXFR request to "+d_remote.toStringWithPort()); | |
3ba05fa7 BH |
381 | d_buf = shared_array<char>(new char[65536]); |
382 | d_remote = remote; // mostly for error reporting | |
383 | this->connect(); | |
384 | d_soacount = 0; | |
385 | ||
386 | vector<uint8_t> packet; | |
387 | DNSPacketWriter pw(packet, domain, QType::AXFR); | |
388 | pw.getHeader()->id = dns_random(0xffff); | |
389 | ||
98c9ec39 | 390 | if(!tt.name.empty()) { |
391 | if (tt.algo == DNSName("hmac-md5")) | |
392 | d_trc.d_algoName = tt.algo + DNSName("sig-alg.reg.int"); | |
a56bc64d | 393 | else |
98c9ec39 | 394 | d_trc.d_algoName = tt.algo; |
3ba05fa7 BH |
395 | d_trc.d_time = time(0); |
396 | d_trc.d_fudge = 300; | |
397 | d_trc.d_origID=ntohs(pw.getHeader()->id); | |
398 | d_trc.d_eRcode=0; | |
98c9ec39 | 399 | addTSIG(pw, &d_trc, tt.name, tt.secret, "", false); |
3ba05fa7 BH |
400 | } |
401 | ||
402 | uint16_t replen=htons(packet.size()); | |
403 | Utility::iovec iov[2]; | |
72a93ecc | 404 | iov[0].iov_base=reinterpret_cast<char*>(&replen); |
3ba05fa7 | 405 | iov[0].iov_len=2; |
72a93ecc | 406 | iov[1].iov_base=packet.data(); |
3ba05fa7 BH |
407 | iov[1].iov_len=packet.size(); |
408 | ||
5b6d751e BH |
409 | int ret=Utility::writev(d_sock, iov, 2); |
410 | if(ret < 0) | |
3ba05fa7 | 411 | throw ResolverException("Error sending question to "+d_remote.toStringWithPort()+": "+stringerror()); |
5b6d751e BH |
412 | if(ret != (int)(2+packet.size())) { |
413 | throw ResolverException("Partial write on AXFR request to "+d_remote.toStringWithPort()); | |
414 | } | |
3ba05fa7 BH |
415 | |
416 | int res = waitForData(d_sock, 10, 0); | |
0c01dd7c | 417 | |
3ba05fa7 BH |
418 | if(!res) |
419 | throw ResolverException("Timeout waiting for answer from "+d_remote.toStringWithPort()+" during AXFR"); | |
420 | if(res<0) | |
421 | throw ResolverException("Error waiting for answer from "+d_remote.toStringWithPort()+": "+stringerror()); | |
422 | } | |
423 | catch(...) { | |
424 | if(d_sock >= 0) | |
425 | close(d_sock); | |
b841314c | 426 | d_sock = -1; |
3ba05fa7 | 427 | throw; |
29b92d6f | 428 | } |
12c86877 BH |
429 | } |
430 | ||
2577467d BH |
431 | AXFRRetriever::~AXFRRetriever() |
432 | { | |
433 | close(d_sock); | |
434 | } | |
12c86877 | 435 | |
54d84273 PD |
436 | |
437 | ||
5aa12996 | 438 | int AXFRRetriever::getChunk(Resolver::res_t &res, vector<DNSRecord>* records) // Implementation is making sure RFC2845 4.4 is followed. |
12c86877 | 439 | { |
0c01dd7c BH |
440 | if(d_soacount > 1) |
441 | return false; | |
236e0c78 | 442 | |
12c86877 BH |
443 | // d_sock is connected and is about to spit out a packet |
444 | int len=getLength(); | |
445 | if(len<0) | |
25b1d5d7 | 446 | throw ResolverException("EOF trying to read axfr chunk from remote TCP client"); |
12c86877 | 447 | |
0c01dd7c | 448 | timeoutReadn(len); |
ab4994df | 449 | MOADNSParser mdp(d_buf.get(), len); |
236e0c78 | 450 | |
5aa12996 | 451 | int err; |
452 | if(!records) | |
453 | err=parseResult(mdp, DNSName(), 0, 0, &res); | |
454 | else { | |
455 | records->clear(); | |
456 | for(const auto& r: mdp.d_answers) | |
457 | records->push_back(r.first); | |
458 | err = mdp.d_header.rcode; | |
459 | } | |
460 | ||
236e0c78 | 461 | if(err) |
bf491f9b | 462 | throw ResolverException("AXFR chunk error: " + RCode::to_s(err)); |
236e0c78 | 463 | |
ef7cd021 | 464 | for(const MOADNSParser::answers_t::value_type& answer : mdp.d_answers) |
236e0c78 PD |
465 | if (answer.first.d_type == QType::SOA) |
466 | d_soacount++; | |
467 | ||
98c9ec39 | 468 | if(!d_tt.name.empty()) { // TSIG verify message |
236e0c78 PD |
469 | // If we have multiple messages, we need to concatenate them together. We also need to make sure we know the location of |
470 | // the TSIG record so we can remove it in makeTSIGMessageFromTSIGPacket | |
471 | d_signData.append(d_buf.get(), len); | |
472 | if (mdp.getTSIGPos() == 0) | |
473 | d_tsigPos += len; | |
474 | else | |
475 | d_tsigPos += mdp.getTSIGPos(); | |
476 | ||
ab4994df | 477 | string theirMac; |
54d84273 | 478 | bool checkTSIG = false; |
236e0c78 | 479 | |
ef7cd021 | 480 | for(const MOADNSParser::answers_t::value_type& answer : mdp.d_answers) { |
236e0c78 | 481 | if (answer.first.d_type == QType::SOA) // A SOA is either the first or the last record. We need to check TSIG if that's the case. |
54d84273 | 482 | checkTSIG = true; |
236e0c78 | 483 | |
5b6d751e | 484 | if(answer.first.d_type == QType::TSIG) { |
ba3c54cb RG |
485 | shared_ptr<TSIGRecordContent> trc = getRR<TSIGRecordContent>(answer.first); |
486 | if(trc) { | |
487 | theirMac = trc->d_mac; | |
488 | d_trc.d_time = trc->d_time; | |
489 | checkTSIG = true; | |
490 | } | |
5b6d751e | 491 | } |
ab4994df | 492 | } |
54d84273 | 493 | |
236e0c78 PD |
494 | if( ! checkTSIG && d_nonSignedMessages > 99) { // We're allowed to get 100 digest without a TSIG. |
495 | throw ResolverException("No TSIG message received in last 100 messages of AXFR transfer."); | |
54d84273 | 496 | } |
54d84273 | 497 | |
54d84273 | 498 | if (checkTSIG) { |
236e0c78 | 499 | if (theirMac.empty()) |
98c9ec39 | 500 | throw ResolverException("No TSIG on AXFR response from "+d_remote.toStringWithPort()+" , should be signed with TSIG key '"+d_tt.name.toString()+"'"); |
236e0c78 | 501 | |
54d84273 PD |
502 | string message; |
503 | if (!d_prevMac.empty()) { | |
98c9ec39 | 504 | message = makeTSIGMessageFromTSIGPacket(d_signData, d_tsigPos, d_tt.name, d_trc, d_prevMac, true, d_signData.size()-len); |
54d84273 | 505 | } else { |
98c9ec39 | 506 | message = makeTSIGMessageFromTSIGPacket(d_signData, d_tsigPos, d_tt.name, d_trc, d_trc.d_mac, false); |
54d84273 | 507 | } |
e820c305 | 508 | |
785594c9 KM |
509 | TSIGHashEnum algo; |
510 | if (!getTSIGHashEnum(d_trc.d_algoName, algo)) { | |
4a51ff72 | 511 | throw ResolverException("Unsupported TSIG HMAC algorithm " + d_trc.d_algoName.toString()); |
e820c305 AT |
512 | } |
513 | ||
7f9ac49b | 514 | if (algo == TSIG_GSS) { |
98c9ec39 | 515 | GssContext gssctx(d_tt.name); |
516 | if (!gss_verify_signature(d_tt.name, message, theirMac)) { | |
517 | throw ResolverException("Signature failed to validate on AXFR response from "+d_remote.toStringWithPort()+" signed with TSIG key '"+d_tt.name.toString()+"'"); | |
7f9ac49b AT |
518 | } |
519 | } else { | |
98c9ec39 | 520 | string ourMac=calculateHMAC(d_tt.secret, message, algo); |
236e0c78 | 521 | |
7f9ac49b AT |
522 | // ourMac[0]++; // sabotage == for testing :-) |
523 | if(ourMac != theirMac) { | |
98c9ec39 | 524 | throw ResolverException("Signature failed to validate on AXFR response from "+d_remote.toStringWithPort()+" signed with TSIG key '"+d_tt.name.toString()+"'"); |
7f9ac49b | 525 | } |
54d84273 | 526 | } |
a426be78 | 527 | |
236e0c78 PD |
528 | // Reset and store some values for the next chunks. |
529 | d_prevMac = theirMac; | |
530 | d_nonSignedMessages = 0; | |
531 | d_signData.clear(); | |
532 | d_tsigPos = 0; | |
54d84273 | 533 | } |
236e0c78 PD |
534 | else |
535 | d_nonSignedMessages++; | |
54d84273 | 536 | } |
236e0c78 | 537 | |
0c01dd7c BH |
538 | return true; |
539 | } | |
12c86877 | 540 | |
0c01dd7c BH |
541 | void AXFRRetriever::timeoutReadn(uint16_t bytes) |
542 | { | |
543 | time_t start=time(0); | |
544 | int n=0; | |
545 | int numread; | |
546 | while(n<bytes) { | |
016a0abb PD |
547 | int res=waitForData(d_sock, 10-(time(0)-start)); |
548 | if(res<0) | |
0c01dd7c | 549 | throw ResolverException("Reading data from remote nameserver over TCP: "+stringerror()); |
016a0abb PD |
550 | if(!res) |
551 | throw ResolverException("Timeout while reading data from remote nameserver over TCP"); | |
0c01dd7c BH |
552 | |
553 | numread=recv(d_sock, d_buf.get()+n, bytes-n, 0); | |
554 | if(numread<0) | |
555 | throw ResolverException("Reading data from remote nameserver over TCP: "+stringerror()); | |
556 | if(numread==0) | |
557 | throw ResolverException("Remote nameserver closed TCP connection"); | |
558 | n+=numread; | |
559 | } | |
12c86877 BH |
560 | } |
561 | ||
0c01dd7c | 562 | void AXFRRetriever::connect() |
12c86877 | 563 | { |
3897b9e1 | 564 | setNonBlocking( d_sock ); |
0c01dd7c BH |
565 | |
566 | int err; | |
567 | ||
568 | if((err=::connect(d_sock,(struct sockaddr*)&d_remote, d_remote.getSocklen()))<0 && errno!=EINPROGRESS) { | |
3897b9e1 | 569 | closesocket(d_sock); |
0c01dd7c BH |
570 | d_sock=-1; |
571 | throw ResolverException("connect: "+stringerror()); | |
88c1bc50 | 572 | } |
0c01dd7c BH |
573 | |
574 | if(!err) | |
575 | goto done; | |
576 | ||
577 | err=waitForRWData(d_sock, false, 10, 0); // wait for writeability | |
578 | ||
579 | if(!err) { | |
3897b9e1 | 580 | closesocket(d_sock); // timeout |
0c01dd7c BH |
581 | d_sock=-1; |
582 | errno=ETIMEDOUT; | |
583 | ||
584 | throw ResolverException("Timeout connecting to server"); | |
88c1bc50 | 585 | } |
0c01dd7c BH |
586 | else if(err < 0) { |
587 | throw ResolverException("Error connecting: "+string(strerror(err))); | |
849fde0b | 588 | } |
0c01dd7c BH |
589 | else { |
590 | Utility::socklen_t len=sizeof(err); | |
591 | if(getsockopt(d_sock, SOL_SOCKET,SO_ERROR,(char *)&err,&len)<0) | |
592 | throw ResolverException("Error connecting: "+stringerror()); // Solaris | |
593 | ||
594 | if(err) | |
595 | throw ResolverException("Error connecting: "+string(strerror(err))); | |
8b3cfcd3 | 596 | } |
0c01dd7c BH |
597 | |
598 | done: | |
3897b9e1 | 599 | setBlocking( d_sock ); |
0c01dd7c | 600 | // d_sock now connected |
12c86877 BH |
601 | } |
602 | ||
0c01dd7c | 603 | int AXFRRetriever::getLength() |
98e05fce | 604 | { |
0c01dd7c BH |
605 | timeoutReadn(2); |
606 | return (unsigned char)d_buf[0]*256+(unsigned char)d_buf[1]; | |
12c86877 BH |
607 | } |
608 |