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