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