]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/ixfrdist.cc
rec: mention rust compiler in compiling docs
[thirdparty/pdns.git] / pdns / ixfrdist.cc
CommitLineData
a2670f5e
PL
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 */
5b546566 22#include "dns.hh"
06699363 23#include "dnsparser.hh"
146ada65 24#include <stdexcept>
a2670f5e
PL
25#ifdef HAVE_CONFIG_H
26#include "config.h"
27#endif
a2670f5e 28#include <boost/program_options.hpp>
352df81e 29#include <arpa/inet.h>
6f8cb809
PL
30#include <sys/types.h>
31#include <grp.h>
32#include <pwd.h>
38e6a9ee 33#include <sys/stat.h>
772cdea3 34#include <mutex>
33494729 35#include <thread>
519f5484 36#include "threadname.hh"
37499e32 37#include <dirent.h>
a2969595
PL
38#include <queue>
39#include <condition_variable>
2a0826d3
CHB
40#include <thread>
41#include <chrono>
a2670f5e 42#include "ixfr.hh"
4db8fd44 43#include "ixfrutils.hh"
0dd42dbb 44#include "axfr-retriever.hh"
38e6a9ee 45#include "dns_random.hh"
aff1f1fd
PL
46#include "sstuff.hh"
47#include "mplexer.hh"
0eb03ead 48#include "misc.hh"
16f495e0 49#include "iputils.hh"
ffb1b049 50#include "lock.hh"
146ada65
CHB
51#include "communicator.hh"
52#include "query-local-address.hh"
1361013e 53#include "logger.hh"
42d4d25b 54#include "ixfrdist-stats.hh"
d5c9e1cb 55#include "ixfrdist-web.hh"
dc125378
OM
56#pragma GCC diagnostic push
57#pragma GCC diagnostic ignored "-Wshadow"
f2d45260 58#include <yaml-cpp/yaml.h>
dc125378 59#pragma GCC diagnostic pop
146ada65
CHB
60#include "auth-packetcache.hh"
61#include "auth-querycache.hh"
62#include "auth-zonecache.hh"
772cdea3 63
aeaf4c15
PL
64/* BEGIN Needed because of deeper dependencies */
65#include "arguments.hh"
66#include "statbag.hh"
a2670f5e 67StatBag S;
ec70d60d 68// NOLINTNEXTLINE(readability-identifier-length)
146ada65 69AuthPacketCache PC;
ec70d60d 70// NOLINTNEXTLINE(readability-identifier-length)
146ada65
CHB
71AuthQueryCache QC;
72AuthZoneCache g_zoneCache;
a2670f5e
PL
73
74ArgvMap &arg()
75{
76 static ArgvMap theArg;
77 return theArg;
78}
aeaf4c15
PL
79/* END Needed because of deeper dependencies */
80
f2d45260
PL
81// Allows reading/writing ComboAddresses and DNSNames in YAML-cpp
82namespace YAML {
83template<>
84struct convert<ComboAddress> {
85 static Node encode(const ComboAddress& rhs) {
86 return Node(rhs.toStringWithPort());
87 }
88 static bool decode(const Node& node, ComboAddress& rhs) {
89 if (!node.IsScalar()) {
90 return false;
91 }
92 try {
4f2b4c54 93 rhs = ComboAddress(node.as<string>(), 53);
f2d45260
PL
94 return true;
95 } catch(const runtime_error &e) {
96 return false;
97 } catch (const PDNSException &e) {
98 return false;
99 }
100 }
101};
102
103template<>
104struct convert<DNSName> {
105 static Node encode(const DNSName& rhs) {
106 return Node(rhs.toStringRootDot());
107 }
108 static bool decode(const Node& node, DNSName& rhs) {
109 if (!node.IsScalar()) {
110 return false;
111 }
112 try {
113 rhs = DNSName(node.as<string>());
114 return true;
115 } catch(const runtime_error &e) {
116 return false;
117 } catch (const PDNSException &e) {
118 return false;
119 }
120 }
121};
9f517da5
PL
122
123template<>
124struct convert<Netmask> {
125 static Node encode(const Netmask& rhs) {
126 return Node(rhs.toString());
127 }
128 static bool decode(const Node& node, Netmask& rhs) {
129 if (!node.IsScalar()) {
130 return false;
131 }
132 try {
133 rhs = Netmask(node.as<string>());
134 return true;
135 } catch(const runtime_error &e) {
136 return false;
137 } catch (const PDNSException &e) {
138 return false;
139 }
140 }
141};
f2d45260
PL
142} // namespace YAML
143
023d807e 144struct ixfrdiff_t {
d06dcda4
RG
145 shared_ptr<const SOARecordContent> oldSOA;
146 shared_ptr<const SOARecordContent> newSOA;
023d807e
RG
147 vector<DNSRecord> removals;
148 vector<DNSRecord> additions;
d56c1443
RG
149 uint32_t oldSOATTL;
150 uint32_t newSOATTL;
023d807e
RG
151};
152
153struct ixfrinfo_t {
d06dcda4 154 shared_ptr<const SOARecordContent> soa; // The SOA of the latest AXFR
023d807e
RG
155 records_t latestAXFR; // The most recent AXFR
156 vector<std::shared_ptr<ixfrdiff_t>> ixfrDiffs;
d56c1443 157 uint32_t soaTTL;
023d807e
RG
158};
159
f2d45260
PL
160// Why a struct? This way we can add more options to a domain in the future
161struct ixfrdistdomain_t {
d525b58b 162 set<ComboAddress> primaries; // A set so we can do multiple primary addresses in the future
146ada65 163 std::set<ComboAddress> notify; // Set of addresses to forward NOTIFY to
f58f871b 164 uint32_t maxSOARefresh{0}; // Cap SOA refresh value to the given value in seconds
f2d45260 165};
aff1f1fd 166
f2d45260 167// This contains the configuration for each domain
971e5911 168static map<DNSName, ixfrdistdomain_t> g_domainConfigs;
aff1f1fd 169
e2cddb03 170// Map domains and their data
ffb1b049 171static LockGuarded<std::map<DNSName, std::shared_ptr<ixfrinfo_t>>> g_soas;
772cdea3 172
d525b58b 173// Queue of received NOTIFYs, already verified against their primary IPs
06699363
PD
174// Lazily implemented as a set
175static LockGuarded<std::set<DNSName>> g_notifiesReceived;
176
146ada65
CHB
177// Queue of outgoing NOTIFY
178static LockGuarded<NotificationQueue> g_notificationQueue;
179
a2969595 180// Condition variable for TCP handling
971e5911
RG
181static std::condition_variable g_tcpHandlerCV;
182static std::queue<pair<int, ComboAddress>> g_tcpRequestFDs;
183static std::mutex g_tcpRequestFDsMutex;
a2969595 184
a2670f5e 185namespace po = boost::program_options;
772cdea3 186
971e5911 187static bool g_exiting = false;
bf676f2f 188
06699363
PD
189static NetmaskGroup g_acl; // networks that can QUERY us
190static NetmaskGroup g_notifySources; // networks (well, IPs) that can NOTIFY us
971e5911 191static bool g_compress = false;
6329cf4d 192
42d4d25b
PD
193static ixfrdistStats g_stats;
194
d5c9e1cb
PL
195// g_stats is static, so local to this file. But the webserver needs this info
196string doGetStats() {
197 return g_stats.getStats();
198}
199
ba779b12 200static void handleSignal(int signum) {
1361013e 201 g_log<<Logger::Notice<<"Got "<<strsignal(signum)<<" signal";
37499e32 202 if (g_exiting) {
1361013e 203 g_log<<Logger::Notice<<", this is the second time we were asked to stop, forcefully exiting"<<endl;
37499e32
PL
204 exit(EXIT_FAILURE);
205 }
b23d9f7a 206 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;
bf676f2f
PL
207 g_exiting = true;
208}
209
ba779b12 210static void usage(po::options_description &desc) {
f2d45260 211 cerr << "Usage: ixfrdist [OPTION]..."<<endl;
a2670f5e
PL
212 cerr << desc << "\n";
213}
214
37499e32 215// The compiler does not like using rfc1982LessThan in std::sort directly
ba779b12 216static bool sortSOA(uint32_t i, uint32_t j) {
37499e32
PL
217 return rfc1982LessThan(i, j);
218}
219
ba779b12 220static void cleanUpDomain(const DNSName& domain, const uint16_t& keep, const string& workdir) {
592f52eb 221 string dir = workdir + "/" + domain.toString();
37499e32 222 vector<uint32_t> zoneVersions;
d3190b7d
RG
223 auto directoryError = pdns::visit_directory(dir, [&zoneVersions]([[maybe_unused]] ino_t inodeNumber, const std::string_view& name) {
224 if (name != "." && name != "..") {
225 try {
226 auto version = pdns::checked_stoi<uint32_t>(std::string(name));
227 zoneVersions.push_back(version);
228 }
229 catch (...) {
694c668c 230 }
37499e32 231 }
d3190b7d
RG
232 return true;
233 });
234
235 if (directoryError) {
236 return;
37499e32 237 }
694c668c 238
592f52eb
PL
239 g_log<<Logger::Info<<"Found "<<zoneVersions.size()<<" versions of "<<domain<<", asked to keep "<<keep<<", ";
240 if (zoneVersions.size() <= keep) {
1361013e 241 g_log<<Logger::Info<<"not cleaning up"<<endl;
37499e32
PL
242 return;
243 }
592f52eb 244 g_log<<Logger::Info<<"cleaning up the oldest "<<zoneVersions.size() - keep<<endl;
37499e32
PL
245
246 // Sort the versions
247 std::sort(zoneVersions.begin(), zoneVersions.end(), sortSOA);
248
249 // And delete all the old ones
250 {
251 // Lock to ensure no one reads this.
ffb1b049 252 auto lock = g_soas.lock();
592f52eb 253 for (auto iter = zoneVersions.cbegin(); iter != zoneVersions.cend() - keep; ++iter) {
37499e32 254 string fname = dir + "/" + std::to_string(*iter);
1361013e 255 g_log<<Logger::Debug<<"Removing "<<fname<<endl;
37499e32
PL
256 unlink(fname.c_str());
257 }
258 }
259}
260
d06dcda4 261static void getSOAFromRecords(const records_t& records, shared_ptr<const SOARecordContent>& soa, uint32_t& soaTTL) {
e2cddb03
PL
262 for (const auto& dnsrecord : records) {
263 if (dnsrecord.d_type == QType::SOA) {
d56c1443 264 soa = getRR<SOARecordContent>(dnsrecord);
e2cddb03
PL
265 if (soa == nullptr) {
266 throw PDNSException("Unable to determine SOARecordContent from old records");
267 }
d56c1443
RG
268 soaTTL = dnsrecord.d_ttl;
269 return;
e2cddb03
PL
270 }
271 }
272 throw PDNSException("No SOA in supplied records");
273}
274
d06dcda4 275static void makeIXFRDiff(const records_t& from, const records_t& to, std::shared_ptr<ixfrdiff_t>& diff, const shared_ptr<const SOARecordContent>& fromSOA = nullptr, uint32_t fromSOATTL=0, const shared_ptr<const SOARecordContent>& toSOA = nullptr, uint32_t toSOATTL = 0) {
023d807e
RG
276 set_difference(from.cbegin(), from.cend(), to.cbegin(), to.cend(), back_inserter(diff->removals), from.value_comp());
277 set_difference(to.cbegin(), to.cend(), from.cbegin(), from.cend(), back_inserter(diff->additions), from.value_comp());
278 diff->oldSOA = fromSOA;
d56c1443 279 diff->oldSOATTL = fromSOATTL;
ddd3fe8d 280 if (fromSOA == nullptr) {
d56c1443 281 getSOAFromRecords(from, diff->oldSOA, diff->oldSOATTL);
ddd3fe8d 282 }
023d807e 283 diff->newSOA = toSOA;
d56c1443 284 diff->newSOATTL = toSOATTL;
ddd3fe8d 285 if (toSOA == nullptr) {
d56c1443 286 getSOAFromRecords(to, diff->newSOA, diff->newSOATTL);
ddd3fe8d 287 }
e2cddb03
PL
288}
289
023d807e
RG
290/* you can _never_ alter the content of the resulting shared pointer */
291static std::shared_ptr<ixfrinfo_t> getCurrentZoneInfo(const DNSName& domain)
292{
ffb1b049 293 return (*g_soas.lock())[domain];
023d807e
RG
294}
295
296static void updateCurrentZoneInfo(const DNSName& domain, std::shared_ptr<ixfrinfo_t>& newInfo)
297{
ffb1b049
RG
298 auto soas = g_soas.lock();
299 (*soas)[domain] = newInfo;
42d4d25b 300 g_stats.setSOASerial(domain, newInfo->soa->d_st.serial);
4140208a 301 // FIXME: also report zone size?
023d807e
RG
302}
303
ec70d60d 304static void sendNotification(int sock, const DNSName& domain, const ComboAddress& remote, uint16_t notificationId)
146ada65 305{
ec70d60d
CHB
306 std::vector<std::string> meta;
307 std::vector<uint8_t> packet;
308 DNSPacketWriter packetWriter(packet, domain, QType::SOA, 1, Opcode::Notify);
309 packetWriter.getHeader()->id = notificationId;
310 packetWriter.getHeader()->aa = true;
311
312 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
313 if (sendto(sock, packet.data(), packet.size(), 0, reinterpret_cast<const struct sockaddr*>(&remote), remote.getSocklen()) < 0) {
146ada65
CHB
314 throw std::runtime_error("Unable to send notify to " + remote.toStringWithPort() + ": " + stringerror());
315 }
316}
317
ec70d60d
CHB
318static void communicatorReceiveNotificationAnswers(const int sock4, const int sock6)
319{
df325482
CHB
320 std::set<int> fds = {sock4};
321 if (sock6 > 0) {
322 fds.insert(sock6);
323 }
ec70d60d 324 ComboAddress from;
2a0826d3 325 std::array<char, 1500> buffer{};
ec70d60d
CHB
326 int sock{-1};
327
328 // receive incoming notification answers on the nonblocking sockets and take them off the list
329 while (waitForMultiData(fds, 0, 0, &sock) > 0) {
330 Utility::socklen_t fromlen = sizeof(from);
331 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
332 const auto size = recvfrom(sock, buffer.data(), buffer.size(), 0, reinterpret_cast<struct sockaddr*>(&from), &fromlen);
333 if (size < 0) {
334 break;
335 }
336 DNSPacket packet(true);
337 packet.setRemote(&from);
338
339 if (packet.parse(buffer.data(), (size_t)size) < 0) {
340 g_log << Logger::Warning << "Unable to parse SOA notification answer from " << packet.getRemote() << endl;
341 continue;
342 }
343
344 if (packet.d.rcode != 0) {
345 g_log << Logger::Warning << "Received unsuccessful notification report for '" << packet.qdomain << "' from " << from.toStringWithPort() << ", error: " << RCode::to_s(packet.d.rcode) << endl;
346 }
347
348 if (g_notificationQueue.lock()->removeIf(from, packet.d.id, packet.qdomain)) {
2a0826d3 349 g_log << Logger::Notice << "Removed from notification list: '" << packet.qdomain << "' to " << from.toStringWithPort() << " " << (packet.d.rcode != 0 ? RCode::to_s(packet.d.rcode) : "(was acknowledged)") << endl;
ec70d60d
CHB
350 }
351 else {
352 g_log << Logger::Warning << "Received spurious notify answer for '" << packet.qdomain << "' from " << from.toStringWithPort() << endl;
353 }
354 }
355}
356
357static void communicatorSendNotifications(const int sock4, const int sock6)
358{
359 DNSName domain;
360 string destinationIp;
361 uint16_t notificationId = 0;
362 bool purged{false};
363
364 while (g_notificationQueue.lock()->getOne(domain, destinationIp, &notificationId, purged)) {
365 if (!purged) {
366 ComboAddress remote(destinationIp, 53); // default to 53
df325482
CHB
367 if (remote.sin4.sin_family == AF_INET) {
368 sendNotification(sock4, domain, remote, notificationId);
369 } else if (sock6 > 0) {
370 sendNotification(sock6, domain, remote, notificationId);
371 } else {
372 g_log << Logger::Warning << "Unable to notify " << destinationIp << " for " << domain << " as v6 support is not enabled" << std::endl;
373 }
ec70d60d
CHB
374 } else {
375 g_log << Logger::Warning << "Notification for " << domain << " to " << destinationIp << " failed after retries" << std::endl;
376 }
377 }
378}
379
146ada65
CHB
380static void communicatorThread()
381{
382 setThreadName("ixfrdist/communicator");
383 auto sock4 = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET, 0), true);
384 auto sock6 = makeQuerySocket(pdns::getQueryLocalAddress(AF_INET6, 0), true);
385
df325482
CHB
386 if (sock4 < 0) {
387 throw std::runtime_error("Unable to create local query socket");
388 }
389 // sock6 can be negative if there is no v6 support, but this is handled later while sending notifications
390
146ada65
CHB
391 while (true) {
392 if (g_exiting) {
393 g_log << Logger::Notice << "Communicator thread stopped" << std::endl;
394 break;
395 }
ec70d60d
CHB
396 communicatorReceiveNotificationAnswers(sock4, sock6);
397 communicatorSendNotifications(sock4, sock6);
2a0826d3 398 std::this_thread::sleep_for(std::chrono::seconds(1));
146ada65 399 }
36654cc6 400 if (sock4 >= 0) {
df325482
CHB
401 closesocket(sock4);
402 }
36654cc6 403 if (sock6 >= 0) {
df325482
CHB
404 closesocket(sock6);
405 }
146ada65
CHB
406}
407
612dfead 408static void updateThread(const string& workdir, const uint16_t& keep, const uint16_t& axfrTimeout, const uint16_t& soaRetry, const uint32_t axfrMaxRecords) { // NOLINT(readability-function-cognitive-complexity) 13400 https://github.com/PowerDNS/pdns/issues/13400 Habbie: ixfrdist: reduce complexity
519f5484 409 setThreadName("ixfrdist/update");
38e6a9ee
PL
410 std::map<DNSName, time_t> lastCheck;
411
412 // Initialize the serials we have
f2d45260
PL
413 for (const auto &domainConfig : g_domainConfigs) {
414 DNSName domain = domainConfig.first;
38e6a9ee 415 lastCheck[domain] = 0;
592f52eb 416 string dir = workdir + "/" + domain.toString();
38e6a9ee 417 try {
1361013e 418 g_log<<Logger::Info<<"Trying to initially load domain "<<domain<<" from disk"<<endl;
28942414 419 auto serial = getSerialFromDir(dir);
d06dcda4 420 shared_ptr<const SOARecordContent> soa;
d56c1443 421 uint32_t soaTTL;
772cdea3 422 {
592f52eb 423 string fname = workdir + "/" + domain.toString() + "/" + std::to_string(serial);
d56c1443 424 loadSOAFromDisk(domain, fname, soa, soaTTL);
e2cddb03 425 records_t records;
450a20f2 426 if (soa == nullptr) {
8b0f35ea
PD
427 g_log<<Logger::Error<<"Could not load SOA from disk for zone "<<domain<<", removing file '"<<fname<<"'"<<endl;
428 unlink(fname.c_str());
772cdea3 429 }
450a20f2 430 loadZoneFromDisk(records, fname, domain);
023d807e
RG
431 auto zoneInfo = std::make_shared<ixfrinfo_t>();
432 zoneInfo->latestAXFR = std::move(records);
433 zoneInfo->soa = soa;
d56c1443 434 zoneInfo->soaTTL = soaTTL;
023d807e 435 updateCurrentZoneInfo(domain, zoneInfo);
772cdea3 436 }
37499e32 437 if (soa != nullptr) {
1361013e 438 g_log<<Logger::Notice<<"Loaded zone "<<domain<<" with serial "<<soa->d_st.serial<<endl;
37499e32 439 // Initial cleanup
592f52eb 440 cleanUpDomain(domain, keep, workdir);
37499e32 441 }
38e6a9ee
PL
442 } catch (runtime_error &e) {
443 // Most likely, the directory does not exist.
1361013e 444 g_log<<Logger::Info<<e.what()<<", attempting to create"<<endl;
38e6a9ee 445 // Attempt to create it, if _that_ fails, there is no hope
33494729 446 if (mkdir(dir.c_str(), 0777) == -1 && errno != EEXIST) {
04360367 447 g_log<<Logger::Error<<"Could not create '"<<dir<<"': "<<stringerror()<<endl;
d56c1443 448 _exit(EXIT_FAILURE);
38e6a9ee
PL
449 }
450 }
451 }
452
1361013e 453 g_log<<Logger::Notice<<"Update Thread started"<<endl;
772cdea3 454
38e6a9ee 455 while (true) {
bf676f2f 456 if (g_exiting) {
1361013e 457 g_log<<Logger::Notice<<"UpdateThread stopped"<<endl;
bf676f2f
PL
458 break;
459 }
38e6a9ee 460 time_t now = time(nullptr);
f2d45260 461 for (const auto &domainConfig : g_domainConfigs) {
c9198339
RG
462
463 if (g_exiting) {
464 break;
465 }
466
f2d45260 467 DNSName domain = domainConfig.first;
d06dcda4 468 shared_ptr<const SOARecordContent> current_soa;
023d807e
RG
469 const auto& zoneInfo = getCurrentZoneInfo(domain);
470 if (zoneInfo != nullptr) {
471 current_soa = zoneInfo->soa;
26ecdd79 472 }
4d2bb666
RG
473
474 auto& zoneLastCheck = lastCheck[domain];
f58f871b
CHB
475 uint32_t refresh = soaRetry; // default if we don't get an update at all
476 if (current_soa != nullptr) {
477 // Check every `refresh` seconds as advertised in the SOA record
478 refresh = current_soa->d_st.refresh;
479 if (domainConfig.second.maxSOARefresh > 0) {
480 // Cap refresh value to the configured one if any
481 refresh = std::min(refresh, domainConfig.second.maxSOARefresh);
482 }
483 }
06699363
PD
484
485
486 if (now - zoneLastCheck < refresh && g_notifiesReceived.lock()->erase(domain) == 0) {
772cdea3 487 continue;
38e6a9ee 488 }
f2d45260 489
d525b58b
KM
490 // TODO Keep track of 'down' primaries
491 set<ComboAddress>::const_iterator it(domainConfig.second.primaries.begin());
492 std::advance(it, dns_random(domainConfig.second.primaries.size()));
493 ComboAddress primary = *it;
f2d45260 494
592f52eb 495 string dir = workdir + "/" + domain.toString();
d525b58b 496 g_log << Logger::Info << "Attempting to retrieve SOA Serial update for '" << domain << "' from '" << primary.toStringWithPort() << "'" << endl;
d06dcda4 497 shared_ptr<const SOARecordContent> sr;
772cdea3 498 try {
4d2bb666 499 zoneLastCheck = now;
4140208a 500 g_stats.incrementSOAChecks(domain);
d525b58b 501 auto newSerial = getSerialFromPrimary(primary, domain, sr); // TODO TSIG
26ecdd79 502 if(current_soa != nullptr) {
d525b58b 503 g_log << Logger::Info << "Got SOA Serial for " << domain << " from " << primary.toStringWithPort() << ": " << newSerial << ", had Serial: " << current_soa->d_st.serial;
26ecdd79 504 if (newSerial == current_soa->d_st.serial) {
1361013e 505 g_log<<Logger::Info<<", not updating."<<endl;
23b644c6 506 continue;
38e6a9ee 507 }
1361013e 508 g_log<<Logger::Info<<", will update."<<endl;
38e6a9ee 509 }
772cdea3 510 } catch (runtime_error &e) {
d525b58b 511 g_log << Logger::Warning << "Unable to get SOA serial update for '" << domain << "' from primary " << primary.toStringWithPort() << ": " << e.what() << endl;
4140208a 512 g_stats.incrementSOAChecksFailed(domain);
772cdea3 513 continue;
38e6a9ee
PL
514 }
515 // Now get the full zone!
1361013e 516 g_log<<Logger::Info<<"Attempting to receive full zonedata for '"<<domain<<"'"<<endl;
d525b58b 517 ComboAddress local = primary.isIPv4() ? ComboAddress("0.0.0.0") : ComboAddress("::");
38e6a9ee 518 TSIGTriplet tt;
26ecdd79
PL
519
520 // The *new* SOA
d06dcda4 521 shared_ptr<const SOARecordContent> soa;
d56c1443 522 uint32_t soaTTL = 0;
451605af 523 records_t records;
38e6a9ee 524 try {
d525b58b 525 AXFRRetriever axfr(primary, domain, tt, &local);
ef6f0c6f 526 uint32_t nrecords=0;
38e6a9ee
PL
527 Resolver::res_t nop;
528 vector<DNSRecord> chunk;
281ecb93 529 time_t t_start = time(nullptr);
f2457bfb 530 time_t axfr_now = time(nullptr);
592f52eb 531 while(axfr.getChunk(nop, &chunk, (axfr_now - t_start + axfrTimeout))) {
38e6a9ee
PL
532 for(auto& dr : chunk) {
533 if(dr.d_type == QType::TSIG)
534 continue;
15da1925
PD
535 if(!dr.d_name.isPartOf(domain)) {
536 throw PDNSException("Out-of-zone data received during AXFR of "+domain.toLogString());
537 }
38e6a9ee
PL
538 dr.d_name.makeUsRelative(domain);
539 records.insert(dr);
540 nrecords++;
772cdea3
PL
541 if (dr.d_type == QType::SOA) {
542 soa = getRR<SOARecordContent>(dr);
d56c1443 543 soaTTL = dr.d_ttl;
772cdea3 544 }
38e6a9ee 545 }
ef6f0c6f
PL
546 if (axfrMaxRecords != 0 && nrecords > axfrMaxRecords) {
547 throw PDNSException("Received more than " + std::to_string(axfrMaxRecords) + " records in AXFR, aborted");
548 }
f2457bfb 549 axfr_now = time(nullptr);
592f52eb 550 if (axfr_now - t_start > axfrTimeout) {
4140208a 551 g_stats.incrementAXFRFailures(domain);
f2457bfb
PL
552 throw PDNSException("Total AXFR time exceeded!");
553 }
38e6a9ee 554 }
23b644c6 555 if (soa == nullptr) {
4140208a 556 g_stats.incrementAXFRFailures(domain);
1361013e 557 g_log<<Logger::Warning<<"No SOA was found in the AXFR of "<<domain<<endl;
23b644c6
PL
558 continue;
559 }
1361013e 560 g_log<<Logger::Notice<<"Retrieved all zone data for "<<domain<<". Received "<<nrecords<<" records."<<endl;
451605af 561 } catch (PDNSException &e) {
4140208a 562 g_stats.incrementAXFRFailures(domain);
451605af 563 g_log<<Logger::Warning<<"Could not retrieve AXFR for '"<<domain<<"': "<<e.reason<<endl;
97a2d904 564 continue;
451605af 565 } catch (runtime_error &e) {
4140208a 566 g_stats.incrementAXFRFailures(domain);
451605af 567 g_log<<Logger::Warning<<"Could not retrieve AXFR for zone '"<<domain<<"': "<<e.what()<<endl;
97a2d904 568 continue;
451605af
RG
569 }
570
571 try {
572
38e6a9ee 573 writeZoneToDisk(records, domain, dir);
1361013e 574 g_log<<Logger::Notice<<"Wrote zonedata for "<<domain<<" with serial "<<soa->d_st.serial<<" to "<<dir<<endl;
451605af 575
023d807e 576 const auto oldZoneInfo = getCurrentZoneInfo(domain);
e35f9e46 577 auto ixfrInfo = std::make_shared<ixfrinfo_t>();
451605af 578
023d807e
RG
579 if (oldZoneInfo && !oldZoneInfo->latestAXFR.empty()) {
580 auto diff = std::make_shared<ixfrdiff_t>();
e35f9e46 581 ixfrInfo->ixfrDiffs = oldZoneInfo->ixfrDiffs;
023d807e 582 g_log<<Logger::Debug<<"Calculating diff for "<<domain<<endl;
d56c1443 583 makeIXFRDiff(oldZoneInfo->latestAXFR, records, diff, oldZoneInfo->soa, oldZoneInfo->soaTTL, soa, soaTTL);
023d807e 584 g_log<<Logger::Debug<<"Calculated diff for "<<domain<<", we had "<<diff->removals.size()<<" removals and "<<diff->additions.size()<<" additions"<<endl;
e35f9e46 585 ixfrInfo->ixfrDiffs.push_back(std::move(diff));
023d807e 586 }
451605af 587
023d807e 588 // Clean up the diffs
e35f9e46
OM
589 while (ixfrInfo->ixfrDiffs.size() > keep) {
590 ixfrInfo->ixfrDiffs.erase(ixfrInfo->ixfrDiffs.begin());
0d19f1ca 591 }
023d807e
RG
592
593 g_log<<Logger::Debug<<"Zone "<<domain<<" previously contained "<<(oldZoneInfo ? oldZoneInfo->latestAXFR.size() : 0)<<" entries, "<<records.size()<<" now"<<endl;
e35f9e46 594 ixfrInfo->latestAXFR = std::move(records);
8690b6ff 595 ixfrInfo->soa = std::move(soa);
e35f9e46
OM
596 ixfrInfo->soaTTL = soaTTL;
597 updateCurrentZoneInfo(domain, ixfrInfo);
281ecb93 598 } catch (PDNSException &e) {
4140208a 599 g_stats.incrementAXFRFailures(domain);
451605af 600 g_log<<Logger::Warning<<"Could not save zone '"<<domain<<"' to disk: "<<e.reason<<endl;
38e6a9ee 601 } catch (runtime_error &e) {
4140208a 602 g_stats.incrementAXFRFailures(domain);
1361013e 603 g_log<<Logger::Warning<<"Could not save zone '"<<domain<<"' to disk: "<<e.what()<<endl;
38e6a9ee 604 }
451605af 605
37499e32 606 // Now clean up the directory
592f52eb 607 cleanUpDomain(domain, keep, workdir);
38e6a9ee 608 } /* for (const auto &domain : domains) */
bf676f2f 609 sleep(1);
38e6a9ee
PL
610 } /* while (true) */
611} /* updateThread */
4db8fd44 612
5b546566 613enum class ResponseType {
06699363 614 Unknown,
5b546566
PD
615 ValidQuery,
616 RefusedOpcode,
06699363
PD
617 RefusedQuery,
618 EmptyNoError
5b546566
PD
619};
620
06699363 621static ResponseType maybeHandleNotify(const MOADNSParser& mdp, const ComboAddress& saddr, const string& logPrefix="") {
612dfead 622 if (mdp.d_header.opcode != Opcode::Notify) { // NOLINT(bugprone-narrowing-conversions, cppcoreguidelines-narrowing-conversions) opcode is 4 bits, this is not a dangerous conversion
06699363
PD
623 return ResponseType::Unknown;
624 }
625
626 g_log<<Logger::Info<<logPrefix<<"NOTIFY for "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).toString()<<" "<< Opcode::to_s(mdp.d_header.opcode) <<" from "<<saddr.toStringWithPort()<<endl;
627
628 auto found = g_domainConfigs.find(mdp.d_qname);
629 if (found == g_domainConfigs.end()) {
630 g_log<<Logger::Info<<("Domain name '" + mdp.d_qname.toLogString() + "' is not configured for notification")<<endl;
631 return ResponseType::RefusedQuery;
632 }
633
d525b58b 634 auto primaries = found->second.primaries;
06699363 635
d525b58b 636 bool primaryFound = false;
06699363 637
d525b58b
KM
638 for (const auto& primary : primaries) {
639 if (ComboAddress::addressOnlyEqual()(saddr, primary)) {
640 primaryFound = true;
06699363
PD
641 break;
642 }
643 }
644
d525b58b 645 if (primaryFound) {
06699363 646 g_notifiesReceived.lock()->insert(mdp.d_qname);
146ada65
CHB
647
648 if (!found->second.notify.empty()) {
649 for (const auto& address : found->second.notify) {
650 g_log << Logger::Debug << logPrefix << "Queuing notification for " << mdp.d_qname << " to " << address.toStringWithPort() << std::endl;
651 g_notificationQueue.lock()->add(mdp.d_qname, address);
652 }
653 }
06699363
PD
654 return ResponseType::EmptyNoError;
655 }
656
657 return ResponseType::RefusedQuery;
658}
659
5b546566 660static ResponseType checkQuery(const MOADNSParser& mdp, const ComboAddress& saddr, const bool udp = true, const string& logPrefix="") {
772cdea3
PL
661 vector<string> info_msg;
662
5b546566 663 auto ret = ResponseType::ValidQuery;
772cdea3 664
5b546566 665 g_log<<Logger::Debug<<logPrefix<<"Had "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).toString()<<" query from "<<saddr.toStringWithPort()<<endl;
772cdea3 666
612dfead 667 if (mdp.d_header.opcode != Opcode::Query) { // NOLINT(bugprone-narrowing-conversions, cppcoreguidelines-narrowing-conversions) opcode is 4 bits, this is not a dangerous conversion
06699363 668 info_msg.push_back("Opcode is unsupported (" + Opcode::to_s(mdp.d_header.opcode) + "), expected QUERY"); // note that we also emit this for a NOTIFY from a wrong source
5b546566 669 ret = ResponseType::RefusedOpcode;
772cdea3 670 }
5b546566
PD
671 else {
672 if (udp && mdp.d_qtype != QType::SOA && mdp.d_qtype != QType::IXFR) {
673 info_msg.push_back("QType is unsupported (" + QType(mdp.d_qtype).toString() + " is not in {SOA,IXFR})");
674 ret = ResponseType::RefusedQuery;
675 }
772cdea3 676
5b546566
PD
677 if (!udp && mdp.d_qtype != QType::SOA && mdp.d_qtype != QType::IXFR && mdp.d_qtype != QType::AXFR) {
678 info_msg.push_back("QType is unsupported (" + QType(mdp.d_qtype).toString() + " is not in {SOA,IXFR,AXFR})");
679 ret = ResponseType::RefusedQuery;
d8e734f4 680 }
5b546566
PD
681
682 {
683 if (g_domainConfigs.find(mdp.d_qname) == g_domainConfigs.end()) {
684 info_msg.push_back("Domain name '" + mdp.d_qname.toLogString() + "' is not configured for distribution");
685 ret = ResponseType::RefusedQuery;
686 }
687 else {
688 const auto zoneInfo = getCurrentZoneInfo(mdp.d_qname);
689 if (zoneInfo == nullptr) {
612dfead 690 info_msg.emplace_back("Domain has not been transferred yet");
5b546566
PD
691 ret = ResponseType::RefusedQuery;
692 }
3852fafa 693 }
d8e734f4 694 }
772cdea3
PL
695 }
696
5b546566
PD
697 if (!info_msg.empty()) { // which means ret is not SOA
698 g_log<<Logger::Warning<<logPrefix<<"Refusing "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).toString()<<" "<< Opcode::to_s(mdp.d_header.opcode) <<" from "<<saddr.toStringWithPort();
1361013e
PL
699 g_log<<Logger::Warning<<": ";
700 bool first = true;
701 for (const auto& s : info_msg) {
702 if (!first) {
703 g_log<<Logger::Warning<<", ";
772cdea3 704 }
56b99c6a 705 first = false;
1361013e 706 g_log<<Logger::Warning<<s;
772cdea3 707 }
1361013e 708 g_log<<Logger::Warning<<endl;
5b546566 709 // fall through to return below
772cdea3
PL
710 }
711
5b546566 712 return ret;
772cdea3
PL
713}
714
06699363
PD
715/*
716 * Returns a vector<uint8_t> that represents the full empty NOERROR response.
717 * QNAME is read from mdp.
718 */
719static bool makeEmptyNoErrorPacket(const MOADNSParser& mdp, vector<uint8_t>& packet) {
06699363
PD
720 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
721 pw.getHeader()->opcode = mdp.d_header.opcode;
722 pw.getHeader()->id = mdp.d_header.id;
723 pw.getHeader()->rd = mdp.d_header.rd;
724 pw.getHeader()->qr = 1;
725 pw.getHeader()->aa = 1;
726
727 pw.commit();
728
729 return true;
730}
731
0c16458b 732/*
3852fafa 733 * Returns a vector<uint8_t> that represents the full positive response to a SOA
0c16458b 734 * query. QNAME is read from mdp.
0c16458b 735 */
ba779b12 736static bool makeSOAPacket(const MOADNSParser& mdp, vector<uint8_t>& packet) {
023d807e
RG
737
738 auto zoneInfo = getCurrentZoneInfo(mdp.d_qname);
739 if (zoneInfo == nullptr) {
740 return false;
741 }
742
772cdea3 743 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
06699363 744 pw.getHeader()->opcode = mdp.d_header.opcode;
772cdea3
PL
745 pw.getHeader()->id = mdp.d_header.id;
746 pw.getHeader()->rd = mdp.d_header.rd;
747 pw.getHeader()->qr = 1;
63f3e97b 748 pw.getHeader()->aa = 1;
772cdea3 749
d56c1443 750 pw.startRecord(mdp.d_qname, QType::SOA, zoneInfo->soaTTL);
023d807e 751 zoneInfo->soa->toPacket(pw);
772cdea3
PL
752 pw.commit();
753
0eb03ead 754 return true;
772cdea3
PL
755}
756
3852fafa
PD
757/*
758 * Returns a vector<uint8_t> that represents the full REFUSED response to a
759 * query. QNAME and type are read from mdp.
760 */
761static bool makeRefusedPacket(const MOADNSParser& mdp, vector<uint8_t>& packet) {
762 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
06699363 763 pw.getHeader()->opcode = mdp.d_header.opcode;
3852fafa
PD
764 pw.getHeader()->id = mdp.d_header.id;
765 pw.getHeader()->rd = mdp.d_header.rd;
766 pw.getHeader()->qr = 1;
767 pw.getHeader()->rcode = RCode::Refused;
768
769 return true;
770}
771
5b546566
PD
772/*
773 * Returns a vector<uint8_t> that represents the full NOTIMP response to a
774 * query. QNAME and type are read from mdp.
775 */
776static bool makeNotimpPacket(const MOADNSParser& mdp, vector<uint8_t>& packet) {
777 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
06699363 778 pw.getHeader()->opcode = mdp.d_header.opcode;
5b546566
PD
779 pw.getHeader()->id = mdp.d_header.id;
780 pw.getHeader()->rd = mdp.d_header.rd;
781 pw.getHeader()->qr = 1;
782 pw.getHeader()->rcode = RCode::NotImp;
5b546566
PD
783
784 return true;
785}
786
d06dcda4 787static vector<uint8_t> getSOAPacket(const MOADNSParser& mdp, const shared_ptr<const SOARecordContent>& soa, uint32_t soaTTL) {
da704871 788 vector<uint8_t> packet;
0c16458b
PL
789 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
790 pw.getHeader()->id = mdp.d_header.id;
791 pw.getHeader()->rd = mdp.d_header.rd;
792 pw.getHeader()->qr = 1;
793
da704871 794 // Add the first SOA
d56c1443 795 pw.startRecord(mdp.d_qname, QType::SOA, soaTTL);
da704871
PL
796 soa->toPacket(pw);
797 pw.commit();
798 return packet;
799}
800
ba779b12
RG
801static bool sendPacketOverTCP(int fd, const std::vector<uint8_t>& packet)
802{
803 char sendBuf[2];
804 sendBuf[0]=packet.size()/256;
805 sendBuf[1]=packet.size()%256;
806
7f95a941
CHB
807 writen2(fd, sendBuf, 2);
808 writen2(fd, &packet[0], packet.size());
ba779b12
RG
809 return true;
810}
811
971e5911 812static bool addRecordToWriter(DNSPacketWriter& pw, const DNSName& zoneName, const DNSRecord& record, bool compress)
ba779b12 813{
971e5911 814 pw.startRecord(record.d_name + zoneName, record.d_type, record.d_ttl, QClass::IN, DNSResourceRecord::ANSWER, compress);
d06dcda4 815 record.getContent()->toPacket(pw);
a66a8f71 816 if (pw.size() > 16384) {
ba779b12
RG
817 pw.rollback();
818 return false;
819 }
820 return true;
821}
822
823template <typename T> static bool sendRecordsOverTCP(int fd, const MOADNSParser& mdp, const T& records)
824{
825 vector<uint8_t> packet;
826
827 for (auto it = records.cbegin(); it != records.cend();) {
828 bool recordsAdded = false;
829 packet.clear();
830 DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype);
831 pw.getHeader()->id = mdp.d_header.id;
832 pw.getHeader()->rd = mdp.d_header.rd;
833 pw.getHeader()->qr = 1;
834
835 while (it != records.cend()) {
836 if (it->d_type == QType::SOA) {
837 it++;
838 continue;
839 }
840
971e5911 841 if (addRecordToWriter(pw, mdp.d_qname, *it, g_compress)) {
ba779b12
RG
842 recordsAdded = true;
843 it++;
844 }
845 else {
846 if (recordsAdded) {
847 pw.commit();
848 sendPacketOverTCP(fd, packet);
849 }
850 if (it == records.cbegin()) {
851 /* something is wrong */
852 return false;
853 }
854
855 break;
856 }
857 }
858
859 if (it == records.cend() && recordsAdded) {
860 pw.commit();
861 sendPacketOverTCP(fd, packet);
862 }
863 }
864
865 return true;
866}
867
868
869static bool handleAXFR(int fd, const MOADNSParser& mdp) {
023d807e
RG
870 /* we get a shared pointer of the zone info that we can't modify, ever.
871 A newer one may arise in the meantime, but this one will stay valid
872 until we release it.
873 */
4140208a
PD
874
875 g_stats.incrementAXFRinQueries(mdp.d_qname);
876
023d807e
RG
877 auto zoneInfo = getCurrentZoneInfo(mdp.d_qname);
878 if (zoneInfo == nullptr) {
879 return false;
0c16458b
PL
880 }
881
d06dcda4 882 shared_ptr<const SOARecordContent> soa = zoneInfo->soa;
d56c1443 883 uint32_t soaTTL = zoneInfo->soaTTL;
023d807e 884 const records_t& records = zoneInfo->latestAXFR;
7750f6c6 885
da704871 886 // Initial SOA
d56c1443 887 const auto soaPacket = getSOAPacket(mdp, soa, soaTTL);
ba779b12
RG
888 if (!sendPacketOverTCP(fd, soaPacket)) {
889 return false;
890 }
0c16458b 891
ba779b12
RG
892 if (!sendRecordsOverTCP(fd, mdp, records)) {
893 return false;
0c16458b 894 }
0c16458b 895
da704871 896 // Final SOA
ba779b12
RG
897 if (!sendPacketOverTCP(fd, soaPacket)) {
898 return false;
899 }
0c16458b
PL
900
901 return true;
902}
772cdea3 903
0eb03ead
PL
904/* Produces an IXFR if one can be made according to the rules in RFC 1995 and
905 * creates a SOA or AXFR packet when required by the RFC.
ac9e0a99 906 */
d06dcda4 907static bool handleIXFR(int fd, const MOADNSParser& mdp, const shared_ptr<const SOARecordContent>& clientSOA) {
023d807e
RG
908 vector<std::shared_ptr<ixfrdiff_t>> toSend;
909
910 /* we get a shared pointer of the zone info that we can't modify, ever.
911 A newer one may arise in the meantime, but this one will stay valid
912 until we release it.
913 */
4140208a
PD
914
915 g_stats.incrementIXFRinQueries(mdp.d_qname);
916
023d807e
RG
917 auto zoneInfo = getCurrentZoneInfo(mdp.d_qname);
918 if (zoneInfo == nullptr) {
919 return false;
26ecdd79 920 }
ac9e0a99 921
023d807e
RG
922 uint32_t ourLatestSerial = zoneInfo->soa->d_st.serial;
923
924 if (rfc1982LessThan(ourLatestSerial, clientSOA->d_st.serial) || ourLatestSerial == clientSOA->d_st.serial) {
0eb03ead
PL
925 /* RFC 1995 Section 2
926 * If an IXFR query with the same or newer version number than that of
927 * the server is received, it is replied to with a single SOA record of
d4fec6e6 928 * the server's current version.
0eb03ead 929 */
da704871
PL
930 vector<uint8_t> packet;
931 bool ret = makeSOAPacket(mdp, packet);
932 if (ret) {
ba779b12 933 sendPacketOverTCP(fd, packet);
da704871
PL
934 }
935 return ret;
0eb03ead
PL
936 }
937
023d807e
RG
938 // as we use push_back in the updater, we know the vector is sorted as oldest first
939 bool shouldAdd = false;
940 // Get all relevant IXFR differences
941 for (const auto& diff : zoneInfo->ixfrDiffs) {
942 if (shouldAdd) {
943 toSend.push_back(diff);
944 continue;
945 }
946 if (diff->oldSOA->d_st.serial == clientSOA->d_st.serial) {
947 toSend.push_back(diff);
948 // Add all consecutive diffs
949 shouldAdd = true;
37499e32 950 }
37499e32 951 }
ac9e0a99 952
e2cddb03 953 if (toSend.empty()) {
4140208a 954 // FIXME: incrementIXFRFallbacks
1361013e 955 g_log<<Logger::Warning<<"No IXFR available from serial "<<clientSOA->d_st.serial<<" for zone "<<mdp.d_qname<<", attempting to send AXFR"<<endl;
ba779b12 956 return handleAXFR(fd, mdp);
ac9e0a99
PL
957 }
958
023d807e 959
f821ff19
HL
960 /* An IXFR packet's ANSWER section looks as follows:
961 * SOA latest_serial C
962
963 First set of changes:
964 * SOA requested_serial A
965 * ... removed records ...
966 * SOA intermediate_serial B
967 * ... added records ...
968
969 Next set of changes:
970 * SOA intermediate_serial B
971 * ... removed records ...
972 * SOA latest_serial C
973 * ... added records ...
974
975 * SOA latest_serial C
976 */
977
978 const auto latestSOAPacket = getSOAPacket(mdp, zoneInfo->soa, zoneInfo->soaTTL);
979 if (!sendPacketOverTCP(fd, latestSOAPacket)) {
980 return false;
981 }
982
983 for (const auto& diff : toSend) {
d56c1443
RG
984 const auto newSOAPacket = getSOAPacket(mdp, diff->newSOA, diff->newSOATTL);
985 const auto oldSOAPacket = getSOAPacket(mdp, diff->oldSOA, diff->oldSOATTL);
ba779b12 986
ba779b12
RG
987 if (!sendPacketOverTCP(fd, oldSOAPacket)) {
988 return false;
989 }
990
991 if (!sendRecordsOverTCP(fd, mdp, diff->removals)) {
992 return false;
993 }
994
995 if (!sendPacketOverTCP(fd, newSOAPacket)) {
996 return false;
997 }
998
999 if (!sendRecordsOverTCP(fd, mdp, diff->additions)) {
1000 return false;
1001 }
f821ff19 1002 }
ba779b12 1003
f821ff19
HL
1004 if (!sendPacketOverTCP(fd, latestSOAPacket)) {
1005 return false;
e2cddb03 1006 }
ac9e0a99
PL
1007
1008 return true;
1009}
1010
06699363
PD
1011static bool allowedByACL(const ComboAddress& addr, bool forNotify = false) {
1012 if (forNotify) {
1013 return g_notifySources.match(addr);
1014 }
1015
6329cf4d
PL
1016 return g_acl.match(addr);
1017}
1018
612dfead 1019static void handleUDPRequest(int fd, boost::any& /*unused*/)
5b546566
PD
1020try
1021{
aff1f1fd
PL
1022 // TODO make the buffer-size configurable
1023 char buf[4096];
772cdea3 1024 ComboAddress saddr;
8be49a38 1025 socklen_t fromlen = sizeof(saddr);
772cdea3 1026 int res = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr*) &saddr, &fromlen);
aff1f1fd
PL
1027
1028 if (res == 0) {
1361013e 1029 g_log<<Logger::Warning<<"Got an empty message from "<<saddr.toStringWithPort()<<endl;
aff1f1fd
PL
1030 return;
1031 }
1032
aff1f1fd 1033 if(res < 0) {
8b9554c9 1034 auto savedErrno = errno;
1361013e 1035 g_log<<Logger::Warning<<"Could not read message from "<<saddr.toStringWithPort()<<": "<<strerror(savedErrno)<<endl;
772cdea3
PL
1036 return;
1037 }
1038
fefb237c
PL
1039 if (saddr == ComboAddress("0.0.0.0", 0)) {
1040 g_log<<Logger::Warning<<"Could not determine source of message"<<endl;
6329cf4d
PL
1041 return;
1042 }
1043
36463240
PD
1044 if (!allowedByACL(saddr, true) && !allowedByACL(saddr, false)) {
1045 g_log<<Logger::Warning<<"UDP query from "<<saddr.toString()<<" did not match any valid query or NOTIFY source, dropping"<<endl;
1046 return;
1047 }
1048
612dfead 1049 MOADNSParser mdp(true, string(&buf[0], static_cast<size_t>(res)));
06699363
PD
1050 vector<uint8_t> packet;
1051
1052 ResponseType respt = ResponseType::Unknown;
1053
1054 if (allowedByACL(saddr, true)) {
1055 respt = maybeHandleNotify(mdp, saddr);
1056 }
1057 else if (!allowedByACL(saddr)) {
fefb237c 1058 g_log<<Logger::Warning<<"UDP query from "<<saddr.toString()<<" is not allowed, dropping"<<endl;
aff1f1fd
PL
1059 return;
1060 }
1061
06699363
PD
1062 if (respt == ResponseType::Unknown) {
1063 // query was not handled yet (so not a valid NOTIFY)
1064 respt = checkQuery(mdp, saddr);
1065 }
5b546566 1066 if (respt == ResponseType::ValidQuery) {
3852fafa
PD
1067 /* RFC 1995 Section 2
1068 * Transport of a query may be by either UDP or TCP. If an IXFR query
1069 * is via UDP, the IXFR server may attempt to reply using UDP if the
1070 * entire response can be contained in a single DNS packet. If the UDP
1071 * reply does not fit, the query is responded to with a single SOA
1072 * record of the server's current version to inform the client that a
1073 * TCP query should be initiated.
1074 *
1075 * Let's not complicate this with IXFR over UDP (and looking if we need to truncate etc).
1076 * Just send the current SOA and let the client try over TCP
1077 */
4140208a 1078 g_stats.incrementSOAinQueries(mdp.d_qname); // FIXME: this also counts IXFR queries (but the response is the same as to a SOA query)
3852fafa 1079 makeSOAPacket(mdp, packet);
06699363
PD
1080 } else if (respt == ResponseType::EmptyNoError) {
1081 makeEmptyNoErrorPacket(mdp, packet);
5b546566 1082 } else if (respt == ResponseType::RefusedQuery) {
ca0831d9 1083 g_stats.incrementUnknownDomainInQueries(mdp.d_qname);
3852fafa 1084 makeRefusedPacket(mdp, packet);
5b546566 1085 } else if (respt == ResponseType::RefusedOpcode) {
c8166ef4 1086 g_stats.incrementNotImplemented(mdp.d_header.opcode);
5b546566 1087 makeNotimpPacket(mdp, packet);
772cdea3 1088 }
aff1f1fd 1089
772cdea3 1090 if(sendto(fd, &packet[0], packet.size(), 0, (struct sockaddr*) &saddr, fromlen) < 0) {
8b9554c9 1091 auto savedErrno = errno;
d5fcd583 1092 g_log<<Logger::Warning<<"Could not send reply for "<<mdp.d_qname<<"|"<<QType(mdp.d_qtype).toString()<<" to "<<saddr.toStringWithPort()<<": "<<strerror(savedErrno)<<endl;
aff1f1fd 1093 }
772cdea3
PL
1094 return;
1095}
5b546566
PD
1096catch(std::exception& e) {
1097 return;
1098}
1099
aff1f1fd 1100
ba779b12 1101static void handleTCPRequest(int fd, boost::any&) {
772cdea3 1102 ComboAddress saddr;
4f80c4e0 1103 int cfd = 0;
772cdea3 1104
4f80c4e0
PL
1105 try {
1106 cfd = SAccept(fd, saddr);
1107 setBlocking(cfd);
1108 } catch(runtime_error &e) {
1361013e 1109 g_log<<Logger::Error<<e.what()<<endl;
772cdea3 1110 return;
aff1f1fd
PL
1111 }
1112
fefb237c
PL
1113 if (saddr == ComboAddress("0.0.0.0", 0)) {
1114 g_log<<Logger::Warning<<"Could not determine source of message"<<endl;
7d6ccb44 1115 close(cfd);
6329cf4d
PL
1116 return;
1117 }
1118
06699363
PD
1119 // we allow the connection if this is a legit client or a legit NOTIFY source
1120 // need to check per-operation later
1121 if (!allowedByACL(saddr) && !allowedByACL(saddr, true)) {
fefb237c
PL
1122 g_log<<Logger::Warning<<"TCP query from "<<saddr.toString()<<" is not allowed, dropping"<<endl;
1123 close(cfd);
772cdea3 1124 return;
aff1f1fd
PL
1125 }
1126
a2969595
PL
1127 {
1128 std::lock_guard<std::mutex> lg(g_tcpRequestFDsMutex);
1129 g_tcpRequestFDs.push({cfd, saddr});
772cdea3 1130 }
a2969595
PL
1131 g_tcpHandlerCV.notify_one();
1132}
772cdea3 1133
a2969595
PL
1134/* Thread to handle TCP traffic
1135 */
ba779b12 1136static void tcpWorker(int tid) {
519f5484 1137 setThreadName("ixfrdist/tcpWor");
a2969595
PL
1138 string prefix = "TCP Worker " + std::to_string(tid) + ": ";
1139
1140 while(true) {
1361013e 1141 g_log<<Logger::Debug<<prefix<<"ready for a new request!"<<endl;
a2969595
PL
1142 std::unique_lock<std::mutex> lk(g_tcpRequestFDsMutex);
1143 g_tcpHandlerCV.wait(lk, []{return g_tcpRequestFDs.size() || g_exiting ;});
1144 if (g_exiting) {
1361013e 1145 g_log<<Logger::Debug<<prefix<<"Stopping thread"<<endl;
a2969595
PL
1146 break;
1147 }
1361013e 1148 g_log<<Logger::Debug<<prefix<<"Going to handle a query"<<endl;
a2969595
PL
1149 auto request = g_tcpRequestFDs.front();
1150 g_tcpRequestFDs.pop();
1151 lk.unlock();
aff1f1fd 1152
a2969595
PL
1153 int cfd = request.first;
1154 ComboAddress saddr = request.second;
1155
1156 char buf[4096];
1157 ssize_t res;
1158 try {
1159 uint16_t toRead;
1160 readn2(cfd, &toRead, sizeof(toRead));
1161 toRead = std::min(ntohs(toRead), static_cast<uint16_t>(sizeof(buf)));
50111728 1162 res = readn2WithTimeout(cfd, &buf, toRead, timeval{2,0});
1361013e 1163 g_log<<Logger::Debug<<prefix<<"Had message of "<<std::to_string(toRead)<<" bytes from "<<saddr.toStringWithPort()<<endl;
a2969595 1164 } catch (runtime_error &e) {
1361013e 1165 g_log<<Logger::Warning<<prefix<<"Could not read message from "<<saddr.toStringWithPort()<<": "<<e.what()<<endl;
772cdea3 1166 close(cfd);
a2969595 1167 continue;
772cdea3
PL
1168 }
1169
a2969595
PL
1170 try {
1171 MOADNSParser mdp(true, string(buf, res));
1172
06699363 1173 ResponseType respt = ResponseType::Unknown;
5b546566 1174
06699363
PD
1175 // this code is duplicated from the UDP path
1176 if (allowedByACL(saddr, true)) {
1177 respt = maybeHandleNotify(mdp, saddr);
1178 }
1179 else if (!allowedByACL(saddr)) {
0eb03ead 1180 close(cfd);
a2969595 1181 continue;
0eb03ead 1182 }
772cdea3 1183
06699363
PD
1184 if (respt == ResponseType::Unknown) {
1185 respt = checkQuery(mdp, saddr, false, prefix);
1186 }
1187
1188 if (respt != ResponseType::ValidQuery && respt != ResponseType::EmptyNoError) { // on TCP, we currently do not bother with sending useful errors
1189 close(cfd);
1190 continue;
1191 }
1192
1193 vector<uint8_t> packet;
1194
1195 if (respt == ResponseType::EmptyNoError) {
1196 bool ret = makeEmptyNoErrorPacket(mdp, packet);
1197 if (!ret) {
1198 close(cfd);
1199 continue;
1200 }
1201 sendPacketOverTCP(cfd, packet);
1202 }
1203 else if (mdp.d_qtype == QType::SOA) {
a2969595
PL
1204 bool ret = makeSOAPacket(mdp, packet);
1205 if (!ret) {
1206 close(cfd);
1207 continue;
1208 }
ba779b12 1209 sendPacketOverTCP(cfd, packet);
0c16458b 1210 }
ba779b12
RG
1211 else if (mdp.d_qtype == QType::AXFR) {
1212 if (!handleAXFR(cfd, mdp)) {
a2969595
PL
1213 close(cfd);
1214 continue;
1215 }
1216 }
ba779b12 1217 else if (mdp.d_qtype == QType::IXFR) {
a2969595
PL
1218 /* RFC 1995 section 3:
1219 * The IXFR query packet format is the same as that of a normal DNS
1220 * query, but with the query type being IXFR and the authority section
1221 * containing the SOA record of client's version of the zone.
1222 */
d06dcda4 1223 shared_ptr<const SOARecordContent> clientSOA;
a2969595
PL
1224 for (auto &answer : mdp.d_answers) {
1225 // from dnsparser.hh:
1226 // typedef vector<pair<DNSRecord, uint16_t > > answers_t;
1227 if (answer.first.d_type == QType::SOA && answer.first.d_place == DNSResourceRecord::AUTHORITY) {
1228 clientSOA = getRR<SOARecordContent>(answer.first);
1229 if (clientSOA != nullptr) {
1230 break;
1231 }
ac9e0a99 1232 }
a2969595
PL
1233 } /* for (auto const &answer : mdp.d_answers) */
1234
1235 if (clientSOA == nullptr) {
1361013e 1236 g_log<<Logger::Warning<<prefix<<"IXFR request packet did not contain a SOA record in the AUTHORITY section"<<endl;
a2969595
PL
1237 close(cfd);
1238 continue;
ac9e0a99 1239 }
ac9e0a99 1240
6a6ae38a 1241 if (!handleIXFR(cfd, mdp, clientSOA)) {
a2969595
PL
1242 close(cfd);
1243 continue;
1244 }
1245 } /* if (mdp.d_qtype == QType::IXFR) */
ac9e0a99 1246
a2969595 1247 shutdown(cfd, 2);
16ce7f18
JS
1248 } catch (const MOADNSException &mde) {
1249 g_log<<Logger::Warning<<prefix<<"Could not parse DNS packet from "<<saddr.toStringWithPort()<<": "<<mde.what()<<endl;
a2969595 1250 } catch (runtime_error &e) {
1361013e 1251 g_log<<Logger::Warning<<prefix<<"Could not write reply to "<<saddr.toStringWithPort()<<": "<<e.what()<<endl;
a2969595
PL
1252 }
1253 // bye!
1254 close(cfd);
772cdea3 1255
a2969595
PL
1256 if (g_exiting) {
1257 break;
da704871 1258 }
772cdea3 1259 }
772cdea3 1260}
aff1f1fd 1261
f2d45260
PL
1262/* Parses the configuration file in configpath into config, adding defaults for
1263 * missing parameters (if applicable), returning true if the config file was
1264 * good, false otherwise. Will log all issues with the config
1265 */
ba779b12 1266static bool parseAndCheckConfig(const string& configpath, YAML::Node& config) {
592f52eb 1267 g_log<<Logger::Info<<"Loading configuration file from "<<configpath<<endl;
f2d45260
PL
1268 try {
1269 config = YAML::LoadFile(configpath);
1270 } catch (const runtime_error &e) {
592f52eb 1271 g_log<<Logger::Error<<"Unable to load configuration file '"<<configpath<<"': "<<e.what()<<endl;
f2d45260
PL
1272 return false;
1273 }
1274
1275 bool retval = true;
1276
1277 if (config["keep"]) {
1278 try {
1279 config["keep"].as<uint16_t>();
1280 } catch (const runtime_error &e) {
1281 g_log<<Logger::Error<<"Unable to read 'keep' value: "<<e.what()<<endl;
1282 retval = false;
1283 }
1284 } else {
592f52eb 1285 config["keep"] = 20;
f2d45260
PL
1286 }
1287
ef6f0c6f
PL
1288 if (config["axfr-max-records"]) {
1289 try {
1290 config["axfr-max-records"].as<uint32_t>();
1291 } catch (const runtime_error &e) {
1292 g_log<<Logger::Error<<"Unable to read 'axfr-max-records' value: "<<e.what()<<endl;
1293 }
1294 } else {
1295 config["axfr-max-records"] = 0;
1296 }
1297
f2d45260
PL
1298 if (config["axfr-timeout"]) {
1299 try {
1300 config["axfr-timeout"].as<uint16_t>();
1301 } catch (const runtime_error &e) {
1302 g_log<<Logger::Error<<"Unable to read 'axfr-timeout' value: "<<e.what()<<endl;
1303 }
1304 } else {
e6aaf558 1305 config["axfr-timeout"] = 20;
f2d45260
PL
1306 }
1307
0ac228c7
PD
1308 if (config["failed-soa-retry"]) {
1309 try {
1310 config["failed-soa-retry"].as<uint16_t>();
1311 } catch (const runtime_error &e) {
1312 g_log<<Logger::Error<<"Unable to read 'failed-soa-retry' value: "<<e.what()<<endl;
1313 }
1314 } else {
1315 config["failed-soa-retry"] = 30;
1316 }
1317
f2d45260
PL
1318 if (config["tcp-in-threads"]) {
1319 try {
1320 config["tcp-in-threads"].as<uint16_t>();
1321 } catch (const runtime_error &e) {
4529cc91 1322 g_log<<Logger::Error<<"Unable to read 'tcp-in-threads' value: "<<e.what()<<endl;
f2d45260
PL
1323 }
1324 } else {
1325 config["tcp-in-threads"] = 10;
1326 }
1327
1328 if (config["listen"]) {
1329 try {
1330 config["listen"].as<vector<ComboAddress>>();
1331 } catch (const runtime_error &e) {
1332 g_log<<Logger::Error<<"Unable to read 'listen' value: "<<e.what()<<endl;
1333 retval = false;
1334 }
1335 } else {
1336 config["listen"].push_back("127.0.0.1:53");
1337 config["listen"].push_back("[::1]:53");
1338 }
1339
1340 if (config["acl"]) {
1341 try {
1342 config["acl"].as<vector<string>>();
1343 } catch (const runtime_error &e) {
1344 g_log<<Logger::Error<<"Unable to read 'acl' value: "<<e.what()<<endl;
1345 retval = false;
1346 }
1347 } else {
1348 config["acl"].push_back("127.0.0.0/8");
1349 config["acl"].push_back("::1/128");
1350 }
1351
1352 if (config["work-dir"]) {
1353 try {
1354 config["work-dir"].as<string>();
1355 } catch(const runtime_error &e) {
1356 g_log<<Logger::Error<<"Unable to read 'work-dir' value: "<<e.what()<<endl;
1357 retval = false;
1358 }
1359 } else {
1360 char tmp[512];
1361 config["work-dir"] = getcwd(tmp, sizeof(tmp)) ? string(tmp) : "";;
1362 }
1363
1364 if (config["uid"]) {
1365 try {
1366 config["uid"].as<string>();
1367 } catch(const runtime_error &e) {
1368 g_log<<Logger::Error<<"Unable to read 'uid' value: "<<e.what()<<endl;
1369 retval = false;
1370 }
1371 }
1372
1373 if (config["gid"]) {
1374 try {
1375 config["gid"].as<string>();
1376 } catch(const runtime_error &e) {
1377 g_log<<Logger::Error<<"Unable to read 'gid' value: "<<e.what()<<endl;
1378 retval = false;
1379 }
1380 }
1381
1382 if (config["domains"]) {
1383 if (config["domains"].size() == 0) {
1384 g_log<<Logger::Error<<"No domains configured"<<endl;
1385 retval = false;
1386 }
1387 for (auto const &domain : config["domains"]) {
1388 try {
1389 if (!domain["domain"]) {
1390 g_log<<Logger::Error<<"An entry in 'domains' is missing a 'domain' key!"<<endl;
1391 retval = false;
1392 continue;
1393 }
1394 domain["domain"].as<DNSName>();
1395 } catch (const runtime_error &e) {
1396 g_log<<Logger::Error<<"Unable to read domain '"<<domain["domain"].as<string>()<<"': "<<e.what()<<endl;
1397 }
1398 try {
1399 if (!domain["master"]) {
d525b58b 1400 g_log << Logger::Error << "Domain '" << domain["domain"].as<string>() << "' has no primary configured!" << endl;
f2d45260
PL
1401 retval = false;
1402 continue;
1403 }
1404 domain["master"].as<ComboAddress>();
06699363
PD
1405
1406 auto notifySource = domain["master"].as<ComboAddress>();
1407
1408 g_notifySources.addMask(notifySource);
f2d45260 1409 } catch (const runtime_error &e) {
d525b58b 1410 g_log << Logger::Error << "Unable to read domain '" << domain["domain"].as<string>() << "' primary address: " << e.what() << endl;
f2d45260
PL
1411 retval = false;
1412 }
f58f871b
CHB
1413 if (domain["max-soa-refresh"]) {
1414 try {
b16aa8e4 1415 domain["max-soa-refresh"].as<uint32_t>();
f58f871b
CHB
1416 } catch (const runtime_error &e) {
1417 g_log<<Logger::Error<<"Unable to read 'max-soa-refresh' value for domain '"<<domain["domain"].as<string>()<<"': "<<e.what()<<endl;
1418 }
1419 }
f2d45260
PL
1420 }
1421 } else {
1422 g_log<<Logger::Error<<"No domains configured"<<endl;
1423 retval = false;
1424 }
1425
971e5911
RG
1426 if (config["compress"]) {
1427 try {
1428 config["compress"].as<bool>();
1429 }
1430 catch (const runtime_error &e) {
1431 g_log<<Logger::Error<<"Unable to read 'compress' value: "<<e.what()<<endl;
1432 retval = false;
1433 }
1434 }
1435 else {
1436 config["compress"] = false;
1437 }
1438
d5c9e1cb
PL
1439 if (config["webserver-address"]) {
1440 try {
1441 config["webserver-address"].as<ComboAddress>();
1442 }
1443 catch (const runtime_error &e) {
1444 g_log<<Logger::Error<<"Unable to read 'webserver-address' value: "<<e.what()<<endl;
1445 retval = false;
1446 }
1447 }
1448
9f517da5
PL
1449 if (config["webserver-acl"]) {
1450 try {
1451 config["webserver-acl"].as<vector<Netmask>>();
1452 }
1453 catch (const runtime_error &e) {
c9a19a1f 1454 g_log<<Logger::Error<<"Unable to read 'webserver-acl' value: "<<e.what()<<endl;
9f517da5
PL
1455 retval = false;
1456 }
1457 }
1458
f6149229
PL
1459 if (config["webserver-loglevel"]) {
1460 try {
1461 config["webserver-loglevel"].as<string>();
1462 }
1463 catch (const runtime_error &e) {
1464 g_log<<Logger::Error<<"Unable to read 'webserver-loglevel' value: "<<e.what()<<endl;
1465 retval = false;
1466 }
1467 }
1468
f2d45260
PL
1469 return retval;
1470}
1471
48d1f24b
RG
1472struct IXFRDistConfiguration
1473{
1474 set<int> listeningSockets;
1475 NetmaskGroup wsACL;
1476 ComboAddress wsAddr;
1477 std::string wsLogLevel{"normal"};
1478 std::string workDir;
b862ecc9 1479 const struct passwd* userInfo{nullptr};
48d1f24b
RG
1480 uint32_t axfrMaxRecords{0};
1481 uint16_t keep{0};
1482 uint16_t axfrTimeout{0};
1483 uint16_t failedSOARetry{0};
1484 uint16_t tcpInThreads{0};
1485 uid_t uid{0};
1486 gid_t gid{0};
18deed4c 1487 bool shouldExit{false};
48d1f24b
RG
1488};
1489
2a0826d3 1490// NOLINTNEXTLINE(readability-function-cognitive-complexity)
48d1f24b
RG
1491static std::optional<IXFRDistConfiguration> parseConfiguration(int argc, char** argv, FDMultiplexer& fdm)
1492{
1493 IXFRDistConfiguration configuration;
592f52eb 1494 po::variables_map g_vm;
32b355c4
RG
1495 std::string configPath;
1496
347671ac
PL
1497 try {
1498 po::options_description desc("IXFR distribution tool");
1499 desc.add_options()
1500 ("help", "produce help message")
1501 ("version", "Display the version of ixfrdist")
1502 ("verbose", "Be verbose")
1503 ("debug", "Be even more verbose")
f2d45260 1504 ("config", po::value<string>()->default_value(SYSCONFDIR + string("/ixfrdist.yml")), "Configuration file to use")
347671ac 1505 ;
347671ac 1506
f2d45260 1507 po::store(po::command_line_parser(argc, argv).options(desc).run(), g_vm);
347671ac
PL
1508 po::notify(g_vm);
1509
1510 if (g_vm.count("help") > 0) {
1511 usage(desc);
18deed4c 1512 configuration.shouldExit = true;
48d1f24b 1513 return configuration;
347671ac 1514 }
a2670f5e 1515
347671ac
PL
1516 if (g_vm.count("version") > 0) {
1517 cout<<"ixfrdist "<<VERSION<<endl;
18deed4c 1518 configuration.shouldExit = true;
48d1f24b 1519 return configuration;
347671ac 1520 }
32b355c4
RG
1521
1522 configPath = g_vm["config"].as<string>();
1523 }
1524 catch (const po::error &e) {
1361013e 1525 g_log<<Logger::Error<<e.what()<<". See `ixfrdist --help` for valid options"<<endl;
48d1f24b 1526 return std::nullopt;
aff1f1fd 1527 }
32b355c4
RG
1528 catch (const std::exception& exp) {
1529 g_log<<Logger::Error<<exp.what()<<". See `ixfrdist --help` for valid options"<<endl;
48d1f24b 1530 return std::nullopt;
32b355c4 1531 }
aff1f1fd 1532
a2670f5e
PL
1533 bool had_error = false;
1534
1361013e
PL
1535 if (g_vm.count("verbose")) {
1536 g_log.setLoglevel(Logger::Info);
1537 g_log.toConsole(Logger::Info);
f3c63e34
PL
1538 }
1539
1540 if (g_vm.count("debug") > 0) {
1361013e
PL
1541 g_log.setLoglevel(Logger::Debug);
1542 g_log.toConsole(Logger::Debug);
f3c63e34
PL
1543 }
1544
08fc00e2
PL
1545 g_log<<Logger::Notice<<"IXFR distributor version "<<VERSION<<" starting up!"<<endl;
1546
32b355c4
RG
1547 try {
1548 YAML::Node config;
1549 if (!parseAndCheckConfig(configPath, config)) {
1550 // parseAndCheckConfig already logged whatever was wrong
48d1f24b 1551 return std::nullopt;
32b355c4 1552 }
a2670f5e 1553
48d1f24b 1554 /* From hereon out, we known that all the values in config are valid. */
a2670f5e 1555
32b355c4
RG
1556 for (auto const &domain : config["domains"]) {
1557 set<ComboAddress> s;
1558 s.insert(domain["master"].as<ComboAddress>());
d525b58b 1559 g_domainConfigs[domain["domain"].as<DNSName>()].primaries = s;
48d1f24b 1560 if (domain["max-soa-refresh"].IsDefined()) {
32b355c4
RG
1561 g_domainConfigs[domain["domain"].as<DNSName>()].maxSOARefresh = domain["max-soa-refresh"].as<uint32_t>();
1562 }
146ada65
CHB
1563 if (domain["notify"].IsDefined()) {
1564 auto& listset = g_domainConfigs[domain["domain"].as<DNSName>()].notify;
1565 if (domain["notify"].IsScalar()) {
1566 auto remote = domain["notify"].as<std::string>();
1567 try {
1568 listset.emplace(remote, 53);
1569 }
1570 catch (PDNSException& e) {
1571 g_log << Logger::Error << "Unparseable IP in notify directive " << remote << ". Error: " << e.reason << endl;
1572 }
1573 } else if (domain["notify"].IsSequence()) {
1574 for (const auto& entry: domain["notify"]) {
1575 auto remote = entry.as<std::string>();
1576 try {
1577 listset.emplace(remote, 53);
1578 }
1579 catch (PDNSException& e) {
1580 g_log << Logger::Error << "Unparseable IP in notify directive " << remote << ". Error: " << e.reason << endl;
1581 }
1582 }
1583 }
1584 }
32b355c4
RG
1585 g_stats.registerDomain(domain["domain"].as<DNSName>());
1586 }
1587
1588 for (const auto &addr : config["acl"].as<vector<string>>()) {
1589 try {
1590 g_acl.addMask(addr);
1591 }
1592 catch (const std::exception& exp) {
1593 g_log<<Logger::Error<<exp.what()<<endl;
1594 had_error = true;
1595 }
1596 catch (const NetmaskException &e) {
1597 g_log<<Logger::Error<<e.reason<<endl;
1598 had_error = true;
1599 }
f58f871b 1600 }
38e6a9ee 1601
6329cf4d 1602 try {
32b355c4
RG
1603 g_log<<Logger::Notice<<"ACL set to "<<g_acl.toString()<<"."<<endl;
1604 }
1605 catch (const std::exception& exp) {
1606 g_log<<Logger::Error<<"Error printing ACL: "<<exp.what()<<endl;
6329cf4d 1607 }
6329cf4d 1608
06699363
PD
1609 g_log<<Logger::Notice<<"NOTIFY accepted from "<<g_notifySources.toString()<<"."<<endl;
1610
48d1f24b 1611 if (config["compress"].IsDefined()) {
32b355c4
RG
1612 g_compress = config["compress"].as<bool>();
1613 if (g_compress) {
1614 g_log<<Logger::Notice<<"Record compression is enabled."<<endl;
1615 }
971e5911 1616 }
971e5911 1617
32b355c4
RG
1618 for (const auto& addr : config["listen"].as<vector<ComboAddress>>()) {
1619 for (const auto& stype : {SOCK_DGRAM, SOCK_STREAM}) {
1620 try {
1621 int s = SSocket(addr.sin4.sin_family, stype, 0);
1622 setNonBlocking(s);
1623 setReuseAddr(s);
0c87a2b8
PD
1624 if (addr.isIPv6()) {
1625 int one = 1;
1626 (void)setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
1627 }
1628
32b355c4
RG
1629 SBind(s, addr);
1630 if (stype == SOCK_STREAM) {
1631 SListen(s, 30); // TODO make this configurable
1632 }
48d1f24b
RG
1633 fdm.addReadFD(s, stype == SOCK_DGRAM ? handleUDPRequest : handleTCPRequest);
1634 configuration.listeningSockets.insert(s);
32b355c4
RG
1635 }
1636 catch (const runtime_error& exp) {
1637 g_log<<Logger::Error<<exp.what()<<endl;
1638 had_error = true;
1639 continue;
1640 }
1641 catch (const PDNSException& exp) {
1642 g_log<<Logger::Error<<exp.reason<<endl;
1643 had_error = true;
1644 continue;
4f80c4e0 1645 }
4f80c4e0 1646 }
aff1f1fd 1647 }
aff1f1fd 1648
48d1f24b
RG
1649 if (config["gid"].IsDefined()) {
1650 auto gid = config["gid"].as<string>();
1651 try {
1652 configuration.gid = pdns::checked_stoi<gid_t>(gid);
1653 }
1654 catch (const std::exception& e) {
1655 g_log<<Logger::Error<<"Can not parse gid "<<gid<<endl;
1656 had_error = true;
1657 }
1658 if (configuration.gid != 0) {
1659 //NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
1660 const struct group *gr = getgrnam(gid.c_str());
32b355c4
RG
1661 if (gr == nullptr) {
1662 g_log<<Logger::Error<<"Can not determine group-id for gid "<<gid<<endl;
1663 had_error = true;
1664 } else {
48d1f24b 1665 configuration.gid = gr->gr_gid;
32b355c4
RG
1666 }
1667 }
6f8cb809 1668 }
6f8cb809 1669
48d1f24b
RG
1670 if (config["webserver-address"].IsDefined()) {
1671 configuration.wsAddr = config["webserver-address"].as<ComboAddress>();
1672
32b355c4 1673 try {
48d1f24b
RG
1674 configuration.wsACL.addMask("127.0.0.0/8");
1675 configuration.wsACL.addMask("::1/128");
32b355c4 1676
48d1f24b
RG
1677 if (config["webserver-acl"].IsDefined()) {
1678 configuration.wsACL.clear();
32b355c4 1679 for (const auto &acl : config["webserver-acl"].as<vector<Netmask>>()) {
48d1f24b 1680 configuration.wsACL.addMask(acl);
32b355c4
RG
1681 }
1682 }
1683 }
1684 catch (const NetmaskException& ne) {
1685 g_log<<Logger::Error<<"Could not set the webserver ACL: "<<ne.reason<<endl;
1686 had_error = true;
9f517da5 1687 }
f84b2bc7
RG
1688 catch (const std::exception& exp) {
1689 g_log<<Logger::Error<<"Could not set the webserver ACL: "<<exp.what()<<endl;
1690 had_error = true;
1691 }
9f517da5 1692
32b355c4 1693 if (config["webserver-loglevel"]) {
48d1f24b 1694 configuration.wsLogLevel = config["webserver-loglevel"].as<string>();
32b355c4 1695 }
48d1f24b 1696 }
f6149229 1697
48d1f24b 1698 if (config["uid"].IsDefined()) {
a03ef0a5 1699 auto uid = config["uid"].as<string>();
32b355c4 1700 try {
48d1f24b 1701 configuration.uid = pdns::checked_stoi<uid_t>(uid);
32b355c4 1702 }
48d1f24b
RG
1703 catch (const std::exception& e) {
1704 g_log<<Logger::Error<<"Can not parse uid "<<uid<<endl;
32b355c4
RG
1705 had_error = true;
1706 }
48d1f24b
RG
1707 if (configuration.uid != 0) {
1708 //NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
1709 const struct passwd *pw = getpwnam(uid.c_str());
32b355c4
RG
1710 if (pw == nullptr) {
1711 g_log<<Logger::Error<<"Can not determine user-id for uid "<<uid<<endl;
1712 had_error = true;
1713 } else {
48d1f24b 1714 configuration.uid = pw->pw_uid;
32b355c4 1715 }
b862ecc9
RG
1716 //NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point
1717 configuration.userInfo = getpwuid(configuration.uid);
32b355c4 1718 }
48d1f24b
RG
1719 }
1720
1721 configuration.workDir = config["work-dir"].as<string>();
1722 configuration.keep = config["keep"].as<uint16_t>();
1723 configuration.axfrTimeout = config["axfr-timeout"].as<uint16_t>();
1724 configuration.failedSOARetry = config["failed-soa-retry"].as<uint16_t>();
1725 configuration.axfrMaxRecords = config["axfr-max-records"].as<uint32_t>();
1726 configuration.tcpInThreads = config["tcp-in-threads"].as<uint16_t>();
1727
1728 if (had_error) {
1729 return std::nullopt;
1730 }
1731 return configuration;
1732 }
1733 catch (const YAML::Exception& exp) {
1734 had_error = true;
1735 g_log<<Logger::Error<<"Got an exception while applying our configuration: "<<exp.msg<<endl;
1736 return std::nullopt;
1737 }
1738}
1739
1740int main(int argc, char** argv) {
a03ef0a5
RG
1741 bool had_error = false;
1742 std::optional<IXFRDistConfiguration> configuration{std::nullopt};
1743 std::unique_ptr<FDMultiplexer> fdm{nullptr};
48d1f24b 1744
a03ef0a5
RG
1745 try {
1746 g_log.setLoglevel(Logger::Notice);
1747 g_log.toConsole(Logger::Notice);
1748 g_log.setPrefixed(true);
1749 g_log.disableSyslog(true);
1750 g_log.setTimestamps(false);
1751
1752 fdm = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent());
1753 if (!fdm) {
1754 g_log<<Logger::Error<<"Could not enable a multiplexer for the listen sockets!"<<endl;
1755 return EXIT_FAILURE;
1756 }
48d1f24b 1757
a03ef0a5
RG
1758 configuration = parseConfiguration(argc, argv, *fdm);
1759 if (!configuration) {
1760 // We have already sent the errors to stderr, just die
1761 return EXIT_FAILURE;
1762 }
48d1f24b 1763
18deed4c 1764 if (configuration->shouldExit) {
a03ef0a5
RG
1765 return EXIT_SUCCESS;
1766 }
48d1f24b 1767 }
a03ef0a5
RG
1768 catch (const YAML::Exception& exp) {
1769 had_error = true;
1770 g_log<<Logger::Error<<"Got an exception while processing our configuration: "<<exp.msg<<endl;
48d1f24b
RG
1771 }
1772
48d1f24b
RG
1773 try {
1774 if (configuration->gid != 0) {
1775 g_log<<Logger::Notice<<"Dropping effective group-id to "<<configuration->gid<<endl;
1776 if (setgid(configuration->gid) < 0) {
1777 g_log<<Logger::Error<<"Could not set group id to "<<configuration->gid<<": "<<stringerror()<<endl;
1778 had_error = true;
32b355c4 1779 }
48d1f24b 1780 }
6f8cb809 1781
48d1f24b
RG
1782 // It all starts here
1783 signal(SIGTERM, handleSignal);
1784 signal(SIGINT, handleSignal);
a03ef0a5 1785 //NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
48d1f24b
RG
1786 signal(SIGPIPE, SIG_IGN);
1787
1788 // Launch the webserver!
1789 try {
1790 std::thread(&IXFRDistWebServer::go, IXFRDistWebServer(configuration->wsAddr, configuration->wsACL, configuration->wsLogLevel)).detach();
1791 }
1792 catch (const std::exception& exp) {
1793 g_log<<Logger::Error<<"Unable to start webserver: "<<exp.what()<<endl;
1794 had_error = true;
1795 }
1796 catch (const PDNSException &e) {
1797 g_log<<Logger::Error<<"Unable to start webserver: "<<e.reason<<endl;
1798 had_error = true;
1799 }
6f8cb809 1800
48d1f24b
RG
1801 if (configuration->uid != 0) {
1802 g_log<<Logger::Notice<<"Dropping effective user-id to "<<configuration->uid<<endl;
1803 if (setuid(configuration->uid) < 0) {
1804 g_log<<Logger::Error<<"Could not set user id to "<<configuration->uid<<": "<<stringerror()<<endl;
1805 had_error = true;
1806 }
b862ecc9 1807 if (configuration->userInfo == nullptr) {
32b355c4
RG
1808 if (setgroups(0, nullptr) < 0) {
1809 g_log<<Logger::Error<<"Unable to drop supplementary gids: "<<stringerror()<<endl;
1810 had_error = true;
1811 }
6f8cb809 1812 } else {
b862ecc9 1813 if (initgroups(configuration->userInfo->pw_name, configuration->gid) < 0) {
32b355c4
RG
1814 g_log<<Logger::Error<<"Unable to set supplementary groups: "<<stringerror()<<endl;
1815 had_error = true;
1816 }
6f8cb809 1817 }
6f8cb809
PL
1818 }
1819
32b355c4 1820 if (had_error) {
32b355c4 1821 return EXIT_FAILURE;
6f8cb809 1822 }
a03ef0a5
RG
1823 }
1824 catch (const YAML::Exception& exp) {
1825 had_error = true;
1826 g_log<<Logger::Error<<"Got an exception while applying our configuration: "<<exp.msg<<endl;
1827 }
f2d45260 1828
a03ef0a5 1829 try {
32b355c4
RG
1830 // Init the things we need
1831 reportAllTypes();
1832
1833 std::thread ut(updateThread,
48d1f24b
RG
1834 configuration->workDir,
1835 configuration->keep,
1836 configuration->axfrTimeout,
1837 configuration->failedSOARetry,
1838 configuration->axfrMaxRecords);
146ada65 1839 std::thread communicator(communicatorThread);
32b355c4
RG
1840
1841 vector<std::thread> tcpHandlers;
48d1f24b 1842 tcpHandlers.reserve(configuration->tcpInThreads);
32b355c4
RG
1843 for (size_t i = 0; i < tcpHandlers.capacity(); ++i) {
1844 tcpHandlers.push_back(std::thread(tcpWorker, i));
1845 }
aff1f1fd 1846
32b355c4 1847 struct timeval now;
48d1f24b 1848 for (;;) {
32b355c4
RG
1849 gettimeofday(&now, 0);
1850 fdm->run(&now);
1851 if (g_exiting) {
1852 g_log<<Logger::Debug<<"Closing listening sockets"<<endl;
48d1f24b 1853 for (const int& fd : configuration->listeningSockets) {
32b355c4
RG
1854 try {
1855 closesocket(fd);
48d1f24b 1856 } catch (const PDNSException &e) {
32b355c4
RG
1857 g_log<<Logger::Error<<e.reason<<endl;
1858 }
bf676f2f 1859 }
32b355c4 1860 break;
bf676f2f 1861 }
bf676f2f 1862 }
32b355c4
RG
1863
1864 g_log<<Logger::Debug<<"Waiting for all threads to stop"<<endl;
1865 g_tcpHandlerCV.notify_all();
1866 ut.join();
146ada65 1867 communicator.join();
32b355c4
RG
1868 for (auto &t : tcpHandlers) {
1869 t.join();
1870 }
1871 g_log<<Logger::Notice<<"IXFR distributor stopped"<<endl;
aff1f1fd 1872 }
32b355c4
RG
1873 catch (const YAML::Exception& exp) {
1874 had_error = true;
a03ef0a5 1875 g_log<<Logger::Error<<"Got an exception: "<<exp.msg<<endl;
a2969595 1876 }
32b355c4
RG
1877
1878 return had_error ? EXIT_FAILURE : EXIT_SUCCESS;
a2670f5e 1879}