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