]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/rec-lua-conf.cc
cac3cc71e3347b595b6cbbeed167697afec18e16
[thirdparty/pdns.git] / pdns / rec-lua-conf.cc
1 #include "config.h"
2 #include "ext/luawrapper/include/LuaContext.hpp"
3
4 #include <fstream>
5 #include <thread>
6 #include "namespaces.hh"
7 #include "logger.hh"
8 #include "rec-lua-conf.hh"
9 #include "sortlist.hh"
10 #include "filterpo.hh"
11 #include "syncres.hh"
12 #include "rpzloader.hh"
13 #include "base64.hh"
14 #include "remote_logger.hh"
15 #include "validate.hh"
16 #include "validate-recursor.hh"
17 #include "root-dnssec.hh"
18
19 GlobalStateHolder<LuaConfigItems> g_luaconfs;
20
21 /* SO HOW DOES THIS WORK! AND PLEASE PAY ATTENTION!
22 This function can be called at any time. It is expected to overwrite all the contents
23 of LuaConfigItems, which is held in a GlobalStateHolder for RCU properties.
24
25 This function can be called again at a later date, so you must make sure that anything you
26 allow to be configured from here lives in g_luaconfs AND NOWHERE ELSE.
27
28 If someone loads an empty Lua file, the default LuaConfigItems struct MUST MAKE SENSE.
29
30 To make this easy on you, here is a LuaConfigItems constructor where you
31 can set sane defaults:
32 */
33
34 LuaConfigItems::LuaConfigItems()
35 {
36 DNSName root("."); // don't use g_rootdnsname here, it might not exist yet
37 for (const auto &dsRecord : rootDSs) {
38 auto ds=unique_ptr<DSRecordContent>(dynamic_cast<DSRecordContent*>(DSRecordContent::make(dsRecord)));
39 dsAnchors[root].insert(*ds);
40 }
41 }
42
43 /* DID YOU READ THE STORY ABOVE? */
44
45 template <typename C>
46 typename C::value_type::second_type constGet(const C& c, const std::string& name)
47 {
48 auto iter = c.find(name);
49 if(iter == c.end())
50 return 0;
51 return iter->second;
52 }
53
54
55 static void parseRPZParameters(const std::unordered_map<string,boost::variant<uint32_t, string> >& have, std::string& polName, boost::optional<DNSFilterEngine::Policy>& defpol, uint32_t& maxTTL, size_t& zoneSizeHint)
56 {
57 if(have.count("policyName")) {
58 polName = boost::get<std::string>(constGet(have, "policyName"));
59 }
60 if(have.count("defpol")) {
61 defpol=DNSFilterEngine::Policy();
62 defpol->d_kind = (DNSFilterEngine::PolicyKind)boost::get<uint32_t>(constGet(have, "defpol"));
63 defpol->d_name = std::make_shared<std::string>(polName);
64 if(defpol->d_kind == DNSFilterEngine::PolicyKind::Custom) {
65 defpol->d_custom=
66 DNSRecordContent::mastermake(QType::CNAME, 1,
67 boost::get<string>(constGet(have,"defcontent"))
68 );
69
70 if(have.count("defttl"))
71 defpol->d_ttl = static_cast<int32_t>(boost::get<uint32_t>(constGet(have, "defttl")));
72 else
73 defpol->d_ttl = -1; // get it from the zone
74 }
75 }
76 if(have.count("maxTTL")) {
77 maxTTL = boost::get<uint32_t>(constGet(have, "maxTTL"));
78 }
79 if(have.count("zoneSizeHint")) {
80 zoneSizeHint = static_cast<size_t>(boost::get<uint32_t>(constGet(have, "zoneSizeHint")));
81 }
82 }
83
84 void loadRecursorLuaConfig(const std::string& fname, luaConfigDelayedThreads& delayedThreads)
85 {
86 LuaConfigItems lci;
87
88 LuaContext Lua;
89 if(fname.empty())
90 return;
91 ifstream ifs(fname);
92 if(!ifs)
93 throw PDNSException("Cannot open file '"+fname+"': "+strerror(errno));
94
95 auto luaconfsLocal = g_luaconfs.getLocal();
96 lci.generation = luaconfsLocal->generation + 1;
97
98 Lua.writeFunction("clearSortlist", [&lci]() { lci.sortlist.clear(); });
99
100 /* we can get: "1.2.3.4"
101 {"1.2.3.4", "4.5.6.7"}
102 {"1.2.3.4", {"4.5.6.7", "8.9.10.11"}}
103 */
104
105 map<string,DNSFilterEngine::PolicyKind> pmap{
106 {"NoAction", DNSFilterEngine::PolicyKind::NoAction},
107 {"Drop", DNSFilterEngine::PolicyKind::Drop},
108 {"NXDOMAIN", DNSFilterEngine::PolicyKind::NXDOMAIN},
109 {"NODATA", DNSFilterEngine::PolicyKind::NODATA},
110 {"Truncate", DNSFilterEngine::PolicyKind::Truncate},
111 {"Custom", DNSFilterEngine::PolicyKind::Custom}
112 };
113 Lua.writeVariable("Policy", pmap);
114
115 Lua.writeFunction("rpzFile", [&lci](const string& filename, const boost::optional<std::unordered_map<string,boost::variant<uint32_t, string>>>& options) {
116 try {
117 boost::optional<DNSFilterEngine::Policy> defpol;
118 std::string polName("rpzFile");
119 std::shared_ptr<DNSFilterEngine::Zone> zone = std::make_shared<DNSFilterEngine::Zone>();
120 uint32_t maxTTL = std::numeric_limits<uint32_t>::max();
121 if(options) {
122 auto& have = *options;
123 size_t zoneSizeHint = 0;
124 parseRPZParameters(have, polName, defpol, maxTTL, zoneSizeHint);
125 if (zoneSizeHint > 0) {
126 zone->reserve(zoneSizeHint);
127 }
128 }
129 g_log<<Logger::Warning<<"Loading RPZ from file '"<<filename<<"'"<<endl;
130 zone->setName(polName);
131 loadRPZFromFile(filename, zone, defpol, maxTTL);
132 lci.dfe.addZone(zone);
133 g_log<<Logger::Warning<<"Done loading RPZ from file '"<<filename<<"'"<<endl;
134 }
135 catch(const std::exception& e) {
136 g_log<<Logger::Error<<"Unable to load RPZ zone from '"<<filename<<"': "<<e.what()<<endl;
137 }
138 });
139
140 Lua.writeFunction("rpzMaster", [&lci, &delayedThreads](const boost::variant<string, std::vector<std::pair<int, string> > >& masters_, const string& zoneName, const boost::optional<std::unordered_map<string,boost::variant<uint32_t, string>>>& options) {
141
142 boost::optional<DNSFilterEngine::Policy> defpol;
143 std::shared_ptr<DNSFilterEngine::Zone> zone = std::make_shared<DNSFilterEngine::Zone>();
144 TSIGTriplet tt;
145 uint32_t refresh=0;
146 size_t maxReceivedXFRMBytes = 0;
147 uint16_t axfrTimeout = 20;
148 uint32_t maxTTL = std::numeric_limits<uint32_t>::max();
149 ComboAddress localAddress;
150 std::vector<ComboAddress> masters;
151 if (masters_.type() == typeid(string)) {
152 masters.push_back(ComboAddress(boost::get<std::string>(masters_), 53));
153 }
154 else {
155 for (const auto& master : boost::get<std::vector<std::pair<int, std::string>>>(masters_)) {
156 masters.push_back(ComboAddress(master.second, 53));
157 }
158 }
159
160 size_t zoneIdx;
161 std::string dumpFile;
162 std::shared_ptr<SOARecordContent> sr = nullptr;
163
164 try {
165 std::string seedFile;
166 std::string polName(zoneName);
167
168 if (options) {
169 auto& have = *options;
170 size_t zoneSizeHint = 0;
171 parseRPZParameters(have, polName, defpol, maxTTL, zoneSizeHint);
172 if (zoneSizeHint > 0) {
173 zone->reserve(zoneSizeHint);
174 }
175
176 if(have.count("tsigname")) {
177 tt.name=DNSName(toLower(boost::get<string>(constGet(have, "tsigname"))));
178 tt.algo=DNSName(toLower(boost::get<string>(constGet(have, "tsigalgo"))));
179 if(B64Decode(boost::get<string>(constGet(have, "tsigsecret")), tt.secret))
180 throw std::runtime_error("TSIG secret is not valid Base-64 encoded");
181 }
182
183 if(have.count("refresh")) {
184 refresh = boost::get<uint32_t>(constGet(have,"refresh"));
185 }
186
187 if(have.count("maxReceivedMBytes")) {
188 maxReceivedXFRMBytes = static_cast<size_t>(boost::get<uint32_t>(constGet(have,"maxReceivedMBytes")));
189 }
190
191 if(have.count("localAddress")) {
192 localAddress = ComboAddress(boost::get<string>(constGet(have,"localAddress")));
193 }
194
195 if(have.count("axfrTimeout")) {
196 axfrTimeout = static_cast<uint16_t>(boost::get<uint32_t>(constGet(have, "axfrTimeout")));
197 }
198
199 if(have.count("seedFile")) {
200 seedFile = boost::get<std::string>(constGet(have, "seedFile"));
201 }
202
203 if(have.count("dumpFile")) {
204 dumpFile = boost::get<std::string>(constGet(have, "dumpFile"));
205 }
206 }
207
208 if (localAddress != ComboAddress()) {
209 // We were passed a localAddress, check if its AF matches the masters'
210 for (const auto& master : masters) {
211 if (localAddress.sin4.sin_family != master.sin4.sin_family) {
212 throw PDNSException("Master address("+master.toString()+") is not of the same Address Family as the local address ("+localAddress.toString()+").");
213 }
214 }
215 }
216
217 DNSName domain(zoneName);
218 zone->setDomain(domain);
219 zone->setName(polName);
220 zone->setRefresh(refresh);
221 zoneIdx = lci.dfe.addZone(zone);
222
223 if (!seedFile.empty()) {
224 g_log<<Logger::Info<<"Pre-loading RPZ zone "<<zoneName<<" from seed file '"<<seedFile<<"'"<<endl;
225 try {
226 sr = loadRPZFromFile(seedFile, zone, defpol, maxTTL);
227
228 if (zone->getDomain() != domain) {
229 throw PDNSException("The RPZ zone " + zoneName + " loaded from the seed file (" + zone->getDomain().toString() + ") does not match the one passed in parameter (" + domain.toString() + ")");
230 }
231
232 if (sr == nullptr) {
233 throw PDNSException("The RPZ zone " + zoneName + " loaded from the seed file (" + zone->getDomain().toString() + ") has no SOA record");
234 }
235 }
236 catch(const std::exception& e) {
237 g_log<<Logger::Warning<<"Unable to pre-load RPZ zone "<<zoneName<<" from seed file '"<<seedFile<<"': "<<e.what()<<endl;
238 }
239 }
240 }
241 catch(const std::exception& e) {
242 g_log<<Logger::Error<<"Problem configuring 'rpzMaster': "<<e.what()<<endl;
243 exit(1); // FIXME proper exit code?
244 }
245 catch(const PDNSException& e) {
246 g_log<<Logger::Error<<"Problem configuring 'rpzMaster': "<<e.reason<<endl;
247 exit(1); // FIXME proper exit code?
248 }
249
250 delayedThreads.rpzMasterThreads.push_back(std::make_tuple(masters, defpol, maxTTL, zoneIdx, tt, maxReceivedXFRMBytes, localAddress, axfrTimeout, sr, dumpFile));
251 });
252
253 typedef vector<pair<int,boost::variant<string, vector<pair<int, string> > > > > argvec_t;
254 Lua.writeFunction("addSortList",
255 [&lci](const std::string& formask_,
256 const boost::variant<string, argvec_t>& masks,
257 boost::optional<int> order_)
258 {
259 try {
260 Netmask formask(formask_);
261 int order = order_ ? (*order_) : lci.sortlist.getMaxOrder(formask)+1;
262 if(auto str = boost::get<string>(&masks))
263 lci.sortlist.addEntry(formask, Netmask(*str), order);
264 else {
265
266 auto vec = boost::get<argvec_t>(&masks);
267 for(const auto& e : *vec) {
268 if(auto s = boost::get<string>(&e.second)) {
269 lci.sortlist.addEntry(formask, Netmask(*s), order);
270 }
271 else {
272 const auto& v =boost::get<vector<pair<int, string> > >(e.second);
273 for(const auto& entry : v)
274 lci.sortlist.addEntry(formask, Netmask(entry.second), order);
275 }
276 ++order;
277 }
278 }
279 }
280 catch(std::exception& e) {
281 g_log<<Logger::Error<<"Error in addSortList: "<<e.what()<<endl;
282 }
283 });
284
285 Lua.writeFunction("addDS", [&lci](const std::string& who, const std::string& what) {
286 warnIfDNSSECDisabled("Warning: adding Trust Anchor for DNSSEC (addDS), but dnssec is set to 'off'!");
287 DNSName zone(who);
288 auto ds = unique_ptr<DSRecordContent>(dynamic_cast<DSRecordContent*>(DSRecordContent::make(what)));
289 lci.dsAnchors[zone].insert(*ds);
290 });
291
292 Lua.writeFunction("clearDS", [&lci](boost::optional<string> who) {
293 warnIfDNSSECDisabled("Warning: removing Trust Anchor for DNSSEC (clearDS), but dnssec is set to 'off'!");
294 if(who)
295 lci.dsAnchors.erase(DNSName(*who));
296 else
297 lci.dsAnchors.clear();
298 });
299
300 Lua.writeFunction("addNTA", [&lci](const std::string& who, const boost::optional<std::string> why) {
301 warnIfDNSSECDisabled("Warning: adding Negative Trust Anchor for DNSSEC (addNTA), but dnssec is set to 'off'!");
302 if(why)
303 lci.negAnchors[DNSName(who)] = static_cast<string>(*why);
304 else
305 lci.negAnchors[DNSName(who)] = "";
306 });
307
308 Lua.writeFunction("clearNTA", [&lci](boost::optional<string> who) {
309 warnIfDNSSECDisabled("Warning: removing Negative Trust Anchor for DNSSEC (clearNTA), but dnssec is set to 'off'!");
310 if(who)
311 lci.negAnchors.erase(DNSName(*who));
312 else
313 lci.negAnchors.clear();
314 });
315
316 #if HAVE_PROTOBUF
317 Lua.writeFunction("protobufServer", [&lci](const string& server_, const boost::optional<uint16_t> timeout, const boost::optional<uint64_t> maxQueuedEntries, const boost::optional<uint8_t> reconnectWaitTime, const boost::optional<uint8_t> maskV4, boost::optional<uint8_t> maskV6, boost::optional<bool> asyncConnect, boost::optional<bool> taggedOnly) {
318 try {
319 ComboAddress server(server_);
320 if (!lci.protobufExportConfig.enabled) {
321
322 lci.protobufExportConfig.enabled = true;
323
324 lci.protobufExportConfig.server = server;
325
326 if (timeout) {
327 lci.protobufExportConfig.timeout = *timeout;
328 }
329
330 if (maxQueuedEntries) {
331 lci.protobufExportConfig.maxQueuedEntries = *maxQueuedEntries;
332 }
333
334 if (reconnectWaitTime) {
335 lci.protobufExportConfig.reconnectWaitTime = *reconnectWaitTime;
336 }
337
338 if (asyncConnect) {
339 lci.protobufExportConfig.asyncConnect = *asyncConnect;
340 }
341
342 if (maskV4) {
343 lci.protobufMaskV4 = *maskV4;
344 }
345 if (maskV6) {
346 lci.protobufMaskV6 = *maskV6;
347 }
348 if (taggedOnly) {
349 lci.protobufTaggedOnly = *taggedOnly;
350 }
351 }
352 else {
353 g_log<<Logger::Error<<"Only one protobuf server can be configured, we already have "<<lci.protobufExportConfig.server.toString()<<endl;
354 }
355 }
356 catch(std::exception& e) {
357 g_log<<Logger::Error<<"Error while starting protobuf logger to '"<<server_<<": "<<e.what()<<endl;
358 }
359 catch(PDNSException& e) {
360 g_log<<Logger::Error<<"Error while starting protobuf logger to '"<<server_<<": "<<e.reason<<endl;
361 }
362 });
363
364 Lua.writeFunction("outgoingProtobufServer", [&lci](const string& server_, const boost::optional<uint16_t> timeout, const boost::optional<uint64_t> maxQueuedEntries, const boost::optional<uint8_t> reconnectWaitTime, boost::optional<bool> asyncConnect) {
365 try {
366 ComboAddress server(server_);
367 if (!lci.outgoingProtobufExportConfig.enabled) {
368
369 lci.outgoingProtobufExportConfig.enabled = true;
370
371 lci.outgoingProtobufExportConfig.server = server;
372
373 if (timeout) {
374 lci.outgoingProtobufExportConfig.timeout = *timeout;
375 }
376
377 if (maxQueuedEntries) {
378 lci.outgoingProtobufExportConfig.maxQueuedEntries = *maxQueuedEntries;
379 }
380
381 if (reconnectWaitTime) {
382 lci.outgoingProtobufExportConfig.reconnectWaitTime = *reconnectWaitTime;
383 }
384
385 if (asyncConnect) {
386 lci.outgoingProtobufExportConfig.asyncConnect = *asyncConnect;
387 }
388 }
389 else {
390 g_log<<Logger::Error<<"Only one protobuf server can be configured, we already have "<<lci.outgoingProtobufExportConfig.server.toString()<<endl;
391 }
392 }
393 catch(std::exception& e) {
394 g_log<<Logger::Error<<"Error while starting protobuf logger to '"<<server_<<": "<<e.what()<<endl;
395 }
396 catch(PDNSException& e) {
397 g_log<<Logger::Error<<"Error while starting protobuf logger to '"<<server_<<": "<<e.reason<<endl;
398 }
399 });
400 #endif
401
402 try {
403 Lua.executeCode(ifs);
404 g_luaconfs.setState(lci);
405 }
406 catch(const LuaContext::ExecutionErrorException& e) {
407 g_log<<Logger::Error<<"Unable to load Lua script from '"+fname+"': ";
408 try {
409 std::rethrow_if_nested(e);
410 } catch(const std::exception& exp) {
411 // exp is the exception that was thrown from inside the lambda
412 g_log << exp.what() << std::endl;
413 }
414 catch(const PDNSException& exp) {
415 // exp is the exception that was thrown from inside the lambda
416 g_log << exp.reason << std::endl;
417 }
418 throw;
419
420 }
421 catch(std::exception& err) {
422 g_log<<Logger::Error<<"Unable to load Lua script from '"+fname+"': "<<err.what()<<endl;
423 throw;
424 }
425
426 }
427
428 void startLuaConfigDelayedThreads(const luaConfigDelayedThreads& delayedThreads, uint64_t generation)
429 {
430 for (const auto& rpzMaster : delayedThreads.rpzMasterThreads) {
431 try {
432 std::thread t(RPZIXFRTracker, std::get<0>(rpzMaster), std::get<1>(rpzMaster), std::get<2>(rpzMaster), std::get<3>(rpzMaster), std::get<4>(rpzMaster), std::get<5>(rpzMaster) * 1024 * 1024, std::get<6>(rpzMaster), std::get<7>(rpzMaster), std::get<8>(rpzMaster), std::get<9>(rpzMaster), generation);
433 t.detach();
434 }
435 catch(const std::exception& e) {
436 g_log<<Logger::Error<<"Problem starting RPZIXFRTracker thread: "<<e.what()<<endl;
437 exit(1); // FIXME proper exit code?
438 }
439 catch(const PDNSException& e) {
440 g_log<<Logger::Error<<"Problem starting RPZIXFRTracker thread: "<<e.reason<<endl;
441 exit(1); // FIXME proper exit code?
442 }
443 }
444 }