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