]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/lua-record.cc
Merge pull request #13387 from omoerbeek/rec-b-root-servers
[thirdparty/pdns.git] / pdns / lua-record.cc
CommitLineData
2c010a9f 1#include <thread>
a6897a16 2#include <future>
a6897a16 3#include <boost/format.hpp>
4de01aaa 4#include <utility>
60f9d9a2 5#include <algorithm>
6#include <random>
192e15fc 7#include "version.hh"
b7edebf8 8#include "ext/luawrapper/include/LuaContext.hpp"
18cb84d3 9#include "lock.hh"
b7edebf8 10#include "lua-auth4.hh"
b7edebf8 11#include "sstuff.hh"
b7edebf8 12#include "minicurl.hh"
13#include "ueberbackend.hh"
8900e2e3 14#include "dnsrecords.hh"
45d4e670 15#include "dns_random.hh"
8cb70f23 16#include "auth-main.hh"
f9423419 17#include "../modules/geoipbackend/geoipinterface.hh" // only for the enum
0540d223 18
19/* to do:
25bcfaec 20 block AXFR unless TSIG, or override
69ec43c3 21
22 investigate IPv6
23
24 check the wildcard 'no cache' stuff, we may get it wrong
25
26 ponder ECS scopemask setting
27
28 ponder netmask tree from file for huge number of netmasks
25bcfaec 29
25512599 30 unify ifurlup/ifportup
69ec43c3 31 add attribute for certificate check
0540d223 32 add list of current monitors
33 expire them too?
c9d40b1f 34
35 pool of UeberBackends?
1bc56192
CHB
36
37 Pool checks ?
0540d223 38 */
39
af68014f
CHB
40extern int g_luaRecordExecLimit;
41
1bc56192
CHB
42using iplist_t = vector<pair<int, string> >;
43using wiplist_t = std::unordered_map<int, string>;
44using ipunitlist_t = vector<pair<int, iplist_t> >;
45using opts_t = std::unordered_map<string,string>;
46
b7edebf8 47class IsUpOracle
48{
49private:
b7edebf8 50 struct CheckDesc
51 {
52 ComboAddress rem;
53 string url;
54 opts_t opts;
55 bool operator<(const CheckDesc& rhs) const
56 {
25bcfaec 57 std::map<string,string> oopts, rhsoopts;
58 for(const auto& m : opts)
59 oopts[m.first]=m.second;
60 for(const auto& m : rhs.opts)
61 rhsoopts[m.first]=m.second;
1bc56192 62
0b0882f5
RP
63 return std::tuple(rem, url, oopts) <
64 std::tuple(rhs.rem, rhs.url, rhsoopts);
b7edebf8 65 }
66 };
a6897a16
CHB
67 struct CheckState
68 {
69 CheckState(time_t _lastAccess): lastAccess(_lastAccess) {}
70 /* current status */
71 std::atomic<bool> status{false};
72 /* first check ? */
73 std::atomic<bool> first{true};
74 /* last time the status was accessed */
75 std::atomic<time_t> lastAccess{0};
76 };
77
b7edebf8 78public:
d27a2d30 79 IsUpOracle()
a6897a16 80 {
d27a2d30 81 d_checkerThreadStarted.clear();
a6897a16
CHB
82 }
83 ~IsUpOracle()
84 {
a6897a16 85 }
1bc56192
CHB
86 bool isUp(const ComboAddress& remote, const opts_t& opts);
87 bool isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts);
25bcfaec 88 bool isUp(const CheckDesc& cd);
1bc56192 89
b7edebf8 90private:
a6897a16
CHB
91 void checkURL(const CheckDesc& cd, const bool status, const bool first = false)
92 {
95d0df69 93 string remstring;
a6897a16 94 try {
b69ea3b5 95 int timeout = 2;
a6897a16
CHB
96 if (cd.opts.count("timeout")) {
97 timeout = std::atoi(cd.opts.at("timeout").c_str());
98 }
99 string useragent = productName();
100 if (cd.opts.count("useragent")) {
101 useragent = cd.opts.at("useragent");
102 }
0e524ab0
CHB
103 size_t byteslimit = 0;
104 if (cd.opts.count("byteslimit")) {
105 byteslimit = static_cast<size_t>(std::atoi(cd.opts.at("byteslimit").c_str()));
106 }
a6897a16 107 MiniCurl mc(useragent);
b7edebf8 108
a6897a16 109 string content;
95d0df69
PD
110 const ComboAddress* rem = nullptr;
111 if(cd.rem.sin4.sin_family != AF_UNSPEC) {
112 rem = &cd.rem;
113 remstring = rem->toString();
114 } else {
115 remstring = "[externally checked IP]";
116 }
117
a6897a16
CHB
118 if (cd.opts.count("source")) {
119 ComboAddress src(cd.opts.at("source"));
0e524ab0 120 content=mc.getURL(cd.url, rem, &src, timeout, false, false, byteslimit);
a6897a16
CHB
121 }
122 else {
0e524ab0 123 content=mc.getURL(cd.url, rem, nullptr, timeout, false, false, byteslimit);
a6897a16
CHB
124 }
125 if (cd.opts.count("stringmatch") && content.find(cd.opts.at("stringmatch")) == string::npos) {
126 throw std::runtime_error(boost::str(boost::format("unable to match content with `%s`") % cd.opts.at("stringmatch")));
127 }
95d0df69 128
a6897a16 129 if(!status) {
95d0df69 130 g_log<<Logger::Info<<"LUA record monitoring declaring "<<remstring<<" UP for URL "<<cd.url<<"!"<<endl;
a6897a16
CHB
131 }
132 setUp(cd);
133 }
134 catch(std::exception& ne) {
135 if(status || first)
95d0df69 136 g_log<<Logger::Info<<"LUA record monitoring declaring "<<remstring<<" DOWN for URL "<<cd.url<<", error: "<<ne.what()<<endl;
a6897a16
CHB
137 setDown(cd);
138 }
139 }
140 void checkTCP(const CheckDesc& cd, const bool status, const bool first = false) {
141 try {
b69ea3b5 142 int timeout = 2;
a6897a16
CHB
143 if (cd.opts.count("timeout")) {
144 timeout = std::atoi(cd.opts.at("timeout").c_str());
145 }
146 Socket s(cd.rem.sin4.sin_family, SOCK_STREAM);
147 ComboAddress src;
148 s.setNonBlocking();
149 if (cd.opts.count("source")) {
150 src = ComboAddress(cd.opts.at("source"));
151 s.bind(src);
152 }
153 s.connect(cd.rem, timeout);
154 if (!status) {
e3982a50 155 g_log<<Logger::Info<<"Lua record monitoring declaring TCP/IP "<<cd.rem.toStringWithPort()<<" ";
a6897a16
CHB
156 if(cd.opts.count("source"))
157 g_log<<"(source "<<src.toString()<<") ";
158 g_log<<"UP!"<<endl;
159 }
160 setUp(cd);
161 }
162 catch (const NetworkError& ne) {
163 if(status || first) {
e3982a50 164 g_log<<Logger::Info<<"Lua record monitoring declaring TCP/IP "<<cd.rem.toStringWithPort()<<" DOWN: "<<ne.what()<<endl;
a6897a16
CHB
165 }
166 setDown(cd);
167 }
168 }
169 void checkThread()
b7edebf8 170 {
a6897a16
CHB
171 while (true)
172 {
173 std::chrono::system_clock::time_point checkStart = std::chrono::system_clock::now();
174 std::vector<std::future<void>> results;
175 std::vector<CheckDesc> toDelete;
176 {
bbbfdc22
RG
177 // make sure there's no insertion
178 auto statuses = d_statuses.read_lock();
179 for (auto& it: *statuses) {
a6897a16
CHB
180 auto& desc = it.first;
181 auto& state = it.second;
182
183 if (desc.url.empty()) { // TCP
184 results.push_back(std::async(std::launch::async, &IsUpOracle::checkTCP, this, desc, state->status.load(), state->first.load()));
185 } else { // URL
186 results.push_back(std::async(std::launch::async, &IsUpOracle::checkURL, this, desc, state->status.load(), state->first.load()));
187 }
188 if (std::chrono::system_clock::from_time_t(state->lastAccess) < (checkStart - std::chrono::seconds(g_luaHealthChecksExpireDelay))) {
189 toDelete.push_back(desc);
190 }
191 }
192 }
193 // we can release the lock as nothing will be deleted
194 for (auto& future: results) {
195 future.wait();
196 }
197 if (!toDelete.empty()) {
bbbfdc22 198 auto statuses = d_statuses.write_lock();
a6897a16 199 for (auto& it: toDelete) {
bbbfdc22 200 statuses->erase(it);
a6897a16
CHB
201 }
202 }
203 std::this_thread::sleep_until(checkStart + std::chrono::seconds(g_luaHealthChecksInterval));
204 }
205 }
b7edebf8 206
a6897a16 207 typedef map<CheckDesc, std::unique_ptr<CheckState>> statuses_t;
bbbfdc22 208 SharedLockGuarded<statuses_t> d_statuses;
1bc56192 209
a6897a16 210 std::unique_ptr<std::thread> d_checkerThread;
37f625b0 211 std::atomic_flag d_checkerThreadStarted;
b7edebf8 212
1bc56192 213 void setStatus(const CheckDesc& cd, bool status)
b7edebf8 214 {
bbbfdc22
RG
215 auto statuses = d_statuses.write_lock();
216 auto& state = (*statuses)[cd];
a6897a16
CHB
217 state->status = status;
218 if (state->first) {
219 state->first = false;
220 }
b7edebf8 221 }
222
1bc56192 223 void setDown(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
b7edebf8 224 {
225 CheckDesc cd{rem, url, opts};
226 setStatus(cd, false);
227 }
228
1bc56192 229 void setUp(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
b7edebf8 230 {
231 CheckDesc cd{rem, url, opts};
1bc56192 232
b7edebf8 233 setStatus(cd, true);
234 }
235
236 void setDown(const CheckDesc& cd)
237 {
238 setStatus(cd, false);
239 }
240
241 void setUp(const CheckDesc& cd)
242 {
243 setStatus(cd, true);
244 }
b7edebf8 245};
246
25bcfaec 247bool IsUpOracle::isUp(const CheckDesc& cd)
b7edebf8 248{
37f625b0 249 if (!d_checkerThreadStarted.test_and_set()) {
2bbc9eb0 250 d_checkerThread = std::make_unique<std::thread>([this] { return checkThread(); });
b7edebf8 251 }
a6897a16
CHB
252 time_t now = time(nullptr);
253 {
bbbfdc22
RG
254 auto statuses = d_statuses.read_lock();
255 auto iter = statuses->find(cd);
256 if (iter != statuses->end()) {
a6897a16
CHB
257 iter->second->lastAccess = now;
258 return iter->second->status;
259 }
260 }
e3982a50
CHB
261 // try to parse options so we don't insert any malformed content
262 if (cd.opts.count("source")) {
263 ComboAddress src(cd.opts.at("source"));
264 }
a6897a16 265 {
bbbfdc22 266 auto statuses = d_statuses.write_lock();
a6897a16 267 // Make sure we don't insert new entry twice now we have the lock
bbbfdc22 268 if (statuses->find(cd) == statuses->end()) {
2bbc9eb0 269 (*statuses)[cd] = std::make_unique<CheckState>(now);
a6897a16
CHB
270 }
271 }
272 return false;
25bcfaec 273}
274
1bc56192 275bool IsUpOracle::isUp(const ComboAddress& remote, const opts_t& opts)
25bcfaec 276{
277 CheckDesc cd{remote, "", opts};
278 return isUp(cd);
b7edebf8 279}
280
1bc56192 281bool IsUpOracle::isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts)
b7edebf8 282{
283 CheckDesc cd{remote, url, opts};
a6897a16 284 return isUp(cd);
b7edebf8 285}
286
7d5bf0bb 287IsUpOracle g_up;
b7edebf8 288namespace {
289template<typename T, typename C>
290bool doCompare(const T& var, const std::string& res, const C& cmp)
291{
1bc56192 292 if(auto country = boost::get<string>(&var))
b7edebf8 293 return cmp(*country, res);
294
295 auto countries=boost::get<vector<pair<int,string> > >(&var);
296 for(const auto& country : *countries) {
297 if(cmp(country.second, res))
298 return true;
299 }
300 return false;
301}
302}
4a913a28 303
050e6877 304static std::string getGeo(const std::string& ip, GeoIPInterface::GeoIPQueryAttribute qa)
b7edebf8 305{
03be9ab5 306 static bool initialized;
25bcfaec 307 extern std::function<std::string(const std::string& ip, int)> g_getGeo;
924341d2 308 if(!g_getGeo) {
309 if(!initialized) {
f5f3d7f5 310 g_log<<Logger::Error<<"LUA Record attempted to use GeoIPBackend functionality, but backend not launched"<<endl;
924341d2 311 initialized=true;
312 }
313 return "unknown";
314 }
315 else
25bcfaec 316 return g_getGeo(ip, (int)qa);
4a913a28 317}
318
f52e47eb
PG
319template <typename T>
320static T pickRandom(const vector<T>& items)
4a913a28 321{
60f9d9a2 322 if (items.empty()) {
323 throw std::invalid_argument("The items list cannot be empty");
6d7e1fd3 324 }
60f9d9a2 325 return items[dns_random(items.size())];
4a913a28 326}
327
f52e47eb
PG
328template <typename T>
329static T pickHashed(const ComboAddress& who, const vector<T>& items)
60f9d9a2 330{
331 if (items.empty()) {
332 throw std::invalid_argument("The items list cannot be empty");
333 }
334 ComboAddress::addressOnlyHash aoh;
335 return items[aoh(who) % items.size()];
336}
4a913a28 337
f52e47eb
PG
338template <typename T>
339static T pickWeightedRandom(const vector< pair<int, T> >& items)
975f0839 340{
60f9d9a2 341 if (items.empty()) {
342 throw std::invalid_argument("The items list cannot be empty");
6d7e1fd3 343 }
975f0839 344 int sum=0;
f52e47eb 345 vector< pair<int, T> > pick;
60f9d9a2 346 pick.reserve(items.size());
347
348 for(auto& i : items) {
975f0839 349 sum += i.first;
e32a8d46 350 pick.emplace_back(sum, i.second);
975f0839 351 }
bb85386d 352
60f9d9a2 353 if (sum == 0) {
f52e47eb 354 throw std::invalid_argument("The sum of items cannot be zero");
6d7e1fd3 355 }
bb85386d 356
60f9d9a2 357 int r = dns_random(sum);
f52e47eb 358 auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
60f9d9a2 359 return p->second;
360}
361
f52e47eb
PG
362template <typename T>
363static T pickWeightedHashed(const ComboAddress& bestwho, vector< pair<int, T> >& items)
60f9d9a2 364{
365 if (items.empty()) {
366 throw std::invalid_argument("The items list cannot be empty");
367 }
975f0839 368 int sum=0;
f52e47eb 369 vector< pair<int, T> > pick;
60f9d9a2 370 pick.reserve(items.size());
371
372 for(auto& i : items) {
975f0839 373 sum += i.first;
374 pick.push_back({sum, i.second});
375 }
bb85386d 376
60f9d9a2 377 if (sum == 0) {
f52e47eb 378 throw std::invalid_argument("The sum of items cannot be zero");
c7cd91db 379 }
60f9d9a2 380
975f0839 381 ComboAddress::addressOnlyHash aoh;
382 int r = aoh(bestwho) % sum;
f52e47eb 383 auto p = upper_bound(pick.begin(), pick.end(), r, [](int rarg, const typename decltype(pick)::value_type& a) { return rarg < a.first; });
975f0839 384 return p->second;
385}
386
f52e47eb 387template <typename T>
bb85386d 388static vector<T> pickRandomSample(int n, const vector<T>& items)
60f9d9a2 389{
390 if (items.empty()) {
391 throw std::invalid_argument("The items list cannot be empty");
392 }
bb85386d 393
f52e47eb 394 vector<T> pick;
60f9d9a2 395 pick.reserve(items.size());
bb85386d 396
60f9d9a2 397 for(auto& item : items) {
398 pick.push_back(item);
399 }
bb85386d 400
60f9d9a2 401 int count = std::min(std::max<size_t>(0, n), items.size());
402
403 if (count == 0) {
f52e47eb 404 return vector<T>();
bb85386d 405 }
60f9d9a2 406
37b5a2a0 407 std::shuffle(pick.begin(), pick.end(), pdns::dns_random_engine());
bb85386d 408
f52e47eb 409 vector<T> result = {pick.begin(), pick.begin() + count};
60f9d9a2 410 return result;
411}
412
b7edebf8 413static bool getLatLon(const std::string& ip, double& lat, double& lon)
414{
f9423419 415 string inp = getGeo(ip, GeoIPInterface::Location);
b7edebf8 416 if(inp.empty())
417 return false;
418 lat=atof(inp.c_str());
419 auto pos=inp.find(' ');
420 if(pos != string::npos)
421 lon=atof(inp.c_str() + pos);
422 return true;
423}
424
0540d223 425static bool getLatLon(const std::string& ip, string& loc)
426{
427 int latdeg, latmin, londeg, lonmin;
428 double latsec, lonsec;
429 char lathem='X', lonhem='X';
1bc56192 430
b17434c6 431 double lat = 0, lon = 0;
0540d223 432 if(!getLatLon(ip, lat, lon))
433 return false;
434
435 if(lat > 0) {
436 lathem='N';
437 }
438 else {
439 lat = -lat;
440 lathem='S';
441 }
442
443 if(lon > 0) {
444 lonhem='E';
445 }
446 else {
447 lon = -lon;
448 lonhem='W';
449 }
450
0540d223 451 latdeg = lat;
452 latmin = (lat - latdeg)*60.0;
453 latsec = (((lat - latdeg)*60.0) - latmin)*60.0;
454
455 londeg = lon;
456 lonmin = (lon - londeg)*60.0;
457 lonsec = (((lon - londeg)*60.0) - lonmin)*60.0;
458
459 // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
460
461 boost::format fmt("%d %d %d %c %d %d %d %c 0.00m 1.00m 10000.00m 10.00m");
462
463 loc= (fmt % latdeg % latmin % latsec % lathem % londeg % lonmin % lonsec % lonhem ).str();
464 return true;
465}
975f0839 466
1bc56192 467static ComboAddress pickclosest(const ComboAddress& bestwho, const vector<ComboAddress>& wips)
975f0839 468{
6d7e1fd3
CHB
469 if (wips.empty()) {
470 throw std::invalid_argument("The IP list cannot be empty");
471 }
60f9d9a2 472 map<double, vector<ComboAddress> > ranked;
975f0839 473 double wlat=0, wlon=0;
474 getLatLon(bestwho.toString(), wlat, wlon);
475 // cout<<"bestwho "<<wlat<<", "<<wlon<<endl;
476 vector<string> ret;
477 for(const auto& c : wips) {
478 double lat=0, lon=0;
479 getLatLon(c.toString(), lat, lon);
480 // cout<<c.toString()<<": "<<lat<<", "<<lon<<endl;
481 double latdiff = wlat-lat;
482 double londiff = wlon-lon;
483 if(londiff > 180)
1bc56192 484 londiff = 360 - londiff;
975f0839 485 double dist2=latdiff*latdiff + londiff*londiff;
486 // cout<<" distance: "<<sqrt(dist2) * 40000.0/360<<" km"<<endl; // length of a degree
487 ranked[dist2].push_back(c);
488 }
45d4e670 489 return ranked.begin()->second[dns_random(ranked.begin()->second.size())];
975f0839 490}
491
c9d40b1f 492static std::vector<DNSZoneRecord> lookup(const DNSName& name, uint16_t qtype, int zoneid)
493{
18cb84d3
RG
494 static LockGuarded<UeberBackend> s_ub;
495
c9d40b1f 496 DNSZoneRecord dr;
497 vector<DNSZoneRecord> ret;
18cb84d3
RG
498 {
499 auto ub = s_ub.lock();
500 ub->lookup(QType(qtype), name, zoneid);
501 while (ub->get(dr)) {
502 ret.push_back(dr);
503 }
c9d40b1f 504 }
505 return ret;
506}
975f0839 507
2fe7bbf8 508static std::string getOptionValue(const boost::optional<std::unordered_map<string, string>>& options, const std::string &name, const std::string &defaultValue)
5ca4872a 509{
2fe7bbf8 510 string selector=defaultValue;
5ca4872a 511 if(options) {
2fe7bbf8
OV
512 if(options->count(name))
513 selector=options->find(name)->second;
5ca4872a 514 }
2fe7bbf8
OV
515 return selector;
516}
517
518static vector<ComboAddress> useSelector(const std::string &selector, const ComboAddress& bestwho, const vector<ComboAddress>& candidates)
519{
520 vector<ComboAddress> ret;
5ca4872a 521
707cedf3 522 if(selector=="all")
2fe7bbf8
OV
523 return candidates;
524 else if(selector=="random")
f52e47eb 525 ret.emplace_back(pickRandom<ComboAddress>(candidates));
1bc56192 526 else if(selector=="pickclosest")
2fe7bbf8 527 ret.emplace_back(pickclosest(bestwho, candidates));
5ca4872a 528 else if(selector=="hashed")
f52e47eb 529 ret.emplace_back(pickHashed<ComboAddress>(bestwho, candidates));
2fe7bbf8
OV
530 else {
531 g_log<<Logger::Warning<<"LUA Record called with unknown selector '"<<selector<<"'"<<endl;
f52e47eb 532 ret.emplace_back(pickRandom<ComboAddress>(candidates));
2fe7bbf8
OV
533 }
534
535 return ret;
536}
5ca4872a 537
60f9d9a2 538static vector<string> convComboAddressListToString(const vector<ComboAddress>& items)
2fe7bbf8 539{
60f9d9a2 540 vector<string> result;
541 result.reserve(items.size());
c9768b80 542
60f9d9a2 543 for (const auto& item : items) {
544 result.emplace_back(item.toString());
c9768b80
CHB
545 }
546
60f9d9a2 547 return result;
5ca4872a 548}
549
6c8714d2 550static vector<ComboAddress> convComboAddressList(const iplist_t& items, uint16_t port=0)
1bc56192 551{
60f9d9a2 552 vector<ComboAddress> result;
553 result.reserve(items.size());
1bc56192 554
60f9d9a2 555 for(const auto& item : items) {
6c8714d2 556 result.emplace_back(ComboAddress(item.second, port));
c9768b80 557 }
1bc56192 558
60f9d9a2 559 return result;
1bc56192
CHB
560}
561
04c53783
CHB
562/**
563 * Reads and unify single or multiple sets of ips :
564 * - {'192.0.2.1', '192.0.2.2'}
565 * - {{'192.0.2.1', '192.0.2.2'}, {'198.51.100.1'}}
566 */
567
568static vector<vector<ComboAddress>> convMultiComboAddressList(const boost::variant<iplist_t, ipunitlist_t>& items, uint16_t port = 0)
569{
570 vector<vector<ComboAddress>> candidates;
571
572 if(auto simple = boost::get<iplist_t>(&items)) {
573 vector<ComboAddress> unit = convComboAddressList(*simple, port);
574 candidates.push_back(unit);
575 } else {
576 auto units = boost::get<ipunitlist_t>(items);
577 for(const auto& u : units) {
578 vector<ComboAddress> unit = convComboAddressList(u.second, port);
579 candidates.push_back(unit);
580 }
581 }
582 return candidates;
583}
584
60f9d9a2 585static vector<string> convStringList(const iplist_t& items)
1bc56192 586{
60f9d9a2 587 vector<string> result;
588 result.reserve(items.size());
1bc56192 589
60f9d9a2 590 for(const auto& item : items) {
591 result.emplace_back(item.second);
c9768b80 592 }
1bc56192 593
60f9d9a2 594 return result;
595}
596
60f9d9a2 597static vector< pair<int, string> > convIntStringPairList(const std::unordered_map<int, wiplist_t >& items)
598{
599 vector<pair<int,string> > result;
600 result.reserve(items.size());
601
602 for(const auto& item : items) {
603 result.emplace_back(atoi(item.second.at(1).c_str()), item.second.at(2));
604 }
605
606 return result;
1bc56192
CHB
607}
608
32829819 609bool g_LuaRecordSharedState;
fd1bdfb3 610
c13af350 611typedef struct AuthLuaRecordContext
b7edebf8 612{
c13af350
CHB
613 ComboAddress bestwho;
614 DNSName qname;
615 DNSName zone;
616 int zoneid;
617} lua_record_ctx_t;
1bc56192 618
c13af350 619static thread_local unique_ptr<lua_record_ctx_t> s_lua_record_ctx;
1bc56192 620
04c53783
CHB
621static vector<string> genericIfUp(const boost::variant<iplist_t, ipunitlist_t>& ips, boost::optional<opts_t> options, std::function<bool(const ComboAddress&, const opts_t&)> upcheckf, uint16_t port = 0) {
622 vector<vector<ComboAddress> > candidates;
623 opts_t opts;
624 if(options)
625 opts = *options;
626
627 candidates = convMultiComboAddressList(ips, port);
628
629 for(const auto& unit : candidates) {
630 vector<ComboAddress> available;
631 for(const auto& c : unit) {
632 if (upcheckf(c, opts)) {
633 available.push_back(c);
634 }
635 }
636 if(!available.empty()) {
637 vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
638 return convComboAddressListToString(res);
639 }
640 }
641
642 // All units down, apply backupSelector on all candidates
643 vector<ComboAddress> ret{};
644 for(const auto& unit : candidates) {
645 ret.insert(ret.end(), unit.begin(), unit.end());
646 }
647
648 vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, ret);
649 return convComboAddressListToString(res);
650}
651
d43d0b93 652static void setupLuaRecords(LuaContext& lua)
c13af350 653{
c13af350 654 lua.writeFunction("latlon", []() {
b17434c6 655 double lat = 0, lon = 0;
c13af350 656 getLatLon(s_lua_record_ctx->bestwho.toString(), lat, lon);
0540d223 657 return std::to_string(lat)+" "+std::to_string(lon);
658 });
c13af350 659 lua.writeFunction("latlonloc", []() {
0540d223 660 string loc;
c13af350 661 getLatLon(s_lua_record_ctx->bestwho.toString(), loc);
0540d223 662 return loc;
b7edebf8 663 });
c13af350 664 lua.writeFunction("closestMagic", []() {
b7edebf8 665 vector<ComboAddress> candidates;
cc5c4f6b 666 // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org
c13af350 667 for(auto l : s_lua_record_ctx->qname.getRawLabels()) {
b7edebf8 668 boost::replace_all(l, "-", ".");
b7edebf8 669 try {
670 candidates.emplace_back(l);
6d7e1fd3 671 } catch (const PDNSException& e) {
cc5c4f6b
CHB
672 // no need to continue as we most likely reached the end of the ip list
673 break ;
b7edebf8 674 }
675 }
c13af350 676 return pickclosest(s_lua_record_ctx->bestwho, candidates).toString();
b7edebf8 677 });
c13af350
CHB
678 lua.writeFunction("latlonMagic", [](){
679 auto labels= s_lua_record_ctx->qname.getRawLabels();
7fedb52a 680 if(labels.size()<4)
681 return std::string("unknown");
b17434c6 682 double lat = 0, lon = 0;
7fedb52a 683 getLatLon(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], lat, lon);
684 return std::to_string(lat)+" "+std::to_string(lon);
685 });
c9d40b1f 686
1bc56192 687
54c20d39 688 lua.writeFunction("createReverse", [](string format, boost::optional<std::unordered_map<string,string>> e){
c9d40b1f 689 try {
c13af350
CHB
690 auto labels = s_lua_record_ctx->qname.getRawLabels();
691 if(labels.size()<4)
692 return std::string("unknown");
bb85386d 693
c13af350 694 vector<ComboAddress> candidates;
bb85386d 695
c13af350 696 // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa
223bfcad 697 // e["1.2.3.4"]="bert.powerdns.com" then provides an exception
c13af350
CHB
698 if(e) {
699 ComboAddress req(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], 0);
700 const auto& uom = *e;
701 for(const auto& c : uom)
702 if(ComboAddress(c.first, 0) == req)
703 return c.second;
704 }
54c20d39 705 boost::format fmt(format);
c13af350
CHB
706 fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit ) );
707 fmt % labels[3] % labels[2] % labels[1] % labels[0];
bb85386d 708
c13af350 709 fmt % (labels[3]+"-"+labels[2]+"-"+labels[1]+"-"+labels[0]);
c9d40b1f 710
c13af350
CHB
711 boost::format fmt2("%02x%02x%02x%02x");
712 for(int i=3; i>=0; --i)
713 fmt2 % atoi(labels[i].c_str());
c9d40b1f 714
c13af350 715 fmt % (fmt2.str());
c9d40b1f 716
c13af350 717 return fmt.str();
c9d40b1f 718 }
af6bf47c
OM
719 catch(std::exception& ex) {
720 g_log<<Logger::Error<<"error: "<<ex.what()<<endl;
c9d40b1f 721 }
722 return std::string("error");
723 });
c13af350 724 lua.writeFunction("createForward", []() {
448e7a2d 725 static string allZerosIP("0.0.0.0");
c13af350 726 DNSName rel=s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone);
448e7a2d
PL
727 // parts is something like ["1", "2", "3", "4", "static"] or
728 // ["1", "2", "3", "4"] or ["ip40414243", "ip-addresses", ...]
c9d40b1f 729 auto parts = rel.getRawLabels();
448e7a2d
PL
730 // Yes, this still breaks if an 1-2-3-4.XXXX is nested too deeply...
731 if(parts.size()>=4) {
732 try {
733 ComboAddress ca(parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3]);
734 return ca.toString();
735 } catch (const PDNSException &e) {
736 return allZerosIP;
737 }
738 } else if (parts.size() >= 1) {
c9d40b1f 739 // either hex string, or 12-13-14-15
448e7a2d
PL
740 vector<string> ip_parts;
741 stringtok(ip_parts, parts[0], "-");
df6ad4ee 742 unsigned int x1, x2, x3, x4;
448e7a2d
PL
743 if (ip_parts.size() >= 4) {
744 // 1-2-3-4 with any prefix (e.g. ip-foo-bar-1-2-3-4)
745 string ret;
746 for (size_t n=4; n > 0; n--) {
747 auto octet = ip_parts[ip_parts.size() - n];
748 try {
749 auto octetVal = std::stol(octet);
750 if (octetVal >= 0 && octetVal <= 255) {
751 ret += ip_parts.at(ip_parts.size() - n) + ".";
752 } else {
753 return allZerosIP;
754 }
755 } catch (const std::exception &e) {
756 return allZerosIP;
757 }
758 }
759 ret.resize(ret.size() - 1); // remove trailing dot after last octet
760 return ret;
9af5ff11 761 } else if(parts[0].length() >= 8 && sscanf(parts[0].c_str()+(parts[0].length()-8), "%02x%02x%02x%02x", &x1, &x2, &x3, &x4)==4) {
c9d40b1f 762 return std::to_string(x1)+"."+std::to_string(x2)+"."+std::to_string(x3)+"."+std::to_string(x4);
763 }
c9d40b1f 764 }
448e7a2d 765 return allZerosIP;
c9d40b1f 766 });
767
c13af350
CHB
768 lua.writeFunction("createForward6", []() {
769 DNSName rel=s_lua_record_ctx->qname.makeRelative(s_lua_record_ctx->zone);
c9d40b1f 770 auto parts = rel.getRawLabels();
771 if(parts.size()==8) {
772 string tot;
773 for(int i=0; i<8; ++i) {
774 if(i)
775 tot.append(1,':');
776 tot+=parts[i];
777 }
778 ComboAddress ca(tot);
779 return ca.toString();
780 }
781 else if(parts.size()==1) {
ada6063a
PD
782 if (parts[0].find('-') != std::string::npos) {
783 boost::replace_all(parts[0],"-",":");
784 ComboAddress ca(parts[0]);
785 return ca.toString();
786 } else {
787 if (parts[0].size() >= 32) {
788 auto ippart = parts[0].substr(parts[0].size()-32);
789 auto fulladdress =
790 ippart.substr(0, 4) + ":" +
791 ippart.substr(4, 4) + ":" +
792 ippart.substr(8, 4) + ":" +
793 ippart.substr(12, 4) + ":" +
794 ippart.substr(16, 4) + ":" +
795 ippart.substr(20, 4) + ":" +
796 ippart.substr(24, 4) + ":" +
797 ippart.substr(28, 4);
798
799 ComboAddress ca(fulladdress);
800 return ca.toString();
801 }
802 }
c9d40b1f 803 }
804
805 return std::string("::");
806 });
54c20d39 807 lua.writeFunction("createReverse6", [](string format, boost::optional<std::unordered_map<string,string>> e){
c9d40b1f 808 vector<ComboAddress> candidates;
809
810 try {
c13af350 811 auto labels= s_lua_record_ctx->qname.getRawLabels();
c9d40b1f 812 if(labels.size()<32)
813 return std::string("unknown");
54c20d39 814 boost::format fmt(format);
c9d40b1f 815 fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit ) );
1bc56192 816
c9d40b1f 817
818 string together;
819 vector<string> quads;
820 for(int i=0; i<8; ++i) {
821 if(i)
822 together+=":";
a3739f30 823 string lquad;
c9d40b1f 824 for(int j=0; j <4; ++j) {
a3739f30 825 lquad.append(1, labels[31-i*4-j][0]);
c9d40b1f 826 together += labels[31-i*4-j][0];
827 }
a3739f30 828 quads.push_back(lquad);
c9d40b1f 829 }
830 ComboAddress ip6(together,0);
831
832 if(e) {
833 auto& addrs=*e;
834 for(const auto& addr: addrs) {
835 // this makes sure we catch all forms of the address
836 if(ComboAddress(addr.first,0)==ip6)
837 return addr.second;
838 }
839 }
1bc56192 840
c9d40b1f 841 string dashed=ip6.toString();
842 boost::replace_all(dashed, ":", "-");
1bc56192 843
c9d40b1f 844 for(int i=31; i>=0; --i)
845 fmt % labels[i];
846 fmt % dashed;
847
a3739f30
OM
848 for(const auto& lquad : quads)
849 fmt % lquad;
1bc56192 850
c9d40b1f 851 return fmt.str();
852 }
af6bf47c 853 catch(std::exception& ex) {
e7929442 854 g_log<<Logger::Error<<"LUA Record exception: "<<ex.what()<<endl;
c9d40b1f 855 }
af6bf47c
OM
856 catch(PDNSException& ex) {
857 g_log<<Logger::Error<<"LUA Record exception: "<<ex.reason<<endl;
c9d40b1f 858 }
859 return std::string("unknown");
860 });
861
223bfcad
PD
862 lua.writeFunction("filterForward", [](string address, NetmaskGroup& nmg, boost::optional<string> fallback) {
863 ComboAddress ca(address);
864
865 if (nmg.match(ComboAddress(address))) {
866 return address;
867 } else {
868 if (fallback) {
869 return *fallback;
870 }
871
872 if (ca.isIPv4()) {
873 return string("0.0.0.0");
874 } else {
875 return string("::");
876 }
877 }
878 });
879
1bc56192
CHB
880 /*
881 * Simplistic test to see if an IP address listens on a certain port
882 * Will return a single IP address from the set of available IP addresses. If
883 * no IP address is available, will return a random element of the set of
ef2ea4bf 884 * addresses supplied for testing.
1bc56192
CHB
885 *
886 * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
887 */
6c8714d2 888 lua.writeFunction("ifportup", [](int port, const boost::variant<iplist_t, ipunitlist_t>& ips, const boost::optional<std::unordered_map<string,string>> options) {
6c8714d2
CHB
889 if (port < 0) {
890 port = 0;
891 }
892 if (port > std::numeric_limits<uint16_t>::max()) {
893 port = std::numeric_limits<uint16_t>::max();
894 }
6c8714d2 895
04c53783
CHB
896 auto checker = [](const ComboAddress& addr, const opts_t& opts) {
897 return g_up.isUp(addr, opts);
898 };
de5fb851 899 return genericIfUp(ips, std::move(options), checker, port);
1bc56192 900 });
b7edebf8 901
95d0df69
PD
902 lua.writeFunction("ifurlextup", [](const vector<pair<int, opts_t> >& ipurls, boost::optional<opts_t> options) {
903 vector<ComboAddress> candidates;
904 opts_t opts;
905 if(options)
906 opts = *options;
907
908 ComboAddress ca_unspec;
909 ca_unspec.sin4.sin_family=AF_UNSPEC;
910
911 // ipurls: { { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" } }
912 for (const auto& [count, unitmap] : ipurls) {
913 // unitmap: 1 = { ["192.0.2.1"] = "https://example.com", ["192.0.2.2"] = "https://example.com/404" }
914 vector<ComboAddress> available;
915
916 for (const auto& [ipStr, url] : unitmap) {
917 // unit: ["192.0.2.1"] = "https://example.com"
918 ComboAddress ip(ipStr);
919 candidates.push_back(ip);
920 if (g_up.isUp(ca_unspec, url, opts)) {
921 available.push_back(ip);
922 }
923 }
924 if(!available.empty()) {
925 vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), s_lua_record_ctx->bestwho, available);
60f9d9a2 926 return convComboAddressListToString(res);
95d0df69
PD
927 }
928 }
929
930 // All units down, apply backupSelector on all candidates
931 vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), s_lua_record_ctx->bestwho, candidates);
60f9d9a2 932 return convComboAddressListToString(res);
95d0df69
PD
933 });
934
c13af350 935 lua.writeFunction("ifurlup", [](const std::string& url,
1bc56192
CHB
936 const boost::variant<iplist_t, ipunitlist_t>& ips,
937 boost::optional<opts_t> options) {
b7edebf8 938
04c53783
CHB
939 auto checker = [&url](const ComboAddress& addr, const opts_t& opts) {
940 return g_up.isUp(addr, url, opts);
941 };
942 return genericIfUp(ips, options, checker);
2fe7bbf8 943 });
1bc56192
CHB
944 /*
945 * Returns a random IP address from the supplied list
946 * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
947 */
948 lua.writeFunction("pickrandom", [](const iplist_t& ips) {
60f9d9a2 949 vector<string> items = convStringList(ips);
f52e47eb 950 return pickRandom<string>(items);
b7edebf8 951 });
952
60f9d9a2 953 lua.writeFunction("pickrandomsample", [](int n, const iplist_t& ips) {
954 vector<string> items = convStringList(ips);
f52e47eb 955 return pickRandomSample<string>(n, items);
60f9d9a2 956 });
b7edebf8 957
60f9d9a2 958 lua.writeFunction("pickhashed", [](const iplist_t& ips) {
959 vector<string> items = convStringList(ips);
f52e47eb 960 return pickHashed<string>(s_lua_record_ctx->bestwho, items);
60f9d9a2 961 });
1bc56192
CHB
962 /*
963 * Returns a random IP address from the supplied list, as weighted by the
964 * various ``weight`` parameters
965 * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
966 */
967 lua.writeFunction("pickwrandom", [](std::unordered_map<int, wiplist_t> ips) {
60f9d9a2 968 vector< pair<int, string> > items = convIntStringPairList(ips);
f52e47eb 969 return pickWeightedRandom<string>(items);
975f0839 970 });
971
1bc56192
CHB
972 /*
973 * Based on the hash of `bestwho`, returns an IP address from the list
974 * supplied, as weighted by the various `weight` parameters
975 * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
976 */
c13af350 977 lua.writeFunction("pickwhashed", [](std::unordered_map<int, wiplist_t > ips) {
60f9d9a2 978 vector< pair<int, string> > items;
1bc56192 979
60f9d9a2 980 items.reserve(ips.size());
1bc56192 981 for(auto& i : ips)
60f9d9a2 982 items.emplace_back(atoi(i.second[1].c_str()), i.second[2]);
1bc56192 983
f52e47eb 984 return pickWeightedHashed<string>(s_lua_record_ctx->bestwho, items);
975f0839 985 });
986
987
c13af350 988 lua.writeFunction("pickclosest", [](const iplist_t& ips) {
60f9d9a2 989 vector<ComboAddress> conv = convComboAddressList(ips);
1bc56192 990
c13af350 991 return pickclosest(s_lua_record_ctx->bestwho, conv).toString();
1bc56192 992
b7edebf8 993 });
994
c13af350
CHB
995 if (g_luaRecordExecLimit > 0) {
996 lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
997 }
1bc56192 998
d73de874 999 lua.writeFunction("report", [](string /* event */, boost::optional<string> /* line */){
b7edebf8 1000 throw std::runtime_error("Script took too long");
1001 });
b7edebf8 1002
f9423419 1003 lua.writeFunction("geoiplookup", [](const string &ip, const GeoIPInterface::GeoIPQueryAttribute attr) {
1d56dac6
PD
1004 return getGeo(ip, attr);
1005 });
1006
b7edebf8 1007 typedef const boost::variant<string,vector<pair<int,string> > > combovar_t;
60f9d9a2 1008
1009 lua.writeFunction("asnum", [](const combovar_t& asns) {
1010 string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::ASn);
1011 return doCompare(asns, res, [](const std::string& a, const std::string& b) {
1012 return !strcasecmp(a.c_str(), b.c_str());
1013 });
1014 });
c13af350
CHB
1015 lua.writeFunction("continent", [](const combovar_t& continent) {
1016 string res=getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
b7edebf8 1017 return doCompare(continent, res, [](const std::string& a, const std::string& b) {
1018 return !strcasecmp(a.c_str(), b.c_str());
1019 });
1020 });
60f9d9a2 1021 lua.writeFunction("continentCode", []() {
1022 string unknown("unknown");
1023 string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Continent);
1024 if ( res == unknown ) {
1025 return std::string("--");
1026 }
1027 return res;
1028 });
1029 lua.writeFunction("country", [](const combovar_t& var) {
1030 string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
1031 return doCompare(var, res, [](const std::string& a, const std::string& b) {
b7edebf8 1032 return !strcasecmp(a.c_str(), b.c_str());
1033 });
60f9d9a2 1034
b7edebf8 1035 });
60f9d9a2 1036 lua.writeFunction("countryCode", []() {
1037 string unknown("unknown");
c13af350 1038 string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Country2);
60f9d9a2 1039 if ( res == unknown ) {
1040 return std::string("--");
1041 }
1042 return res;
1043 });
1044 lua.writeFunction("region", [](const combovar_t& var) {
1045 string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
b7edebf8 1046 return doCompare(var, res, [](const std::string& a, const std::string& b) {
1047 return !strcasecmp(a.c_str(), b.c_str());
1048 });
1bc56192 1049
b7edebf8 1050 });
60f9d9a2 1051 lua.writeFunction("regionCode", []() {
1052 string unknown("unknown");
1053 string res = getGeo(s_lua_record_ctx->bestwho.toString(), GeoIPInterface::Region);
1054 if ( res == unknown ) {
1055 return std::string("--");
1056 }
1057 return res;
1058 });
c13af350 1059 lua.writeFunction("netmask", [](const iplist_t& ips) {
b7edebf8 1060 for(const auto& i :ips) {
1061 Netmask nm(i.second);
c13af350 1062 if(nm.match(s_lua_record_ctx->bestwho))
b7edebf8 1063 return true;
1064 }
1065 return false;
1066 });
b7edebf8 1067 /* {
1068 {
1bc56192 1069 {'192.168.0.0/16', '10.0.0.0/8'},
b7edebf8 1070 {'192.168.20.20', '192.168.20.21'}
1071 },
1072 {
1073 {'0.0.0.0/0'}, {'192.0.2.1'}
1074 }
1075 }
1bc56192 1076 */
c13af350 1077 lua.writeFunction("view", [](const vector<pair<int, vector<pair<int, iplist_t> > > >& in) {
b7edebf8 1078 for(const auto& rule : in) {
1079 const auto& netmasks=rule.second[0].second;
1080 const auto& destinations=rule.second[1].second;
1081 for(const auto& nmpair : netmasks) {
1082 Netmask nm(nmpair.second);
c13af350 1083 if(nm.match(s_lua_record_ctx->bestwho)) {
28beeb0f
PD
1084 if (destinations.empty()) {
1085 throw std::invalid_argument("The IP list cannot be empty (for netmask " + nm.toString() + ")");
1086 }
b51ef4f9 1087 return destinations[dns_random(destinations.size())].second;
b7edebf8 1088 }
1089 }
1090 }
1091 return std::string();
60f9d9a2 1092 });
1bc56192 1093
60f9d9a2 1094 lua.writeFunction("all", [](const vector< pair<int,string> >& ips) {
1095 vector<string> result;
1096 result.reserve(ips.size());
bb85386d 1097
60f9d9a2 1098 for(const auto& ip : ips) {
1099 result.emplace_back(ip.second);
1100 }
1101 if(result.empty()) {
1102 throw std::invalid_argument("The IP list cannot be empty");
1103 }
1104 return result;
1105 });
1bc56192 1106
c13af350 1107 lua.writeFunction("include", [&lua](string record) {
388d3a43 1108 DNSName rec;
b7edebf8 1109 try {
388d3a43
PL
1110 rec = DNSName(record) + s_lua_record_ctx->zone;
1111 } catch (const std::exception &e){
51a4e451 1112 g_log<<Logger::Error<<"Included record cannot be loaded, the name ("<<record<<") is malformed: "<<e.what()<<endl;
388d3a43
PL
1113 return;
1114 }
1115 try {
1116 vector<DNSZoneRecord> drs = lookup(rec, QType::LUA, s_lua_record_ctx->zoneid);
c9d40b1f 1117 for(const auto& dr : drs) {
b7edebf8 1118 auto lr = getRR<LUARecordContent>(dr.dr);
1119 lua.executeCode(lr->getCode());
1120 }
25bcfaec 1121 }
1122 catch(std::exception& e) {
388d3a43 1123 g_log<<Logger::Error<<"Failed to load include record for LUArecord "<<rec<<": "<<e.what()<<endl;
25bcfaec 1124 }
b7edebf8 1125 });
c13af350
CHB
1126}
1127
87f1cd7c 1128std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, const DNSName& query, const DNSName& zone, int zoneid, const DNSPacket& dnsp, uint16_t qtype, unique_ptr<AuthLua4>& LUA)
c13af350 1129{
87f1cd7c 1130 if(!LUA || // we don't have a Lua state yet
c13af350 1131 !g_LuaRecordSharedState) { // or we want a new one even if we had one
87f1cd7c
PD
1132 LUA = make_unique<AuthLua4>();
1133 setupLuaRecords(*LUA->getLua());
c13af350
CHB
1134 }
1135
1136 std::vector<shared_ptr<DNSRecordContent>> ret;
1137
87f1cd7c 1138 LuaContext& lua = *LUA->getLua();
c13af350 1139
2bbc9eb0 1140 s_lua_record_ctx = std::make_unique<lua_record_ctx_t>();
c13af350
CHB
1141 s_lua_record_ctx->qname = query;
1142 s_lua_record_ctx->zone = zone;
1143 s_lua_record_ctx->zoneid = zoneid;
bb85386d 1144
c13af350
CHB
1145 lua.writeVariable("qname", query);
1146 lua.writeVariable("zone", zone);
1147 lua.writeVariable("zoneid", zoneid);
4172a5b2 1148 lua.writeVariable("who", dnsp.getInnerRemote());
c13af350
CHB
1149 lua.writeVariable("dh", (dnsheader*)&dnsp.d);
1150 lua.writeVariable("dnssecOK", dnsp.d_dnssecOk);
1151 lua.writeVariable("tcp", dnsp.d_tcp);
1152 lua.writeVariable("ednsPKTSize", dnsp.d_ednsRawPacketSizeLimit);
1153 if(dnsp.hasEDNSSubnet()) {
1154 lua.writeVariable("ecswho", dnsp.getRealRemote());
1155 s_lua_record_ctx->bestwho = dnsp.getRealRemote().getNetwork();
1156 }
1157 else {
1158 lua.writeVariable("ecswho", nullptr);
4172a5b2 1159 s_lua_record_ctx->bestwho = dnsp.getInnerRemote();
c13af350
CHB
1160 }
1161 lua.writeVariable("bestwho", s_lua_record_ctx->bestwho);
b7edebf8 1162
b7edebf8 1163 try {
1164 string actual;
1165 if(!code.empty() && code[0]!=';')
6180eab4
PD
1166 actual = "return " + code;
1167 else
1168 actual = code.substr(1);
c9d40b1f 1169
b7edebf8 1170 auto content=lua.executeCode<boost::variant<string, vector<pair<int, string> > > >(actual);
c9d40b1f 1171
b7edebf8 1172 vector<string> contents;
1173 if(auto str = boost::get<string>(&content))
1174 contents.push_back(*str);
1175 else
1176 for(const auto& c : boost::get<vector<pair<int,string>>>(content))
1177 contents.push_back(c.second);
1bc56192 1178
690b86b7 1179 for(const auto& content_it: contents) {
b7edebf8 1180 if(qtype==QType::TXT)
d525b58b 1181 ret.push_back(DNSRecordContent::make(qtype, QClass::IN, '"' + content_it + '"'));
b7edebf8 1182 else
d525b58b 1183 ret.push_back(DNSRecordContent::make(qtype, QClass::IN, content_it));
b7edebf8 1184 }
1bc56192 1185 } catch(std::exception &e) {
d5fcd583 1186 g_log << Logger::Info << "Lua record ("<<query<<"|"<<QType(qtype).toString()<<") reported: " << e.what();
3217f4ea
PD
1187 try {
1188 std::rethrow_if_nested(e);
1189 g_log<<endl;
1190 } catch(const std::exception& ne) {
1191 g_log << ": " << ne.what() << std::endl;
1192 }
1193 catch(const PDNSException& ne) {
1194 g_log << ": " << ne.reason << std::endl;
1195 }
1bc56192 1196 throw ;
b7edebf8 1197 }
1198
1199 return ret;
1200}