]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/ixfrdist.cc
ixfrdist: clarify exit message
[thirdparty/pdns.git] / pdns / ixfrdist.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 <boost/program_options.hpp>
26 #include <arpa/inet.h>
27 #include <sys/types.h>
28 #include <grp.h>
29 #include <pwd.h>
30 #include <sys/stat.h>
31 #include <mutex>
32 #include <thread>
33 #include <dirent.h>
34 #include <queue>
35 #include <condition_variable>
36 #include "ixfr.hh"
37 #include "ixfrutils.hh"
38 #include "resolver.hh"
39 #include "dns_random.hh"
40 #include "sstuff.hh"
41 #include "mplexer.hh"
42 #include "misc.hh"
43 #include "iputils.hh"
44 #include "logger.hh"
45 #include <yaml-cpp/yaml.h>
46
47 /* BEGIN Needed because of deeper dependencies */
48 #include "arguments.hh"
49 #include "statbag.hh"
50 StatBag S;
51
52 ArgvMap &arg()
53 {
54 static ArgvMap theArg;
55 return theArg;
56 }
57 /* END Needed because of deeper dependencies */
58
59 // Allows reading/writing ComboAddresses and DNSNames in YAML-cpp
60 namespace YAML {
61 template<>
62 struct convert<ComboAddress> {
63 static Node encode(const ComboAddress& rhs) {
64 return Node(rhs.toStringWithPort());
65 }
66 static bool decode(const Node& node, ComboAddress& rhs) {
67 if (!node.IsScalar()) {
68 return false;
69 }
70 try {
71 rhs = ComboAddress(node.as<string>(), 53);
72 return true;
73 } catch(const runtime_error &e) {
74 return false;
75 } catch (const PDNSException &e) {
76 return false;
77 }
78 }
79 };
80
81 template<>
82 struct convert<DNSName> {
83 static Node encode(const DNSName& rhs) {
84 return Node(rhs.toStringRootDot());
85 }
86 static bool decode(const Node& node, DNSName& rhs) {
87 if (!node.IsScalar()) {
88 return false;
89 }
90 try {
91 rhs = DNSName(node.as<string>());
92 return true;
93 } catch(const runtime_error &e) {
94 return false;
95 } catch (const PDNSException &e) {
96 return false;
97 }
98 }
99 };
100 } // namespace YAML
101
102 // Why a struct? This way we can add more options to a domain in the future
103 struct ixfrdistdomain_t {
104 set<ComboAddress> masters; // A set so we can do multiple master addresses in the future
105 };
106
107 // This contains the configuration for each domain
108 map<DNSName, ixfrdistdomain_t> g_domainConfigs;
109
110 // Map domains and their data
111 std::map<DNSName, ixfrinfo_t> g_soas;
112 std::mutex g_soas_mutex;
113
114 // Condition variable for TCP handling
115 std::condition_variable g_tcpHandlerCV;
116 std::queue<pair<int, ComboAddress>> g_tcpRequestFDs;
117 std::mutex g_tcpRequestFDsMutex;
118
119 namespace po = boost::program_options;
120
121 bool g_exiting = false;
122
123 NetmaskGroup g_acl;
124
125 void handleSignal(int signum) {
126 g_log<<Logger::Notice<<"Got "<<strsignal(signum)<<" signal";
127 if (g_exiting) {
128 g_log<<Logger::Notice<<", this is the second time we were asked to stop, forcefully exiting"<<endl;
129 exit(EXIT_FAILURE);
130 }
131 g_log<<Logger::Notice<<", stopping, this may take a few second due to in-progress transfers and cleanup. Send this signal again to forcefully stop"<<endl;
132 g_exiting = true;
133 }
134
135 void usage(po::options_description &desc) {
136 cerr << "Usage: ixfrdist [OPTION]..."<<endl;
137 cerr << desc << "\n";
138 }
139
140 // The compiler does not like using rfc1982LessThan in std::sort directly
141 bool sortSOA(uint32_t i, uint32_t j) {
142 return rfc1982LessThan(i, j);
143 }
144
145 void cleanUpDomain(const DNSName& domain, const uint16_t& keep, const string& workdir) {
146 string dir = workdir + "/" + domain.toString();
147 DIR *dp;
148 dp = opendir(dir.c_str());
149 if (dp == nullptr) {
150 return;
151 }
152 vector<uint32_t> zoneVersions;
153 struct dirent *d;
154 while ((d = readdir(dp)) != nullptr) {
155 if(!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) {
156 continue;
157 }
158 zoneVersions.push_back(std::stoi(d->d_name));
159 }
160 closedir(dp);
161 g_log<<Logger::Info<<"Found "<<zoneVersions.size()<<" versions of "<<domain<<", asked to keep "<<keep<<", ";
162 if (zoneVersions.size() <= keep) {
163 g_log<<Logger::Info<<"not cleaning up"<<endl;
164 return;
165 }
166 g_log<<Logger::Info<<"cleaning up the oldest "<<zoneVersions.size() - keep<<endl;
167
168 // Sort the versions
169 std::sort(zoneVersions.begin(), zoneVersions.end(), sortSOA);
170
171 // And delete all the old ones
172 {
173 // Lock to ensure no one reads this.
174 std::lock_guard<std::mutex> guard(g_soas_mutex);
175 for (auto iter = zoneVersions.cbegin(); iter != zoneVersions.cend() - keep; ++iter) {
176 string fname = dir + "/" + std::to_string(*iter);
177 g_log<<Logger::Debug<<"Removing "<<fname<<endl;
178 unlink(fname.c_str());
179 }
180 }
181 }
182
183 static shared_ptr<SOARecordContent> getSOAFromRecords(const records_t& records) {
184 for (const auto& dnsrecord : records) {
185 if (dnsrecord.d_type == QType::SOA) {
186 auto soa = getRR<SOARecordContent>(dnsrecord);
187 if (soa == nullptr) {
188 throw PDNSException("Unable to determine SOARecordContent from old records");
189 }
190 return soa;
191 }
192 }
193 throw PDNSException("No SOA in supplied records");
194 }
195
196 static void makeIXFRDiff(const records_t& from, const records_t& to, ixfrdiff_t& diff, const shared_ptr<SOARecordContent>& fromSOA = nullptr, const shared_ptr<SOARecordContent>& toSOA = nullptr) {
197 set_difference(from.cbegin(), from.cend(), to.cbegin(), to.cend(), back_inserter(diff.removals), from.value_comp());
198 set_difference(to.cbegin(), to.cend(), from.cbegin(), from.cend(), back_inserter(diff.additions), from.value_comp());
199 diff.oldSOA = fromSOA;
200 if (fromSOA == nullptr) {
201 getSOAFromRecords(from);
202 }
203 diff.newSOA = toSOA;
204 if (toSOA == nullptr) {
205 getSOAFromRecords(to);
206 }
207 }
208
209 void updateThread(const string& workdir, const uint16_t& keep, const uint16_t& axfrTimeout) {
210 std::map<DNSName, time_t> lastCheck;
211
212 // Initialize the serials we have
213 for (const auto &domainConfig : g_domainConfigs) {
214 DNSName domain = domainConfig.first;
215 lastCheck[domain] = 0;
216 string dir = workdir + "/" + domain.toString();
217 try {
218 g_log<<Logger::Info<<"Trying to initially load domain "<<domain<<" from disk"<<endl;
219 auto serial = getSerialsFromDir(dir);
220 shared_ptr<SOARecordContent> soa;
221 {
222 string fname = workdir + "/" + domain.toString() + "/" + std::to_string(serial);
223 loadSOAFromDisk(domain, fname, soa);
224 records_t records;
225 if (soa != nullptr) {
226 loadZoneFromDisk(records, fname, domain);
227 }
228 std::lock_guard<std::mutex> guard(g_soas_mutex);
229 g_soas[domain].latestAXFR = records;
230 g_soas[domain].soa = soa;
231 }
232 if (soa != nullptr) {
233 g_log<<Logger::Notice<<"Loaded zone "<<domain<<" with serial "<<soa->d_st.serial<<endl;
234 // Initial cleanup
235 cleanUpDomain(domain, keep, workdir);
236 }
237 } catch (runtime_error &e) {
238 // Most likely, the directory does not exist.
239 g_log<<Logger::Info<<e.what()<<", attempting to create"<<endl;
240 // Attempt to create it, if _that_ fails, there is no hope
241 if (mkdir(dir.c_str(), 0777) == -1 && errno != EEXIST) {
242 g_log<<Logger::Error<<"Could not create '"<<dir<<"': "<<strerror(errno)<<endl;
243 exit(EXIT_FAILURE);
244 }
245 }
246 }
247
248 g_log<<Logger::Notice<<"Update Thread started"<<endl;
249
250 while (true) {
251 if (g_exiting) {
252 g_log<<Logger::Notice<<"UpdateThread stopped"<<endl;
253 break;
254 }
255 time_t now = time(nullptr);
256 for (const auto &domainConfig : g_domainConfigs) {
257 DNSName domain = domainConfig.first;
258 shared_ptr<SOARecordContent> current_soa;
259 {
260 std::lock_guard<std::mutex> guard(g_soas_mutex);
261 if (g_soas.find(domain) != g_soas.end()) {
262 current_soa = g_soas[domain].soa;
263 }
264 }
265 if ((current_soa != nullptr && now - lastCheck[domain] < current_soa->d_st.refresh) || // Only check if we have waited `refresh` seconds
266 (current_soa == nullptr && now - lastCheck[domain] < 30)) { // Or if we could not get an update at all still, every 30 seconds
267 continue;
268 }
269
270 // TODO Keep track of 'down' masters
271 set<ComboAddress>::const_iterator it(g_domainConfigs[domain].masters.begin());
272 std::advance(it, random() % g_domainConfigs[domain].masters.size());
273 ComboAddress master = *it;
274
275 string dir = workdir + "/" + domain.toString();
276 g_log<<Logger::Info<<"Attempting to retrieve SOA Serial update for '"<<domain<<"' from '"<<master.toStringWithPort()<<"'"<<endl;
277 shared_ptr<SOARecordContent> sr;
278 try {
279 lastCheck[domain] = now;
280 auto newSerial = getSerialFromMaster(master, domain, sr); // TODO TSIG
281 if(current_soa != nullptr) {
282 g_log<<Logger::Info<<"Got SOA Serial for "<<domain<<" from "<<master.toStringWithPort()<<": "<< newSerial<<", had Serial: "<<current_soa->d_st.serial;
283 if (newSerial == current_soa->d_st.serial) {
284 g_log<<Logger::Info<<", not updating."<<endl;
285 continue;
286 }
287 g_log<<Logger::Info<<", will update."<<endl;
288 }
289 } catch (runtime_error &e) {
290 g_log<<Logger::Warning<<"Unable to get SOA serial update for '"<<domain<<"' from master "<<master.toStringWithPort()<<": "<<e.what()<<endl;
291 continue;
292 }
293 // Now get the full zone!
294 g_log<<Logger::Info<<"Attempting to receive full zonedata for '"<<domain<<"'"<<endl;
295 ComboAddress local = master.isIPv4() ? ComboAddress("0.0.0.0") : ComboAddress("::");
296 TSIGTriplet tt;
297
298 // The *new* SOA
299 shared_ptr<SOARecordContent> soa;
300 try {
301 AXFRRetriever axfr(master, domain, tt, &local);
302 unsigned int nrecords=0;
303 Resolver::res_t nop;
304 vector<DNSRecord> chunk;
305 records_t records;
306 time_t t_start = time(nullptr);
307 time_t axfr_now = time(nullptr);
308 while(axfr.getChunk(nop, &chunk, (axfr_now - t_start + axfrTimeout))) {
309 for(auto& dr : chunk) {
310 if(dr.d_type == QType::TSIG)
311 continue;
312 dr.d_name.makeUsRelative(domain);
313 records.insert(dr);
314 nrecords++;
315 if (dr.d_type == QType::SOA) {
316 soa = getRR<SOARecordContent>(dr);
317 }
318 }
319 axfr_now = time(nullptr);
320 if (axfr_now - t_start > axfrTimeout) {
321 throw PDNSException("Total AXFR time exceeded!");
322 }
323 }
324 if (soa == nullptr) {
325 g_log<<Logger::Warning<<"No SOA was found in the AXFR of "<<domain<<endl;
326 continue;
327 }
328 g_log<<Logger::Notice<<"Retrieved all zone data for "<<domain<<". Received "<<nrecords<<" records."<<endl;
329 writeZoneToDisk(records, domain, dir);
330 g_log<<Logger::Notice<<"Wrote zonedata for "<<domain<<" with serial "<<soa->d_st.serial<<" to "<<dir<<endl;
331 {
332 std::lock_guard<std::mutex> guard(g_soas_mutex);
333 ixfrdiff_t diff;
334 if (!g_soas[domain].latestAXFR.empty()) {
335 makeIXFRDiff(g_soas[domain].latestAXFR, records, diff, g_soas[domain].soa, soa);
336 g_soas[domain].ixfrDiffs.push_back(diff);
337 }
338 // Clean up the diffs
339 while (g_soas[domain].ixfrDiffs.size() > keep) {
340 g_soas[domain].ixfrDiffs.erase(g_soas[domain].ixfrDiffs.begin());
341 }
342 g_soas[domain].latestAXFR = records;
343 g_soas[domain].soa = soa;
344 }
345 } catch (PDNSException &e) {
346 g_log<<Logger::Warning<<"Could not retrieve AXFR for '"<<domain<<"': "<<e.reason<<endl;
347 } catch (runtime_error &e) {
348 g_log<<Logger::Warning<<"Could not save zone '"<<domain<<"' to disk: "<<e.what()<<endl;
349 }
350 // Now clean up the directory
351 cleanUpDomain(domain, keep, workdir);
352 } /* for (const auto &domain : domains) */
353 sleep(1);
354 } /* while (true) */
355 } /* updateThread */
356
357 bool checkQuery(const MOADNSParser& mdp, const ComboAddress& saddr, const bool udp = true, const string& logPrefix="") {
358 vector<string> info_msg;
359
360 g_log<<Logger::Debug<<logPrefix<<"Had "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).getName()<<" query from "<<saddr.toStringWithPort()<<endl;
361
362 if (udp && mdp.d_qtype != QType::SOA && mdp.d_qtype != QType::IXFR) {
363 info_msg.push_back("QType is unsupported (" + QType(mdp.d_qtype).getName() + " is not in {SOA,IXFR}");
364 }
365
366 if (!udp && mdp.d_qtype != QType::SOA && mdp.d_qtype != QType::IXFR && mdp.d_qtype != QType::AXFR) {
367 info_msg.push_back("QType is unsupported (" + QType(mdp.d_qtype).getName() + " is not in {SOA,IXFR,AXFR}");
368 }
369
370 {
371 std::lock_guard<std::mutex> guard(g_soas_mutex);
372 if (g_domainConfigs.find(mdp.d_qname) == g_domainConfigs.end()) {
373 info_msg.push_back("Domain name '" + mdp.d_qname.toLogString() + "' is not configured for distribution");
374 }
375
376 if (g_soas.find(mdp.d_qname) == g_soas.end()) {
377 info_msg.push_back("Domain has not been transferred yet");
378 }
379 }
380
381 if (!info_msg.empty()) {
382 g_log<<Logger::Warning<<logPrefix<<"Ignoring "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).getName()<<" query from "<<saddr.toStringWithPort();
383 g_log<<Logger::Warning<<": ";
384 bool first = true;
385 for (const auto& s : info_msg) {
386 if (!first) {
387 g_log<<Logger::Warning<<", ";
388 first = false;
389 }
390 g_log<<Logger::Warning<<s;
391 }
392 g_log<<Logger::Warning<<endl;
393 return false;
394 }
395
396 return true;
397 }
398
399 /*
400 * Returns a vector<uint8_t> that represents the full response to a SOA
401 * query. QNAME is read from mdp.
402 */
403 bool makeSOAPacket(const MOADNSParser& mdp, vector<uint8_t>& packet) {
404 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
405 pw.getHeader()->id = mdp.d_header.id;
406 pw.getHeader()->rd = mdp.d_header.rd;
407 pw.getHeader()->qr = 1;
408
409 pw.startRecord(mdp.d_qname, QType::SOA);
410 {
411 std::lock_guard<std::mutex> guard(g_soas_mutex);
412 g_soas[mdp.d_qname].soa->toPacket(pw);
413 }
414 pw.commit();
415
416 return true;
417 }
418
419 vector<uint8_t> getSOAPacket(const MOADNSParser& mdp, const shared_ptr<SOARecordContent>& soa) {
420 vector<uint8_t> packet;
421 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
422 pw.getHeader()->id = mdp.d_header.id;
423 pw.getHeader()->rd = mdp.d_header.rd;
424 pw.getHeader()->qr = 1;
425
426 // Add the first SOA
427 pw.startRecord(mdp.d_qname, QType::SOA);
428 soa->toPacket(pw);
429 pw.commit();
430 return packet;
431 }
432
433 bool makeAXFRPackets(const MOADNSParser& mdp, vector<vector<uint8_t>>& packets) {
434 shared_ptr<SOARecordContent> soa;
435 records_t records;
436 {
437 // Make copies of what we have
438 std::lock_guard<std::mutex> guard(g_soas_mutex);
439 soa = g_soas[mdp.d_qname].soa;
440 records = g_soas[mdp.d_qname].latestAXFR;
441 }
442
443 // Initial SOA
444 packets.push_back(getSOAPacket(mdp, soa));
445
446 for (auto const &record : records) {
447 if (record.d_type == QType::SOA) {
448 continue;
449 }
450 vector<uint8_t> packet;
451 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
452 pw.getHeader()->id = mdp.d_header.id;
453 pw.getHeader()->rd = mdp.d_header.rd;
454 pw.getHeader()->qr = 1;
455 pw.startRecord(record.d_name + mdp.d_qname, record.d_type);
456 record.d_content->toPacket(pw);
457 pw.commit();
458 packets.push_back(packet);
459 }
460
461 // Final SOA
462 packets.push_back(getSOAPacket(mdp, soa));
463
464 return true;
465 }
466
467 void makeXFRPacketsFromDNSRecords(const MOADNSParser& mdp, const vector<DNSRecord>& records, vector<vector<uint8_t>>& packets) {
468 for(const auto& r : records) {
469 if (r.d_type == QType::SOA) {
470 continue;
471 }
472 vector<uint8_t> packet;
473 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
474 pw.getHeader()->id = mdp.d_header.id;
475 pw.getHeader()->rd = mdp.d_header.rd;
476 pw.getHeader()->qr = 1;
477 pw.startRecord(r.d_name + mdp.d_qname, r.d_type);
478 r.d_content->toPacket(pw);
479 pw.commit();
480 packets.push_back(packet);
481 }
482 }
483
484 /* Produces an IXFR if one can be made according to the rules in RFC 1995 and
485 * creates a SOA or AXFR packet when required by the RFC.
486 */
487 bool makeIXFRPackets(const MOADNSParser& mdp, const shared_ptr<SOARecordContent>& clientSOA, vector<vector<uint8_t>>& packets) {
488 // Get the new SOA only once, so it will not change under our noses from the
489 // updateThread.
490 vector<ixfrdiff_t> toSend;
491 uint32_t ourLatestSerial;
492 {
493 std::lock_guard<std::mutex> guard(g_soas_mutex);
494 ourLatestSerial = g_soas[mdp.d_qname].soa->d_st.serial;
495 }
496
497 if (rfc1982LessThan(ourLatestSerial, clientSOA->d_st.serial) || ourLatestSerial == clientSOA->d_st.serial){
498 /* RFC 1995 Section 2
499 * If an IXFR query with the same or newer version number than that of
500 * the server is received, it is replied to with a single SOA record of
501 * the server's current version, just as in AXFR.
502 */
503 vector<uint8_t> packet;
504 bool ret = makeSOAPacket(mdp, packet);
505 if (ret) {
506 packets.push_back(packet);
507 }
508 return ret;
509 }
510
511 {
512 // as we use push_back in the updater, we know the vector is sorted as oldest first
513 bool shouldAdd = false;
514 // Get all relevant IXFR differences
515 std::lock_guard<std::mutex> guard(g_soas_mutex);
516 for (const auto& diff : g_soas[mdp.d_qname].ixfrDiffs) {
517 if (shouldAdd) {
518 toSend.push_back(diff);
519 continue;
520 }
521 if (diff.oldSOA->d_st.serial == clientSOA->d_st.serial) {
522 toSend.push_back(diff);
523 // Add all consecutive diffs
524 shouldAdd = true;
525 }
526 }
527 }
528
529 if (toSend.empty()) {
530 g_log<<Logger::Warning<<"No IXFR available from serial "<<clientSOA->d_st.serial<<" for zone "<<mdp.d_qname<<", attempting to send AXFR"<<endl;
531 return makeAXFRPackets(mdp, packets);
532 }
533
534 for (const auto& diff : toSend) {
535 /* An IXFR packet's ANSWER section looks as follows:
536 * SOA new_serial
537 * SOA old_serial
538 * ... removed records ...
539 * SOA new_serial
540 * ... added records ...
541 * SOA new_serial
542 */
543 packets.push_back(getSOAPacket(mdp, diff.newSOA));
544 packets.push_back(getSOAPacket(mdp, diff.oldSOA));
545 makeXFRPacketsFromDNSRecords(mdp, diff.removals, packets);
546 packets.push_back(getSOAPacket(mdp, diff.newSOA));
547 makeXFRPacketsFromDNSRecords(mdp, diff.additions, packets);
548 packets.push_back(getSOAPacket(mdp, diff.newSOA));
549 }
550
551 return true;
552 }
553
554 bool allowedByACL(const ComboAddress& addr) {
555 return g_acl.match(addr);
556 }
557
558 void handleUDPRequest(int fd, boost::any&) {
559 // TODO make the buffer-size configurable
560 char buf[4096];
561 ComboAddress saddr;
562 socklen_t fromlen = sizeof(saddr);
563 int res = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr*) &saddr, &fromlen);
564
565 if (res == 0) {
566 g_log<<Logger::Warning<<"Got an empty message from "<<saddr.toStringWithPort()<<endl;
567 return;
568 }
569
570 if(res < 0) {
571 auto savedErrno = errno;
572 g_log<<Logger::Warning<<"Could not read message from "<<saddr.toStringWithPort()<<": "<<strerror(savedErrno)<<endl;
573 return;
574 }
575
576 if (saddr == ComboAddress("0.0.0.0", 0)) {
577 g_log<<Logger::Warning<<"Could not determine source of message"<<endl;
578 return;
579 }
580
581 if (!allowedByACL(saddr)) {
582 g_log<<Logger::Warning<<"UDP query from "<<saddr.toString()<<" is not allowed, dropping"<<endl;
583 return;
584 }
585
586 MOADNSParser mdp(true, string(buf, res));
587 if (!checkQuery(mdp, saddr)) {
588 return;
589 }
590
591 /* RFC 1995 Section 2
592 * Transport of a query may be by either UDP or TCP. If an IXFR query
593 * is via UDP, the IXFR server may attempt to reply using UDP if the
594 * entire response can be contained in a single DNS packet. If the UDP
595 * reply does not fit, the query is responded to with a single SOA
596 * record of the server's current version to inform the client that a
597 * TCP query should be initiated.
598 *
599 * Let's not complicate this with IXFR over UDP (and looking if we need to truncate etc).
600 * Just send the current SOA and let the client try over TCP
601 */
602 vector<uint8_t> packet;
603 makeSOAPacket(mdp, packet);
604 if(sendto(fd, &packet[0], packet.size(), 0, (struct sockaddr*) &saddr, fromlen) < 0) {
605 auto savedErrno = errno;
606 g_log<<Logger::Warning<<"Could not send reply for "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).getName()<<" to "<<saddr.toStringWithPort()<<": "<<strerror(savedErrno)<<endl;
607 }
608 return;
609 }
610
611 void handleTCPRequest(int fd, boost::any&) {
612 ComboAddress saddr;
613 int cfd = 0;
614
615 try {
616 cfd = SAccept(fd, saddr);
617 setBlocking(cfd);
618 } catch(runtime_error &e) {
619 g_log<<Logger::Error<<e.what()<<endl;
620 return;
621 }
622
623 if (saddr == ComboAddress("0.0.0.0", 0)) {
624 g_log<<Logger::Warning<<"Could not determine source of message"<<endl;
625 close(cfd);
626 return;
627 }
628
629 if (!allowedByACL(saddr)) {
630 g_log<<Logger::Warning<<"TCP query from "<<saddr.toString()<<" is not allowed, dropping"<<endl;
631 close(cfd);
632 return;
633 }
634
635 {
636 std::lock_guard<std::mutex> lg(g_tcpRequestFDsMutex);
637 g_tcpRequestFDs.push({cfd, saddr});
638 }
639 g_tcpHandlerCV.notify_one();
640 }
641
642 /* Thread to handle TCP traffic
643 */
644 void tcpWorker(int tid) {
645 string prefix = "TCP Worker " + std::to_string(tid) + ": ";
646
647 while(true) {
648 g_log<<Logger::Debug<<prefix<<"ready for a new request!"<<endl;
649 std::unique_lock<std::mutex> lk(g_tcpRequestFDsMutex);
650 g_tcpHandlerCV.wait(lk, []{return g_tcpRequestFDs.size() || g_exiting ;});
651 if (g_exiting) {
652 g_log<<Logger::Debug<<prefix<<"Stopping thread"<<endl;
653 break;
654 }
655 g_log<<Logger::Debug<<prefix<<"Going to handle a query"<<endl;
656 auto request = g_tcpRequestFDs.front();
657 g_tcpRequestFDs.pop();
658 lk.unlock();
659
660 int cfd = request.first;
661 ComboAddress saddr = request.second;
662
663 char buf[4096];
664 ssize_t res;
665 try {
666 uint16_t toRead;
667 readn2(cfd, &toRead, sizeof(toRead));
668 toRead = std::min(ntohs(toRead), static_cast<uint16_t>(sizeof(buf)));
669 res = readn2WithTimeout(cfd, &buf, toRead, 2);
670 g_log<<Logger::Debug<<prefix<<"Had message of "<<std::to_string(toRead)<<" bytes from "<<saddr.toStringWithPort()<<endl;
671 } catch (runtime_error &e) {
672 g_log<<Logger::Warning<<prefix<<"Could not read message from "<<saddr.toStringWithPort()<<": "<<e.what()<<endl;
673 close(cfd);
674 continue;
675 }
676
677 try {
678 MOADNSParser mdp(true, string(buf, res));
679
680 if (!checkQuery(mdp, saddr, false, prefix)) {
681 close(cfd);
682 continue;
683 }
684
685 vector<vector<uint8_t>> packets;
686 if (mdp.d_qtype == QType::SOA) {
687 vector<uint8_t> packet;
688 bool ret = makeSOAPacket(mdp, packet);
689 if (!ret) {
690 close(cfd);
691 continue;
692 }
693 packets.push_back(packet);
694 }
695
696 if (mdp.d_qtype == QType::AXFR) {
697 if (!makeAXFRPackets(mdp, packets)) {
698 close(cfd);
699 continue;
700 }
701 }
702
703 if (mdp.d_qtype == QType::IXFR) {
704 /* RFC 1995 section 3:
705 * The IXFR query packet format is the same as that of a normal DNS
706 * query, but with the query type being IXFR and the authority section
707 * containing the SOA record of client's version of the zone.
708 */
709 shared_ptr<SOARecordContent> clientSOA;
710 for (auto &answer : mdp.d_answers) {
711 // from dnsparser.hh:
712 // typedef vector<pair<DNSRecord, uint16_t > > answers_t;
713 if (answer.first.d_type == QType::SOA && answer.first.d_place == DNSResourceRecord::AUTHORITY) {
714 clientSOA = getRR<SOARecordContent>(answer.first);
715 if (clientSOA != nullptr) {
716 break;
717 }
718 }
719 } /* for (auto const &answer : mdp.d_answers) */
720
721 if (clientSOA == nullptr) {
722 g_log<<Logger::Warning<<prefix<<"IXFR request packet did not contain a SOA record in the AUTHORITY section"<<endl;
723 close(cfd);
724 continue;
725 }
726
727 if (!makeIXFRPackets(mdp, clientSOA, packets)) {
728 close(cfd);
729 continue;
730 }
731 } /* if (mdp.d_qtype == QType::IXFR) */
732
733 g_log<<Logger::Debug<<prefix<<"Sending "<<packets.size()<<" packets to "<<saddr.toStringWithPort()<<endl;
734 for (const auto& packet : packets) {
735 char sendBuf[2];
736 sendBuf[0]=packet.size()/256;
737 sendBuf[1]=packet.size()%256;
738
739 ssize_t send = writen2(cfd, sendBuf, 2);
740 send += writen2(cfd, &packet[0], packet.size());
741 }
742 shutdown(cfd, 2);
743 } catch (MOADNSException &e) {
744 g_log<<Logger::Warning<<prefix<<"Could not parse DNS packet from "<<saddr.toStringWithPort()<<": "<<e.what()<<endl;
745 } catch (runtime_error &e) {
746 g_log<<Logger::Warning<<prefix<<"Could not write reply to "<<saddr.toStringWithPort()<<": "<<e.what()<<endl;
747 }
748 // bye!
749 close(cfd);
750
751 if (g_exiting) {
752 break;
753 }
754 }
755 }
756
757 /* Parses the configuration file in configpath into config, adding defaults for
758 * missing parameters (if applicable), returning true if the config file was
759 * good, false otherwise. Will log all issues with the config
760 */
761 bool parseAndCheckConfig(const string& configpath, YAML::Node& config) {
762 g_log<<Logger::Info<<"Loading configuration file from "<<configpath<<endl;
763 try {
764 config = YAML::LoadFile(configpath);
765 } catch (const runtime_error &e) {
766 g_log<<Logger::Error<<"Unable to load configuration file '"<<configpath<<"': "<<e.what()<<endl;
767 return false;
768 }
769
770 bool retval = true;
771
772 if (config["keep"]) {
773 try {
774 config["keep"].as<uint16_t>();
775 } catch (const runtime_error &e) {
776 g_log<<Logger::Error<<"Unable to read 'keep' value: "<<e.what()<<endl;
777 retval = false;
778 }
779 } else {
780 config["keep"] = 20;
781 }
782
783 if (config["axfr-timeout"]) {
784 try {
785 config["axfr-timeout"].as<uint16_t>();
786 } catch (const runtime_error &e) {
787 g_log<<Logger::Error<<"Unable to read 'axfr-timeout' value: "<<e.what()<<endl;
788 }
789 } else {
790 config["axfr-timeout"] = 10;
791 }
792
793 if (config["tcp-in-threads"]) {
794 try {
795 config["tcp-in-threads"].as<uint16_t>();
796 } catch (const runtime_error &e) {
797 g_log<<Logger::Error<<"Unable to read 'tcp-in-threads' value: "<<e.what()<<endl;
798 }
799 } else {
800 config["tcp-in-threads"] = 10;
801 }
802
803 if (config["listen"]) {
804 try {
805 config["listen"].as<vector<ComboAddress>>();
806 } catch (const runtime_error &e) {
807 g_log<<Logger::Error<<"Unable to read 'listen' value: "<<e.what()<<endl;
808 retval = false;
809 }
810 } else {
811 config["listen"].push_back("127.0.0.1:53");
812 config["listen"].push_back("[::1]:53");
813 }
814
815 if (config["acl"]) {
816 try {
817 config["acl"].as<vector<string>>();
818 } catch (const runtime_error &e) {
819 g_log<<Logger::Error<<"Unable to read 'acl' value: "<<e.what()<<endl;
820 retval = false;
821 }
822 } else {
823 config["acl"].push_back("127.0.0.0/8");
824 config["acl"].push_back("::1/128");
825 }
826
827 if (config["work-dir"]) {
828 try {
829 config["work-dir"].as<string>();
830 } catch(const runtime_error &e) {
831 g_log<<Logger::Error<<"Unable to read 'work-dir' value: "<<e.what()<<endl;
832 retval = false;
833 }
834 } else {
835 char tmp[512];
836 config["work-dir"] = getcwd(tmp, sizeof(tmp)) ? string(tmp) : "";;
837 }
838
839 if (config["uid"]) {
840 try {
841 config["uid"].as<string>();
842 } catch(const runtime_error &e) {
843 g_log<<Logger::Error<<"Unable to read 'uid' value: "<<e.what()<<endl;
844 retval = false;
845 }
846 }
847
848 if (config["gid"]) {
849 try {
850 config["gid"].as<string>();
851 } catch(const runtime_error &e) {
852 g_log<<Logger::Error<<"Unable to read 'gid' value: "<<e.what()<<endl;
853 retval = false;
854 }
855 }
856
857 if (config["domains"]) {
858 if (config["domains"].size() == 0) {
859 g_log<<Logger::Error<<"No domains configured"<<endl;
860 retval = false;
861 }
862 for (auto const &domain : config["domains"]) {
863 try {
864 if (!domain["domain"]) {
865 g_log<<Logger::Error<<"An entry in 'domains' is missing a 'domain' key!"<<endl;
866 retval = false;
867 continue;
868 }
869 domain["domain"].as<DNSName>();
870 } catch (const runtime_error &e) {
871 g_log<<Logger::Error<<"Unable to read domain '"<<domain["domain"].as<string>()<<"': "<<e.what()<<endl;
872 }
873 try {
874 if (!domain["master"]) {
875 g_log<<Logger::Error<<"Domain '"<<domain["domain"].as<string>()<<"' has no master configured!"<<endl;
876 retval = false;
877 continue;
878 }
879 domain["master"].as<ComboAddress>();
880 } catch (const runtime_error &e) {
881 g_log<<Logger::Error<<"Unable to read domain '"<<domain["domain"].as<string>()<<"' master address: "<<e.what()<<endl;
882 retval = false;
883 }
884 }
885 } else {
886 g_log<<Logger::Error<<"No domains configured"<<endl;
887 retval = false;
888 }
889
890 return retval;
891 }
892
893 int main(int argc, char** argv) {
894 g_log.setLoglevel(Logger::Notice);
895 g_log.toConsole(Logger::Notice);
896 g_log.setPrefixed(true);
897 g_log.disableSyslog(true);
898 g_log.setTimestamps(false);
899 po::variables_map g_vm;
900 try {
901 po::options_description desc("IXFR distribution tool");
902 desc.add_options()
903 ("help", "produce help message")
904 ("version", "Display the version of ixfrdist")
905 ("verbose", "Be verbose")
906 ("debug", "Be even more verbose")
907 ("config", po::value<string>()->default_value(SYSCONFDIR + string("/ixfrdist.yml")), "Configuration file to use")
908 ;
909
910 po::store(po::command_line_parser(argc, argv).options(desc).run(), g_vm);
911 po::notify(g_vm);
912
913 if (g_vm.count("help") > 0) {
914 usage(desc);
915 return EXIT_SUCCESS;
916 }
917
918 if (g_vm.count("version") > 0) {
919 cout<<"ixfrdist "<<VERSION<<endl;
920 return EXIT_SUCCESS;
921 }
922 } catch (po::error &e) {
923 g_log<<Logger::Error<<e.what()<<". See `ixfrdist --help` for valid options"<<endl;
924 return(EXIT_FAILURE);
925 }
926
927 bool had_error = false;
928
929 if (g_vm.count("verbose")) {
930 g_log.setLoglevel(Logger::Info);
931 g_log.toConsole(Logger::Info);
932 }
933
934 if (g_vm.count("debug") > 0) {
935 g_log.setLoglevel(Logger::Debug);
936 g_log.toConsole(Logger::Debug);
937 }
938
939 g_log<<Logger::Notice<<"IXFR distributor version "<<VERSION<<" starting up!"<<endl;
940
941 YAML::Node config;
942 if (!parseAndCheckConfig(g_vm["config"].as<string>(), config)) {
943 // parseAndCheckConfig already logged whatever was wrong
944 return EXIT_FAILURE;
945 }
946
947 /* From hereon out, we known that all the values in config are valid. */
948
949 for (auto const &domain : config["domains"]) {
950 set<ComboAddress> s;
951 s.insert(domain["master"].as<ComboAddress>());
952 g_domainConfigs[domain["domain"].as<DNSName>()].masters = s;
953 }
954
955 for (const auto &addr : config["acl"].as<vector<string>>()) {
956 try {
957 g_acl.addMask(addr);
958 } catch (const NetmaskException &e) {
959 g_log<<Logger::Error<<e.reason<<endl;
960 had_error = true;
961 }
962 }
963 g_log<<Logger::Notice<<"ACL set to "<<g_acl.toString()<<"."<<endl;
964
965 FDMultiplexer* fdm = FDMultiplexer::getMultiplexerSilent();
966 if (fdm == nullptr) {
967 g_log<<Logger::Error<<"Could not enable a multiplexer for the listen sockets!"<<endl;
968 return EXIT_FAILURE;
969 }
970
971 set<int> allSockets;
972 for (const auto& addr : config["listen"].as<vector<ComboAddress>>()) {
973 for (const auto& stype : {SOCK_DGRAM, SOCK_STREAM}) {
974 try {
975 int s = SSocket(addr.sin4.sin_family, stype, 0);
976 setNonBlocking(s);
977 setReuseAddr(s);
978 SBind(s, addr);
979 if (stype == SOCK_STREAM) {
980 SListen(s, 30); // TODO make this configurable
981 }
982 fdm->addReadFD(s, stype == SOCK_DGRAM ? handleUDPRequest : handleTCPRequest);
983 allSockets.insert(s);
984 } catch(runtime_error &e) {
985 g_log<<Logger::Error<<e.what()<<endl;
986 had_error = true;
987 continue;
988 }
989 }
990 }
991
992 int newgid = 0;
993
994 if (config["gid"]) {
995 string gid = config["gid"].as<string>();
996 if (!(newgid = atoi(gid.c_str()))) {
997 struct group *gr = getgrnam(gid.c_str());
998 if (gr == nullptr) {
999 g_log<<Logger::Error<<"Can not determine group-id for gid "<<gid<<endl;
1000 had_error = true;
1001 } else {
1002 newgid = gr->gr_gid;
1003 }
1004 }
1005 g_log<<Logger::Notice<<"Dropping effective group-id to "<<newgid<<endl;
1006 if (setgid(newgid) < 0) {
1007 g_log<<Logger::Error<<"Could not set group id to "<<newgid<<": "<<stringerror()<<endl;
1008 had_error = true;
1009 }
1010 }
1011
1012 int newuid = 0;
1013
1014 if (config["uid"]) {
1015 string uid = config["uid"].as<string>();
1016 if (!(newuid = atoi(uid.c_str()))) {
1017 struct passwd *pw = getpwnam(uid.c_str());
1018 if (pw == nullptr) {
1019 g_log<<Logger::Error<<"Can not determine user-id for uid "<<uid<<endl;
1020 had_error = true;
1021 } else {
1022 newuid = pw->pw_uid;
1023 }
1024 }
1025
1026 struct passwd *pw = getpwuid(newuid);
1027 if (pw == nullptr) {
1028 if (setgroups(0, nullptr) < 0) {
1029 g_log<<Logger::Error<<"Unable to drop supplementary gids: "<<stringerror()<<endl;
1030 had_error = true;
1031 }
1032 } else {
1033 if (initgroups(pw->pw_name, newgid) < 0) {
1034 g_log<<Logger::Error<<"Unable to set supplementary groups: "<<stringerror()<<endl;
1035 had_error = true;
1036 }
1037 }
1038
1039 g_log<<Logger::Notice<<"Dropping effective user-id to "<<newuid<<endl;
1040 if (setuid(newuid) < 0) {
1041 g_log<<Logger::Error<<"Could not set user id to "<<newuid<<": "<<stringerror()<<endl;
1042 had_error = true;
1043 }
1044 }
1045
1046 if (had_error) {
1047 // We have already sent the errors to stderr, just die
1048 return EXIT_FAILURE;
1049 }
1050
1051 // It all starts here
1052 signal(SIGTERM, handleSignal);
1053 signal(SIGINT, handleSignal);
1054 signal(SIGSTOP, handleSignal);
1055 signal(SIGPIPE, SIG_IGN);
1056
1057 // Init the things we need
1058 reportAllTypes();
1059
1060 dns_random_init();
1061
1062 std::thread ut(updateThread,
1063 config["work-dir"].as<string>(),
1064 config["keep"].as<uint16_t>(),
1065 config["axfr-timeout"].as<uint16_t>());
1066
1067 vector<std::thread> tcpHandlers;
1068 tcpHandlers.reserve(config["tcp-in-threads"].as<uint16_t>());
1069 for (size_t i = 0; i < tcpHandlers.capacity(); ++i) {
1070 tcpHandlers.push_back(std::thread(tcpWorker, i));
1071 }
1072
1073 struct timeval now;
1074 for(;;) {
1075 gettimeofday(&now, 0);
1076 fdm->run(&now);
1077 if (g_exiting) {
1078 g_log<<Logger::Debug<<"Closing listening sockets"<<endl;
1079 for (const int& fd : allSockets) {
1080 try {
1081 closesocket(fd);
1082 } catch(PDNSException &e) {
1083 g_log<<Logger::Error<<e.reason<<endl;
1084 }
1085 }
1086 break;
1087 }
1088 }
1089 g_log<<Logger::Debug<<"Waiting for al threads to stop"<<endl;
1090 g_tcpHandlerCV.notify_all();
1091 ut.join();
1092 for (auto &t : tcpHandlers) {
1093 t.join();
1094 }
1095 g_log<<Logger::Notice<<"IXFR distributor stopped"<<endl;
1096 return EXIT_SUCCESS;
1097 }