]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/ixfr.cc
rec: allow exception to proxy protocal usage for specific listen addresses
[thirdparty/pdns.git] / pdns / ixfr.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 #include "ixfr.hh"
23 #include "sstuff.hh"
24 #include "dns_random.hh"
25 #include "dnsrecords.hh"
26 #include "dnssecinfra.hh"
27 #include "tsigverifier.hh"
28
29 vector<pair<vector<DNSRecord>, vector<DNSRecord> > > processIXFRRecords(const ComboAddress& primary, const DNSName& zone,
30 const vector<DNSRecord>& records, const std::shared_ptr<const SOARecordContent>& primarySOA)
31 {
32 vector<pair<vector<DNSRecord>, vector<DNSRecord> > > ret;
33
34 if (records.size() == 0 || primarySOA == nullptr) {
35 return ret;
36 }
37
38 // we start at 1 to skip the first SOA record
39 // we don't increase pos because the final SOA
40 // of the previous sequence is also the first SOA
41 // of this one
42 for(unsigned int pos = 1; pos < records.size(); ) {
43 vector<DNSRecord> remove, add;
44
45 // cerr<<"Looking at record in position "<<pos<<" of type "<<QType(records[pos].d_type).getName()<<endl;
46
47 if (records[pos].d_type != QType::SOA) {
48 // this is an actual AXFR!
49 return {{remove, records}};
50 }
51
52 auto sr = getRR<SOARecordContent>(records[pos]);
53 if (!sr) {
54 throw std::runtime_error("Error getting the content of the first SOA record of this IXFR sequence for zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort()+"'");
55 }
56
57 // cerr<<"Serial is "<<sr->d_st.serial<<", final serial is "<<primarySOA->d_st.serial<<endl;
58
59 // the serial of this SOA record is the serial of the
60 // zone before the removals and updates of this sequence
61 if (sr->d_st.serial == primarySOA->d_st.serial) {
62 if (records.size() == 2) {
63 // if the entire update is two SOAs records with the same
64 // serial, this is actually an empty AXFR!
65 return {{remove, records}};
66 }
67
68 // if it's the final SOA, there is nothing for us to see
69 break;
70 }
71
72 remove.push_back(records[pos]); // this adds the SOA
73
74 // process removals
75 for(pos++; pos < records.size() && records[pos].d_type != QType::SOA; ++pos) {
76 remove.push_back(records[pos]);
77 }
78
79 if (pos >= records.size()) {
80 throw std::runtime_error("No SOA record to finish the removals part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + primary.toStringWithPort());
81 }
82
83 sr = getRR<SOARecordContent>(records[pos]);
84 if (!sr) {
85 throw std::runtime_error("Invalid SOA record to finish the removals part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + primary.toStringWithPort());
86 }
87
88 // this is the serial of the zone after the removals
89 // and updates, but that might not be the final serial
90 // because there might be several sequences
91 uint32_t newSerial = sr->d_st.serial;
92 add.push_back(records[pos]); // this adds the new SOA
93
94 // process additions
95 for(pos++; pos < records.size() && records[pos].d_type != QType::SOA; ++pos) {
96 add.push_back(records[pos]);
97 }
98
99 if (pos >= records.size()) {
100 throw std::runtime_error("No SOA record to finish the additions part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + primary.toStringWithPort());
101 }
102
103 sr = getRR<SOARecordContent>(records[pos]);
104 if (!sr) {
105 throw std::runtime_error("Invalid SOA record to finish the additions part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + primary.toStringWithPort());
106 }
107
108 if (sr->d_st.serial != newSerial) {
109 throw std::runtime_error("Invalid serial (" + std::to_string(sr->d_st.serial) + ", expecting " + std::to_string(newSerial) + ") in the SOA record finishing the additions part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + primary.toStringWithPort());
110 }
111
112 if (newSerial == primarySOA->d_st.serial) {
113 // this was the last sequence
114 if (pos != (records.size() - 1)) {
115 throw std::runtime_error("Trailing records after the last IXFR sequence of zone '" + zone.toLogString() + "' from " + primary.toStringWithPort());
116 }
117 }
118
119 ret.emplace_back(remove, add);
120 }
121
122 return ret;
123 }
124
125 // Returns pairs of "remove & add" vectors. If you get an empty remove, it means you got an AXFR!
126 // NOLINTNEXTLINE(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791
127 vector<pair<vector<DNSRecord>, vector<DNSRecord>>> getIXFRDeltas(const ComboAddress& primary, const DNSName& zone, const DNSRecord& oursr,
128 uint16_t xfrTimeout, bool totalTimeout,
129 const TSIGTriplet& tt, const ComboAddress* laddr, size_t maxReceivedBytes)
130 {
131 // Auth documents xfrTimeout to be a max idle time (sets totalTimeout=false)
132 // Rec documents it to be a total XFR time (sets totalTimeout=true)
133 //
134 vector<pair<vector<DNSRecord>, vector<DNSRecord> > > ret;
135 vector<uint8_t> packet;
136 DNSPacketWriter pw(packet, zone, QType::IXFR);
137 pw.getHeader()->qr=0;
138 pw.getHeader()->rd=0;
139 pw.getHeader()->id=dns_random_uint16();
140 pw.startRecord(zone, QType::SOA, 0, QClass::IN, DNSResourceRecord::AUTHORITY);
141 oursr.getContent()->toPacket(pw);
142
143 pw.commit();
144 TSIGRecordContent trc;
145 TSIGTCPVerifier tsigVerifier(tt, primary, trc);
146 if(!tt.algo.empty()) {
147 TSIGHashEnum the;
148 getTSIGHashEnum(tt.algo, the);
149 try {
150 trc.d_algoName = getTSIGAlgoName(the);
151 } catch(PDNSException& pe) {
152 throw std::runtime_error("TSIG algorithm '"+tt.algo.toLogString()+"' is unknown.");
153 }
154 trc.d_time = time((time_t*)nullptr);
155 trc.d_fudge = 300;
156 trc.d_origID=ntohs(pw.getHeader()->id);
157 trc.d_eRcode=0;
158 addTSIG(pw, trc, tt.name, tt.secret, "", false);
159 }
160 uint16_t len=htons(packet.size());
161 string msg((const char*)&len, 2);
162 msg.append((const char*)&packet[0], packet.size());
163
164 Socket s(primary.sin4.sin_family, SOCK_STREAM);
165 if (laddr != nullptr) {
166 s.bind(*laddr);
167 }
168 s.setNonBlocking();
169
170 const time_t xfrStart = time(nullptr);
171
172 // Helper function: if we have a total timeout, check it and set elapsed to the total time taken sofar,
173 // otherwise set elapsed to 0, making the total time limit ineffective
174 const auto timeoutChecker = [=] () -> time_t {
175 time_t elapsed = 0;
176 if (totalTimeout) {
177 elapsed = time(nullptr) - xfrStart;
178 if (elapsed >= xfrTimeout) {
179 throw std::runtime_error("Reached the maximum elapsed time in an IXFR delta for zone '" + zone.toLogString() + "' from primary " + primary.toStringWithPort());
180 }
181 }
182 return elapsed;
183 };
184
185 s.connect(primary, xfrTimeout);
186
187 time_t elapsed = timeoutChecker();
188 // coverity[store_truncates_time_t]
189 s.writenWithTimeout(msg.data(), msg.size(), xfrTimeout - elapsed);
190
191 // CURRENT PRIMARY SOA
192 // REPEAT:
193 // SOA WHERE THIS DELTA STARTS
194 // RECORDS TO REMOVE
195 // SOA WHERE THIS DELTA GOES
196 // RECORDS TO ADD
197 // CURRENT PRIMARY SOA
198 std::shared_ptr<const SOARecordContent> primarySOA = nullptr;
199 vector<DNSRecord> records;
200 size_t receivedBytes = 0;
201 std::string reply;
202
203 enum transferStyle { Unknown, AXFR, IXFR } style = Unknown;
204 const unsigned int expectedSOAForAXFR = 2;
205 const unsigned int expectedSOAForIXFR = 3;
206 unsigned int primarySOACount = 0;
207
208 std::string state;
209 for (;;) {
210 // IXFR or AXFR style end reached? We don't want to process trailing data after the closing SOA
211 if (style == AXFR && primarySOACount == expectedSOAForAXFR) {
212 state = "AXFRdone";
213 break;
214 }
215 if (style == IXFR && primarySOACount == expectedSOAForIXFR) {
216 state = "IXFRdone";
217 break;
218 }
219
220 elapsed = timeoutChecker();
221 try {
222 const struct timeval remainingTime = { .tv_sec = xfrTimeout - elapsed, .tv_usec = 0 };
223 const struct timeval idleTime = remainingTime;
224 readn2WithTimeout(s.getHandle(), &len, sizeof(len), idleTime, remainingTime, false);
225 }
226 catch (const runtime_error& ex) {
227 state = ex.what();
228 break;
229 }
230
231 len = ntohs(len);
232 if (len == 0) {
233 state = "zeroLen";
234 break;
235 }
236 // Currently no more break statements after this
237
238 if (maxReceivedBytes > 0 && (maxReceivedBytes - receivedBytes) < (size_t) len) {
239 throw std::runtime_error("Reached the maximum number of received bytes in an IXFR delta for zone '"+zone.toLogString()+"' from primary "+primary.toStringWithPort());
240 }
241
242 reply.resize(len);
243
244 elapsed = timeoutChecker();
245 const struct timeval remainingTime = { .tv_sec = xfrTimeout - elapsed, .tv_usec = 0 };
246 const struct timeval idleTime = remainingTime;
247 readn2WithTimeout(s.getHandle(), reply.data(), len, idleTime, remainingTime, false);
248 receivedBytes += len;
249
250 MOADNSParser mdp(false, reply);
251 if (mdp.d_header.rcode) {
252 throw std::runtime_error("Got an error trying to IXFR zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort()+"': "+RCode::to_s(mdp.d_header.rcode));
253 }
254
255 if (!tt.algo.empty()) { // TSIG verify message
256 tsigVerifier.check(reply, mdp);
257 }
258
259 for (auto& r: mdp.d_answers) {
260 if(!primarySOA) {
261 // we have not seen the first SOA record yet
262 if (r.first.d_type != QType::SOA) {
263 throw std::runtime_error("The first record of the IXFR answer for zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort()+"' is not a SOA ("+QType(r.first.d_type).toString()+")");
264 }
265
266 auto sr = getRR<SOARecordContent>(r.first);
267 if (!sr) {
268 throw std::runtime_error("Error getting the content of the first SOA record of the IXFR answer for zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort()+"'");
269 }
270
271 if(sr->d_st.serial == getRR<SOARecordContent>(oursr)->d_st.serial) {
272 // we are up to date
273 return ret;
274 }
275 primarySOA = std::move(sr);
276 ++primarySOACount;
277 } else if (r.first.d_type == QType::SOA) {
278 auto sr = getRR<SOARecordContent>(r.first);
279 if (!sr) {
280 throw std::runtime_error("Error getting the content of SOA record of IXFR answer for zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort()+"'");
281 }
282
283 // we hit a marker SOA record
284 if (primarySOA->d_st.serial == sr->d_st.serial) {
285 ++primarySOACount;
286 }
287 }
288 // When we see the 2nd record, we can decide what the style is
289 if (records.size() == 1 && style == Unknown) {
290 if (r.first.d_type != QType::SOA) {
291 // Non-empty AXFR style has a non-SOA record following the first SOA
292 style = AXFR;
293 }
294 else if (primarySOACount == expectedSOAForAXFR) {
295 // Empty zone AXFR style: start SOA is immediately followed by end marker SOA
296 style = AXFR;
297 }
298 else {
299 // IXFR has a 2nd SOA (with different serial) following the first
300 style = IXFR;
301 }
302 }
303
304 if(r.first.d_place != DNSResourceRecord::ANSWER) {
305 if(r.first.d_type == QType::TSIG)
306 continue;
307
308 if(r.first.d_type == QType::OPT)
309 continue;
310
311 throw std::runtime_error("Unexpected record (" +QType(r.first.d_type).toString()+") in non-answer section ("+std::to_string(r.first.d_place)+") in IXFR response for zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort());
312 }
313
314 r.first.d_name.makeUsRelative(zone);
315 records.push_back(r.first);
316 }
317 }
318
319 switch (style) {
320 case IXFR:
321 if (primarySOACount != expectedSOAForIXFR) {
322 throw std::runtime_error("Incomplete IXFR transfer (primarySOACount=" + std::to_string(primarySOACount) + ") for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort() + " state=" + state);
323 }
324 break;
325 case AXFR:
326 if (primarySOACount != expectedSOAForAXFR){
327 throw std::runtime_error("Incomplete AXFR style transfer (primarySOACount=" + std::to_string(primarySOACount) + ") for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort() + " state=" + state);
328 }
329 break;
330 case Unknown:
331 throw std::runtime_error("Incomplete XFR (primarySOACount=" + std::to_string(primarySOACount) + ") for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort() + " state=" + state);
332 break;
333 }
334
335 return processIXFRRecords(primary, zone, records, primarySOA);
336 }