]>
Commit | Line | Data |
---|---|---|
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 | 67 | StatBag S; |
ec70d60d | 68 | // NOLINTNEXTLINE(readability-identifier-length) |
146ada65 | 69 | AuthPacketCache PC; |
ec70d60d | 70 | // NOLINTNEXTLINE(readability-identifier-length) |
146ada65 CHB |
71 | AuthQueryCache QC; |
72 | AuthZoneCache g_zoneCache; | |
a2670f5e PL |
73 | |
74 | ArgvMap &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 |
82 | namespace YAML { | |
83 | template<> | |
84 | struct 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 | ||
103 | template<> | |
104 | struct 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 | |
123 | template<> | |
124 | struct 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 | 144 | struct 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 | ||
153 | struct 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 |
161 | struct 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 | 168 | static map<DNSName, ixfrdistdomain_t> g_domainConfigs; |
aff1f1fd | 169 | |
e2cddb03 | 170 | // Map domains and their data |
ffb1b049 | 171 | static 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 |
175 | static LockGuarded<std::set<DNSName>> g_notifiesReceived; | |
176 | ||
146ada65 CHB |
177 | // Queue of outgoing NOTIFY |
178 | static LockGuarded<NotificationQueue> g_notificationQueue; | |
179 | ||
a2969595 | 180 | // Condition variable for TCP handling |
971e5911 RG |
181 | static std::condition_variable g_tcpHandlerCV; |
182 | static std::queue<pair<int, ComboAddress>> g_tcpRequestFDs; | |
183 | static std::mutex g_tcpRequestFDsMutex; | |
a2969595 | 184 | |
a2670f5e | 185 | namespace po = boost::program_options; |
772cdea3 | 186 | |
971e5911 | 187 | static bool g_exiting = false; |
bf676f2f | 188 | |
06699363 PD |
189 | static NetmaskGroup g_acl; // networks that can QUERY us |
190 | static NetmaskGroup g_notifySources; // networks (well, IPs) that can NOTIFY us | |
971e5911 | 191 | static bool g_compress = false; |
6329cf4d | 192 | |
42d4d25b PD |
193 | static ixfrdistStats g_stats; |
194 | ||
d5c9e1cb PL |
195 | // g_stats is static, so local to this file. But the webserver needs this info |
196 | string doGetStats() { | |
197 | return g_stats.getStats(); | |
198 | } | |
199 | ||
ba779b12 | 200 | static 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 | 210 | static 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 | 216 | static bool sortSOA(uint32_t i, uint32_t j) { |
37499e32 PL |
217 | return rfc1982LessThan(i, j); |
218 | } | |
219 | ||
ba779b12 | 220 | static 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 | 261 | static 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 | 275 | static 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 */ |
291 | static std::shared_ptr<ixfrinfo_t> getCurrentZoneInfo(const DNSName& domain) | |
292 | { | |
ffb1b049 | 293 | return (*g_soas.lock())[domain]; |
023d807e RG |
294 | } |
295 | ||
296 | static 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 | 304 | static 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 |
318 | static 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 | ||
357 | static 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, ¬ificationId, 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 |
380 | static 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 | 408 | static 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 | 613 | enum class ResponseType { |
06699363 | 614 | Unknown, |
5b546566 PD |
615 | ValidQuery, |
616 | RefusedOpcode, | |
06699363 PD |
617 | RefusedQuery, |
618 | EmptyNoError | |
5b546566 PD |
619 | }; |
620 | ||
06699363 | 621 | static 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 | 660 | static 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 | */ | |
719 | static 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 | 736 | static 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 | */ | |
761 | static 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 | */ | |
776 | static 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 | 787 | static 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 |
801 | static 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 | 812 | static 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 | ||
823 | template <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 | ||
869 | static 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 | 907 | static 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 |
1011 | static 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 | 1019 | static void handleUDPRequest(int fd, boost::any& /*unused*/) |
5b546566 PD |
1020 | try |
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 |
1096 | catch(std::exception& e) { |
1097 | return; | |
1098 | } | |
1099 | ||
aff1f1fd | 1100 | |
ba779b12 | 1101 | static 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 | 1136 | static 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 | 1266 | static 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 |
1472 | struct 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 |
1491 | static 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); | |
1624 | SBind(s, addr); | |
1625 | if (stype == SOCK_STREAM) { | |
1626 | SListen(s, 30); // TODO make this configurable | |
1627 | } | |
48d1f24b RG |
1628 | fdm.addReadFD(s, stype == SOCK_DGRAM ? handleUDPRequest : handleTCPRequest); |
1629 | configuration.listeningSockets.insert(s); | |
32b355c4 RG |
1630 | } |
1631 | catch (const runtime_error& exp) { | |
1632 | g_log<<Logger::Error<<exp.what()<<endl; | |
1633 | had_error = true; | |
1634 | continue; | |
1635 | } | |
1636 | catch (const PDNSException& exp) { | |
1637 | g_log<<Logger::Error<<exp.reason<<endl; | |
1638 | had_error = true; | |
1639 | continue; | |
4f80c4e0 | 1640 | } |
4f80c4e0 | 1641 | } |
aff1f1fd | 1642 | } |
aff1f1fd | 1643 | |
48d1f24b RG |
1644 | if (config["gid"].IsDefined()) { |
1645 | auto gid = config["gid"].as<string>(); | |
1646 | try { | |
1647 | configuration.gid = pdns::checked_stoi<gid_t>(gid); | |
1648 | } | |
1649 | catch (const std::exception& e) { | |
1650 | g_log<<Logger::Error<<"Can not parse gid "<<gid<<endl; | |
1651 | had_error = true; | |
1652 | } | |
1653 | if (configuration.gid != 0) { | |
1654 | //NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point | |
1655 | const struct group *gr = getgrnam(gid.c_str()); | |
32b355c4 RG |
1656 | if (gr == nullptr) { |
1657 | g_log<<Logger::Error<<"Can not determine group-id for gid "<<gid<<endl; | |
1658 | had_error = true; | |
1659 | } else { | |
48d1f24b | 1660 | configuration.gid = gr->gr_gid; |
32b355c4 RG |
1661 | } |
1662 | } | |
6f8cb809 | 1663 | } |
6f8cb809 | 1664 | |
48d1f24b RG |
1665 | if (config["webserver-address"].IsDefined()) { |
1666 | configuration.wsAddr = config["webserver-address"].as<ComboAddress>(); | |
1667 | ||
32b355c4 | 1668 | try { |
48d1f24b RG |
1669 | configuration.wsACL.addMask("127.0.0.0/8"); |
1670 | configuration.wsACL.addMask("::1/128"); | |
32b355c4 | 1671 | |
48d1f24b RG |
1672 | if (config["webserver-acl"].IsDefined()) { |
1673 | configuration.wsACL.clear(); | |
32b355c4 | 1674 | for (const auto &acl : config["webserver-acl"].as<vector<Netmask>>()) { |
48d1f24b | 1675 | configuration.wsACL.addMask(acl); |
32b355c4 RG |
1676 | } |
1677 | } | |
1678 | } | |
1679 | catch (const NetmaskException& ne) { | |
1680 | g_log<<Logger::Error<<"Could not set the webserver ACL: "<<ne.reason<<endl; | |
1681 | had_error = true; | |
9f517da5 | 1682 | } |
f84b2bc7 RG |
1683 | catch (const std::exception& exp) { |
1684 | g_log<<Logger::Error<<"Could not set the webserver ACL: "<<exp.what()<<endl; | |
1685 | had_error = true; | |
1686 | } | |
9f517da5 | 1687 | |
32b355c4 | 1688 | if (config["webserver-loglevel"]) { |
48d1f24b | 1689 | configuration.wsLogLevel = config["webserver-loglevel"].as<string>(); |
32b355c4 | 1690 | } |
48d1f24b | 1691 | } |
f6149229 | 1692 | |
48d1f24b | 1693 | if (config["uid"].IsDefined()) { |
a03ef0a5 | 1694 | auto uid = config["uid"].as<string>(); |
32b355c4 | 1695 | try { |
48d1f24b | 1696 | configuration.uid = pdns::checked_stoi<uid_t>(uid); |
32b355c4 | 1697 | } |
48d1f24b RG |
1698 | catch (const std::exception& e) { |
1699 | g_log<<Logger::Error<<"Can not parse uid "<<uid<<endl; | |
32b355c4 RG |
1700 | had_error = true; |
1701 | } | |
48d1f24b RG |
1702 | if (configuration.uid != 0) { |
1703 | //NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point | |
1704 | const struct passwd *pw = getpwnam(uid.c_str()); | |
32b355c4 RG |
1705 | if (pw == nullptr) { |
1706 | g_log<<Logger::Error<<"Can not determine user-id for uid "<<uid<<endl; | |
1707 | had_error = true; | |
1708 | } else { | |
48d1f24b | 1709 | configuration.uid = pw->pw_uid; |
32b355c4 | 1710 | } |
b862ecc9 RG |
1711 | //NOLINTNEXTLINE(concurrency-mt-unsafe): only one thread at this point |
1712 | configuration.userInfo = getpwuid(configuration.uid); | |
32b355c4 | 1713 | } |
48d1f24b RG |
1714 | } |
1715 | ||
1716 | configuration.workDir = config["work-dir"].as<string>(); | |
1717 | configuration.keep = config["keep"].as<uint16_t>(); | |
1718 | configuration.axfrTimeout = config["axfr-timeout"].as<uint16_t>(); | |
1719 | configuration.failedSOARetry = config["failed-soa-retry"].as<uint16_t>(); | |
1720 | configuration.axfrMaxRecords = config["axfr-max-records"].as<uint32_t>(); | |
1721 | configuration.tcpInThreads = config["tcp-in-threads"].as<uint16_t>(); | |
1722 | ||
1723 | if (had_error) { | |
1724 | return std::nullopt; | |
1725 | } | |
1726 | return configuration; | |
1727 | } | |
1728 | catch (const YAML::Exception& exp) { | |
1729 | had_error = true; | |
1730 | g_log<<Logger::Error<<"Got an exception while applying our configuration: "<<exp.msg<<endl; | |
1731 | return std::nullopt; | |
1732 | } | |
1733 | } | |
1734 | ||
1735 | int main(int argc, char** argv) { | |
a03ef0a5 RG |
1736 | bool had_error = false; |
1737 | std::optional<IXFRDistConfiguration> configuration{std::nullopt}; | |
1738 | std::unique_ptr<FDMultiplexer> fdm{nullptr}; | |
48d1f24b | 1739 | |
a03ef0a5 RG |
1740 | try { |
1741 | g_log.setLoglevel(Logger::Notice); | |
1742 | g_log.toConsole(Logger::Notice); | |
1743 | g_log.setPrefixed(true); | |
1744 | g_log.disableSyslog(true); | |
1745 | g_log.setTimestamps(false); | |
1746 | ||
1747 | fdm = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent()); | |
1748 | if (!fdm) { | |
1749 | g_log<<Logger::Error<<"Could not enable a multiplexer for the listen sockets!"<<endl; | |
1750 | return EXIT_FAILURE; | |
1751 | } | |
48d1f24b | 1752 | |
a03ef0a5 RG |
1753 | configuration = parseConfiguration(argc, argv, *fdm); |
1754 | if (!configuration) { | |
1755 | // We have already sent the errors to stderr, just die | |
1756 | return EXIT_FAILURE; | |
1757 | } | |
48d1f24b | 1758 | |
18deed4c | 1759 | if (configuration->shouldExit) { |
a03ef0a5 RG |
1760 | return EXIT_SUCCESS; |
1761 | } | |
48d1f24b | 1762 | } |
a03ef0a5 RG |
1763 | catch (const YAML::Exception& exp) { |
1764 | had_error = true; | |
1765 | g_log<<Logger::Error<<"Got an exception while processing our configuration: "<<exp.msg<<endl; | |
48d1f24b RG |
1766 | } |
1767 | ||
48d1f24b RG |
1768 | try { |
1769 | if (configuration->gid != 0) { | |
1770 | g_log<<Logger::Notice<<"Dropping effective group-id to "<<configuration->gid<<endl; | |
1771 | if (setgid(configuration->gid) < 0) { | |
1772 | g_log<<Logger::Error<<"Could not set group id to "<<configuration->gid<<": "<<stringerror()<<endl; | |
1773 | had_error = true; | |
32b355c4 | 1774 | } |
48d1f24b | 1775 | } |
6f8cb809 | 1776 | |
48d1f24b RG |
1777 | // It all starts here |
1778 | signal(SIGTERM, handleSignal); | |
1779 | signal(SIGINT, handleSignal); | |
a03ef0a5 | 1780 | //NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) |
48d1f24b RG |
1781 | signal(SIGPIPE, SIG_IGN); |
1782 | ||
1783 | // Launch the webserver! | |
1784 | try { | |
1785 | std::thread(&IXFRDistWebServer::go, IXFRDistWebServer(configuration->wsAddr, configuration->wsACL, configuration->wsLogLevel)).detach(); | |
1786 | } | |
1787 | catch (const std::exception& exp) { | |
1788 | g_log<<Logger::Error<<"Unable to start webserver: "<<exp.what()<<endl; | |
1789 | had_error = true; | |
1790 | } | |
1791 | catch (const PDNSException &e) { | |
1792 | g_log<<Logger::Error<<"Unable to start webserver: "<<e.reason<<endl; | |
1793 | had_error = true; | |
1794 | } | |
6f8cb809 | 1795 | |
48d1f24b RG |
1796 | if (configuration->uid != 0) { |
1797 | g_log<<Logger::Notice<<"Dropping effective user-id to "<<configuration->uid<<endl; | |
1798 | if (setuid(configuration->uid) < 0) { | |
1799 | g_log<<Logger::Error<<"Could not set user id to "<<configuration->uid<<": "<<stringerror()<<endl; | |
1800 | had_error = true; | |
1801 | } | |
b862ecc9 | 1802 | if (configuration->userInfo == nullptr) { |
32b355c4 RG |
1803 | if (setgroups(0, nullptr) < 0) { |
1804 | g_log<<Logger::Error<<"Unable to drop supplementary gids: "<<stringerror()<<endl; | |
1805 | had_error = true; | |
1806 | } | |
6f8cb809 | 1807 | } else { |
b862ecc9 | 1808 | if (initgroups(configuration->userInfo->pw_name, configuration->gid) < 0) { |
32b355c4 RG |
1809 | g_log<<Logger::Error<<"Unable to set supplementary groups: "<<stringerror()<<endl; |
1810 | had_error = true; | |
1811 | } | |
6f8cb809 | 1812 | } |
6f8cb809 PL |
1813 | } |
1814 | ||
32b355c4 | 1815 | if (had_error) { |
32b355c4 | 1816 | return EXIT_FAILURE; |
6f8cb809 | 1817 | } |
a03ef0a5 RG |
1818 | } |
1819 | catch (const YAML::Exception& exp) { | |
1820 | had_error = true; | |
1821 | g_log<<Logger::Error<<"Got an exception while applying our configuration: "<<exp.msg<<endl; | |
1822 | } | |
f2d45260 | 1823 | |
a03ef0a5 | 1824 | try { |
32b355c4 RG |
1825 | // Init the things we need |
1826 | reportAllTypes(); | |
1827 | ||
1828 | std::thread ut(updateThread, | |
48d1f24b RG |
1829 | configuration->workDir, |
1830 | configuration->keep, | |
1831 | configuration->axfrTimeout, | |
1832 | configuration->failedSOARetry, | |
1833 | configuration->axfrMaxRecords); | |
146ada65 | 1834 | std::thread communicator(communicatorThread); |
32b355c4 RG |
1835 | |
1836 | vector<std::thread> tcpHandlers; | |
48d1f24b | 1837 | tcpHandlers.reserve(configuration->tcpInThreads); |
32b355c4 RG |
1838 | for (size_t i = 0; i < tcpHandlers.capacity(); ++i) { |
1839 | tcpHandlers.push_back(std::thread(tcpWorker, i)); | |
1840 | } | |
aff1f1fd | 1841 | |
32b355c4 | 1842 | struct timeval now; |
48d1f24b | 1843 | for (;;) { |
32b355c4 RG |
1844 | gettimeofday(&now, 0); |
1845 | fdm->run(&now); | |
1846 | if (g_exiting) { | |
1847 | g_log<<Logger::Debug<<"Closing listening sockets"<<endl; | |
48d1f24b | 1848 | for (const int& fd : configuration->listeningSockets) { |
32b355c4 RG |
1849 | try { |
1850 | closesocket(fd); | |
48d1f24b | 1851 | } catch (const PDNSException &e) { |
32b355c4 RG |
1852 | g_log<<Logger::Error<<e.reason<<endl; |
1853 | } | |
bf676f2f | 1854 | } |
32b355c4 | 1855 | break; |
bf676f2f | 1856 | } |
bf676f2f | 1857 | } |
32b355c4 RG |
1858 | |
1859 | g_log<<Logger::Debug<<"Waiting for all threads to stop"<<endl; | |
1860 | g_tcpHandlerCV.notify_all(); | |
1861 | ut.join(); | |
146ada65 | 1862 | communicator.join(); |
32b355c4 RG |
1863 | for (auto &t : tcpHandlers) { |
1864 | t.join(); | |
1865 | } | |
1866 | g_log<<Logger::Notice<<"IXFR distributor stopped"<<endl; | |
aff1f1fd | 1867 | } |
32b355c4 RG |
1868 | catch (const YAML::Exception& exp) { |
1869 | had_error = true; | |
a03ef0a5 | 1870 | g_log<<Logger::Error<<"Got an exception: "<<exp.msg<<endl; |
a2969595 | 1871 | } |
32b355c4 RG |
1872 | |
1873 | return had_error ? EXIT_FAILURE : EXIT_SUCCESS; | |
a2670f5e | 1874 | } |