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