]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/lua-record.cc
Merge pull request #8223 from PowerDNS/omoerbeek-patch-1
[thirdparty/pdns.git] / pdns / lua-record.cc
CommitLineData
192e15fc 1#include "version.hh"
b7edebf8 2#include "ext/luawrapper/include/LuaContext.hpp"
3#include "lua-auth4.hh"
4#include <thread>
5#include "sstuff.hh"
6#include <mutex>
7#include "minicurl.hh"
8#include "ueberbackend.hh"
975f0839 9#include <boost/format.hpp>
8900e2e3 10#include "dnsrecords.hh"
45d4e670 11#include "dns_random.hh"
25bcfaec 12
f9423419 13#include "../modules/geoipbackend/geoipinterface.hh" // only for the enum
0540d223 14
15/* to do:
25bcfaec 16 block AXFR unless TSIG, or override
69ec43c3 17
18 investigate IPv6
19
20 check the wildcard 'no cache' stuff, we may get it wrong
21
22 ponder ECS scopemask setting
23
24 ponder netmask tree from file for huge number of netmasks
25bcfaec 25
0540d223 26 unify ifupurl/ifupport
69ec43c3 27 add attribute for certificate check
0540d223 28 add list of current monitors
29 expire them too?
c9d40b1f 30
31 pool of UeberBackends?
1bc56192
CHB
32
33 Pool checks ?
0540d223 34 */
35
af68014f
CHB
36extern int g_luaRecordExecLimit;
37
1bc56192
CHB
38using iplist_t = vector<pair<int, string> >;
39using wiplist_t = std::unordered_map<int, string>;
40using ipunitlist_t = vector<pair<int, iplist_t> >;
41using opts_t = std::unordered_map<string,string>;
42
b7edebf8 43class IsUpOracle
44{
45private:
b7edebf8 46 struct CheckDesc
47 {
48 ComboAddress rem;
49 string url;
50 opts_t opts;
51 bool operator<(const CheckDesc& rhs) const
52 {
25bcfaec 53 std::map<string,string> oopts, rhsoopts;
54 for(const auto& m : opts)
55 oopts[m.first]=m.second;
56 for(const auto& m : rhs.opts)
57 rhsoopts[m.first]=m.second;
1bc56192 58
25bcfaec 59 return std::make_tuple(rem, url, oopts) <
60 std::make_tuple(rhs.rem, rhs.url, rhsoopts);
b7edebf8 61 }
62 };
63public:
1bc56192
CHB
64 bool isUp(const ComboAddress& remote, const opts_t& opts);
65 bool isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts);
25bcfaec 66 bool isUp(const CheckDesc& cd);
1bc56192 67
b7edebf8 68private:
1bc56192
CHB
69 void checkURLThread(ComboAddress rem, std::string url, const opts_t& opts);
70 void checkTCPThread(ComboAddress rem, const opts_t& opts);
b7edebf8 71
72 struct Checker
73 {
df6ad4ee 74 std::thread thr;
b7edebf8 75 bool status;
76 };
77
78 typedef map<CheckDesc, Checker> statuses_t;
79 statuses_t d_statuses;
1bc56192 80
b7edebf8 81 std::mutex d_mutex;
82
1bc56192 83 void setStatus(const CheckDesc& cd, bool status)
b7edebf8 84 {
85 std::lock_guard<std::mutex> l(d_mutex);
86 d_statuses[cd].status=status;
87 }
88
1bc56192 89 void setDown(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
b7edebf8 90 {
91 CheckDesc cd{rem, url, opts};
92 setStatus(cd, false);
93 }
94
1bc56192 95 void setUp(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
b7edebf8 96 {
97 CheckDesc cd{rem, url, opts};
1bc56192 98
b7edebf8 99 setStatus(cd, true);
100 }
101
102 void setDown(const CheckDesc& cd)
103 {
104 setStatus(cd, false);
105 }
106
107 void setUp(const CheckDesc& cd)
108 {
109 setStatus(cd, true);
110 }
111
1bc56192 112 bool upStatus(const ComboAddress& rem, const std::string& url=std::string(), const opts_t& opts = opts_t())
b7edebf8 113 {
114 CheckDesc cd{rem, url, opts};
115 std::lock_guard<std::mutex> l(d_mutex);
116 return d_statuses[cd].status;
117 }
b7edebf8 118};
119
25bcfaec 120bool IsUpOracle::isUp(const CheckDesc& cd)
b7edebf8 121{
122 std::lock_guard<std::mutex> l(d_mutex);
b7edebf8 123 auto iter = d_statuses.find(cd);
124 if(iter == d_statuses.end()) {
df6ad4ee 125 d_statuses[cd]=Checker{std::thread(&IsUpOracle::checkTCPThread, this, cd.rem, cd.opts), false};
b7edebf8 126 return false;
127 }
128 return iter->second.status;
25bcfaec 129
130}
131
1bc56192 132bool IsUpOracle::isUp(const ComboAddress& remote, const opts_t& opts)
25bcfaec 133{
134 CheckDesc cd{remote, "", opts};
135 return isUp(cd);
b7edebf8 136}
137
1bc56192 138bool IsUpOracle::isUp(const ComboAddress& remote, const std::string& url, const opts_t& opts)
b7edebf8 139{
140 CheckDesc cd{remote, url, opts};
141 std::lock_guard<std::mutex> l(d_mutex);
142 auto iter = d_statuses.find(cd);
143 if(iter == d_statuses.end()) {
f5f3d7f5 144 // g_log<<Logger::Warning<<"Launching HTTP(s) status checker for "<<remote.toStringWithPort()<<" and URL "<<url<<endl;
df6ad4ee 145 d_statuses[cd]=Checker{std::thread(&IsUpOracle::checkURLThread, this, remote, url, opts), false};
b7edebf8 146 return false;
147 }
1bc56192 148
b7edebf8 149 return iter->second.status;
150}
151
1bc56192 152void IsUpOracle::checkTCPThread(ComboAddress rem, const opts_t& opts)
25bcfaec 153{
154 CheckDesc cd{rem, "", opts};
155 setDown(cd);
156 for(bool first=true;;first=false) {
157 try {
158 Socket s(rem.sin4.sin_family, SOCK_STREAM);
25bcfaec 159 ComboAddress src;
1bc56192 160 s.setNonBlocking();
25bcfaec 161 if(opts.count("source")) {
1bc56192 162 src=ComboAddress(opts.at("source"));
25bcfaec 163 s.bind(src);
164 }
165 s.connect(rem, 1);
166 if(!isUp(cd)) {
f5f3d7f5 167 g_log<<Logger::Warning<<"Lua record monitoring declaring TCP/IP "<<rem.toStringWithPort()<<" ";
25bcfaec 168 if(opts.count("source"))
f5f3d7f5
CHB
169 g_log<<"(source "<<src.toString()<<") ";
170 g_log<<"UP!"<<endl;
25bcfaec 171 }
172 setUp(cd);
173 }
174 catch(NetworkError& ne) {
175 if(isUp(rem, opts) || first)
f5f3d7f5 176 g_log<<Logger::Warning<<"Lua record monitoring declaring TCP/IP "<<rem.toStringWithPort()<<" DOWN: "<<ne.what()<<endl;
25bcfaec 177 setDown(cd);
178 }
179 sleep(1);
180 }
181}
182
7d5bf0bb 183
1bc56192 184void IsUpOracle::checkURLThread(ComboAddress rem, std::string url, const opts_t& opts)
b7edebf8 185{
186 setDown(rem, url, opts);
187 for(bool first=true;;first=false) {
188 try {
adf5f025 189 string useragent = productName();
192e15fc
PL
190 if (opts.count("useragent")) {
191 useragent = opts.at("useragent");
192 }
193 MiniCurl mc(useragent);
25bcfaec 194
195 string content;
196 if(opts.count("source")) {
1bc56192 197 ComboAddress src(opts.at("source"));
25bcfaec 198 content=mc.getURL(url, &rem, &src);
199 }
1bc56192 200 else {
25bcfaec 201 content=mc.getURL(url, &rem);
1bc56192
CHB
202 }
203 if(opts.count("stringmatch") && content.find(opts.at("stringmatch")) == string::npos) {
204 throw std::runtime_error(boost::str(boost::format("unable to match content with `%s`") % opts.at("stringmatch")));
b7edebf8 205 }
5ca4872a 206 if(!upStatus(rem,url,opts))
f5f3d7f5 207 g_log<<Logger::Warning<<"LUA record monitoring declaring "<<rem.toString()<<" UP for URL "<<url<<"!"<<endl;
25bcfaec 208 setUp(rem, url,opts);
b7edebf8 209 }
210 catch(std::exception& ne) {
211 if(upStatus(rem,url,opts) || first)
f5f3d7f5 212 g_log<<Logger::Warning<<"LUA record monitoring declaring "<<rem.toString()<<" DOWN for URL "<<url<<", error: "<<ne.what()<<endl;
25bcfaec 213 setDown(rem,url,opts);
b7edebf8 214 }
b7edebf8 215 sleep(5);
216 }
217}
218
5941d59a 219
7d5bf0bb 220IsUpOracle g_up;
b7edebf8 221namespace {
222template<typename T, typename C>
223bool doCompare(const T& var, const std::string& res, const C& cmp)
224{
1bc56192 225 if(auto country = boost::get<string>(&var))
b7edebf8 226 return cmp(*country, res);
227
228 auto countries=boost::get<vector<pair<int,string> > >(&var);
229 for(const auto& country : *countries) {
230 if(cmp(country.second, res))
231 return true;
232 }
233 return false;
234}
235}
4a913a28 236
237
f9423419 238std::string getGeo(const std::string& ip, GeoIPInterface::GeoIPQueryAttribute qa)
b7edebf8 239{
03be9ab5 240 static bool initialized;
25bcfaec 241 extern std::function<std::string(const std::string& ip, int)> g_getGeo;
924341d2 242 if(!g_getGeo) {
243 if(!initialized) {
f5f3d7f5 244 g_log<<Logger::Error<<"LUA Record attempted to use GeoIPBackend functionality, but backend not launched"<<endl;
924341d2 245 initialized=true;
246 }
247 return "unknown";
248 }
249 else
25bcfaec 250 return g_getGeo(ip, (int)qa);
4a913a28 251}
252
5ca4872a 253static ComboAddress pickrandom(const vector<ComboAddress>& ips)
4a913a28 254{
6d7e1fd3
CHB
255 if (ips.empty()) {
256 throw std::invalid_argument("The IP list cannot be empty");
257 }
45d4e670 258 return ips[dns_random(ips.size())];
4a913a28 259}
260
5ca4872a 261static ComboAddress hashed(const ComboAddress& who, const vector<ComboAddress>& ips)
4a913a28 262{
6d7e1fd3
CHB
263 if (ips.empty()) {
264 throw std::invalid_argument("The IP list cannot be empty");
265 }
4a913a28 266 ComboAddress::addressOnlyHash aoh;
267 return ips[aoh(who) % ips.size()];
b7edebf8 268}
269
4a913a28 270
1bc56192 271static ComboAddress pickwrandom(const vector<pair<int,ComboAddress> >& wips)
975f0839 272{
6d7e1fd3
CHB
273 if (wips.empty()) {
274 throw std::invalid_argument("The IP list cannot be empty");
275 }
975f0839 276 int sum=0;
277 vector<pair<int, ComboAddress> > pick;
278 for(auto& i : wips) {
279 sum += i.first;
280 pick.push_back({sum, i.second});
281 }
45d4e670 282 int r = dns_random(sum);
975f0839 283 auto p = upper_bound(pick.begin(), pick.end(),r, [](int r, const decltype(pick)::value_type& a) { return r < a.first;});
284 return p->second;
285}
286
1bc56192 287static ComboAddress pickwhashed(const ComboAddress& bestwho, vector<pair<int,ComboAddress> >& wips)
975f0839 288{
6d7e1fd3
CHB
289 if (wips.empty()) {
290 return ComboAddress();
291 }
975f0839 292 int sum=0;
293 vector<pair<int, ComboAddress> > pick;
294 for(auto& i : wips) {
295 sum += i.first;
296 pick.push_back({sum, i.second});
297 }
298 ComboAddress::addressOnlyHash aoh;
299 int r = aoh(bestwho) % sum;
300 auto p = upper_bound(pick.begin(), pick.end(),r, [](int r, const decltype(pick)::value_type& a) { return r < a.first;});
301 return p->second;
302}
303
b7edebf8 304static bool getLatLon(const std::string& ip, double& lat, double& lon)
305{
f9423419 306 string inp = getGeo(ip, GeoIPInterface::Location);
b7edebf8 307 if(inp.empty())
308 return false;
309 lat=atof(inp.c_str());
310 auto pos=inp.find(' ');
311 if(pos != string::npos)
312 lon=atof(inp.c_str() + pos);
313 return true;
314}
315
0540d223 316static bool getLatLon(const std::string& ip, string& loc)
317{
318 int latdeg, latmin, londeg, lonmin;
319 double latsec, lonsec;
320 char lathem='X', lonhem='X';
1bc56192 321
0540d223 322 double lat, lon;
323 if(!getLatLon(ip, lat, lon))
324 return false;
325
326 if(lat > 0) {
327 lathem='N';
328 }
329 else {
330 lat = -lat;
331 lathem='S';
332 }
333
334 if(lon > 0) {
335 lonhem='E';
336 }
337 else {
338 lon = -lon;
339 lonhem='W';
340 }
341
342 /*
343 >>> deg = int(R)
344 >>> min = int((R - int(R)) * 60.0)
345 >>> sec = (((R - int(R)) * 60.0) - min) * 60.0
346 >>> print("{}º {}' {}\"".format(deg, min, sec))
347 */
348
1bc56192 349
0540d223 350 latdeg = lat;
351 latmin = (lat - latdeg)*60.0;
352 latsec = (((lat - latdeg)*60.0) - latmin)*60.0;
353
354 londeg = lon;
355 lonmin = (lon - londeg)*60.0;
356 lonsec = (((lon - londeg)*60.0) - lonmin)*60.0;
357
358 // 51 59 00.000 N 5 55 00.000 E 4.00m 1.00m 10000.00m 10.00m
359
360 boost::format fmt("%d %d %d %c %d %d %d %c 0.00m 1.00m 10000.00m 10.00m");
361
362 loc= (fmt % latdeg % latmin % latsec % lathem % londeg % lonmin % lonsec % lonhem ).str();
363 return true;
364}
975f0839 365
1bc56192 366static ComboAddress pickclosest(const ComboAddress& bestwho, const vector<ComboAddress>& wips)
975f0839 367{
6d7e1fd3
CHB
368 if (wips.empty()) {
369 throw std::invalid_argument("The IP list cannot be empty");
370 }
975f0839 371 map<double,vector<ComboAddress> > ranked;
372 double wlat=0, wlon=0;
373 getLatLon(bestwho.toString(), wlat, wlon);
374 // cout<<"bestwho "<<wlat<<", "<<wlon<<endl;
375 vector<string> ret;
376 for(const auto& c : wips) {
377 double lat=0, lon=0;
378 getLatLon(c.toString(), lat, lon);
379 // cout<<c.toString()<<": "<<lat<<", "<<lon<<endl;
380 double latdiff = wlat-lat;
381 double londiff = wlon-lon;
382 if(londiff > 180)
1bc56192 383 londiff = 360 - londiff;
975f0839 384 double dist2=latdiff*latdiff + londiff*londiff;
385 // cout<<" distance: "<<sqrt(dist2) * 40000.0/360<<" km"<<endl; // length of a degree
386 ranked[dist2].push_back(c);
387 }
45d4e670 388 return ranked.begin()->second[dns_random(ranked.begin()->second.size())];
975f0839 389}
390
c9d40b1f 391static std::vector<DNSZoneRecord> lookup(const DNSName& name, uint16_t qtype, int zoneid)
392{
393 static UeberBackend ub;
394 static std::mutex mut;
395 std::lock_guard<std::mutex> lock(mut);
396 ub.lookup(QType(qtype), name, nullptr, zoneid);
397 DNSZoneRecord dr;
398 vector<DNSZoneRecord> ret;
399 while(ub.get(dr)) {
400 ret.push_back(dr);
401 }
402 return ret;
403}
975f0839 404
2fe7bbf8 405static std::string getOptionValue(const boost::optional<std::unordered_map<string, string>>& options, const std::string &name, const std::string &defaultValue)
5ca4872a 406{
2fe7bbf8 407 string selector=defaultValue;
5ca4872a 408 if(options) {
2fe7bbf8
OV
409 if(options->count(name))
410 selector=options->find(name)->second;
5ca4872a 411 }
2fe7bbf8
OV
412 return selector;
413}
414
415static vector<ComboAddress> useSelector(const std::string &selector, const ComboAddress& bestwho, const vector<ComboAddress>& candidates)
416{
417 vector<ComboAddress> ret;
5ca4872a 418
707cedf3 419 if(selector=="all")
2fe7bbf8
OV
420 return candidates;
421 else if(selector=="random")
422 ret.emplace_back(pickrandom(candidates));
1bc56192 423 else if(selector=="pickclosest")
2fe7bbf8 424 ret.emplace_back(pickclosest(bestwho, candidates));
5ca4872a 425 else if(selector=="hashed")
2fe7bbf8
OV
426 ret.emplace_back(hashed(bestwho, candidates));
427 else {
428 g_log<<Logger::Warning<<"LUA Record called with unknown selector '"<<selector<<"'"<<endl;
429 ret.emplace_back(pickrandom(candidates));
430 }
431
432 return ret;
433}
5ca4872a 434
2fe7bbf8
OV
435static vector<string> convIpListToString(const vector<ComboAddress> &comboAddresses)
436{
437 vector<string> ret;
c9768b80
CHB
438
439 for (const auto& c : comboAddresses) {
2fe7bbf8 440 ret.emplace_back(c.toString());
c9768b80
CHB
441 }
442
2fe7bbf8 443 return ret;
5ca4872a 444}
445
1bc56192
CHB
446static vector<ComboAddress> convIplist(const iplist_t& src)
447{
448 vector<ComboAddress> ret;
449
c9768b80 450 for(const auto& ip : src) {
1bc56192 451 ret.emplace_back(ip.second);
c9768b80 452 }
1bc56192
CHB
453
454 return ret;
455}
456
457static vector<pair<int, ComboAddress> > convWIplist(std::unordered_map<int, wiplist_t > src)
458{
459 vector<pair<int,ComboAddress> > ret;
460
c9768b80 461 for(const auto& i : src) {
1bc56192 462 ret.emplace_back(atoi(i.second.at(1).c_str()), ComboAddress(i.second.at(2)));
c9768b80 463 }
1bc56192
CHB
464
465 return ret;
466}
467
32829819
PD
468static thread_local unique_ptr<AuthLua4> s_LUA;
469bool g_LuaRecordSharedState;
fd1bdfb3 470
1bc56192 471std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, const DNSName& query, const DNSName& zone, int zoneid, const DNSPacket& dnsp, uint16_t qtype)
b7edebf8 472{
32829819
PD
473 if(!s_LUA || // we don't have a Lua state yet
474 !g_LuaRecordSharedState) { // or we want a new one even if we had one
475 s_LUA = make_unique<AuthLua4>();
fd1bdfb3 476 }
1bc56192 477
b7edebf8 478 std::vector<shared_ptr<DNSRecordContent>> ret;
1bc56192 479
32829819 480 LuaContext& lua = *s_LUA->getLua();
b7edebf8 481 lua.writeVariable("qname", query);
482 lua.writeVariable("who", dnsp.getRemote());
25e92bfa 483 lua.writeVariable("dh", (dnsheader*)&dnsp.d);
484 lua.writeVariable("dnssecOK", dnsp.d_dnssecOk);
485 lua.writeVariable("tcp", dnsp.d_tcp);
486 lua.writeVariable("ednsPKTSize", dnsp.d_ednsRawPacketSizeLimit);
b7edebf8 487 ComboAddress bestwho;
488 if(dnsp.hasEDNSSubnet()) {
0540d223 489 lua.writeVariable("ecswho", dnsp.getRealRemote());
b7edebf8 490 bestwho=dnsp.getRealRemote().getNetwork();
b7edebf8 491 }
492 else {
e2cefbf5 493 lua.writeVariable("ecswho", nullptr);
b7edebf8 494 bestwho=dnsp.getRemote();
495 }
496
5a6d5b08
PD
497 lua.writeVariable("bestwho", bestwho);
498
b7edebf8 499 lua.writeFunction("latlon", [&bestwho]() {
0540d223 500 double lat, lon;
501 getLatLon(bestwho.toString(), lat, lon);
502 return std::to_string(lat)+" "+std::to_string(lon);
503 });
504
505 lua.writeFunction("latlonloc", [&bestwho]() {
506 string loc;
507 getLatLon(bestwho.toString(), loc);
0540d223 508 return loc;
b7edebf8 509 });
975f0839 510
1bc56192 511
6d7e1fd3 512 lua.writeFunction("closestMagic", [&bestwho,&query]() {
b7edebf8 513 vector<ComboAddress> candidates;
cc5c4f6b 514 // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org
b7edebf8 515 for(auto l : query.getRawLabels()) {
516 boost::replace_all(l, "-", ".");
b7edebf8 517 try {
518 candidates.emplace_back(l);
6d7e1fd3 519 } catch (const PDNSException& e) {
cc5c4f6b
CHB
520 // no need to continue as we most likely reached the end of the ip list
521 break ;
b7edebf8 522 }
523 }
1bc56192 524 return pickclosest(bestwho, candidates).toString();
b7edebf8 525 });
1bc56192 526
7fedb52a 527 lua.writeFunction("latlonMagic", [&query](){
528 auto labels= query.getRawLabels();
529 if(labels.size()<4)
530 return std::string("unknown");
531 double lat, lon;
532 getLatLon(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], lat, lon);
533 return std::to_string(lat)+" "+std::to_string(lon);
534 });
c9d40b1f 535
1bc56192 536
17e300d7 537 lua.writeFunction("createReverse", [&query](string suffix, boost::optional<std::unordered_map<string,string>> e){
c9d40b1f 538 try {
539 auto labels= query.getRawLabels();
540 if(labels.size()<4)
541 return std::string("unknown");
542
543 vector<ComboAddress> candidates;
544
545 // exceptions are relative to zone
546 // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa
547 // e["1.2.3.4"]="bert.powerdns.com" - should match, easy enough to do
1bc56192 548 // the issue is with classless delegation..
c9d40b1f 549 if(e) {
550 ComboAddress req(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], 0);
551 const auto& uom = *e;
552 for(const auto& c : uom)
553 if(ComboAddress(c.first, 0) == req)
554 return c.second;
555 }
1bc56192 556
c9d40b1f 557
558 boost::format fmt(suffix);
559 fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit ) );
560 fmt % labels[3] % labels[2] % labels[1] % labels[0];
561
562 fmt % (labels[3]+"-"+labels[2]+"-"+labels[1]+"-"+labels[0]);
563
564 boost::format fmt2("%02x%02x%02x%02x");
565 for(int i=3; i>=0; --i)
566 fmt2 % atoi(labels[i].c_str());
567
568 fmt % (fmt2.str());
569
570 return fmt.str();
571 }
572 catch(std::exception& e) {
f5f3d7f5 573 g_log<<Logger::Error<<"error: "<<e.what()<<endl;
c9d40b1f 574 }
575 return std::string("error");
576 });
577
578 lua.writeFunction("createForward", [&zone, &query]() {
579 DNSName rel=query.makeRelative(zone);
580 auto parts = rel.getRawLabels();
581 if(parts.size()==4)
582 return parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3];
583 if(parts.size()==1) {
584 // either hex string, or 12-13-14-15
cb6bd1a9 585 // cout<<parts[0]<<endl;
df6ad4ee 586 unsigned int x1, x2, x3, x4;
c9d40b1f 587 if(sscanf(parts[0].c_str()+2, "%02x%02x%02x%02x", &x1, &x2, &x3, &x4)==4) {
588 return std::to_string(x1)+"."+std::to_string(x2)+"."+std::to_string(x3)+"."+std::to_string(x4);
589 }
1bc56192
CHB
590
591
c9d40b1f 592 }
593 return std::string("0.0.0.0");
594 });
595
596 lua.writeFunction("createForward6", [&query,&zone]() {
597 DNSName rel=query.makeRelative(zone);
598 auto parts = rel.getRawLabels();
599 if(parts.size()==8) {
600 string tot;
601 for(int i=0; i<8; ++i) {
602 if(i)
603 tot.append(1,':');
604 tot+=parts[i];
605 }
606 ComboAddress ca(tot);
607 return ca.toString();
608 }
609 else if(parts.size()==1) {
610 boost::replace_all(parts[0],"-",":");
611 ComboAddress ca(parts[0]);
612 return ca.toString();
613 }
614
615 return std::string("::");
616 });
617
1bc56192 618
17e300d7 619 lua.writeFunction("createReverse6", [&query](string suffix, boost::optional<std::unordered_map<string,string>> e){
c9d40b1f 620 vector<ComboAddress> candidates;
621
622 try {
623 auto labels= query.getRawLabels();
624 if(labels.size()<32)
625 return std::string("unknown");
c9d40b1f 626 boost::format fmt(suffix);
627 fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit ) );
1bc56192 628
c9d40b1f 629
630 string together;
631 vector<string> quads;
632 for(int i=0; i<8; ++i) {
633 if(i)
634 together+=":";
635 string quad;
636 for(int j=0; j <4; ++j) {
637 quad.append(1, labels[31-i*4-j][0]);
638 together += labels[31-i*4-j][0];
639 }
640 quads.push_back(quad);
641 }
642 ComboAddress ip6(together,0);
643
644 if(e) {
645 auto& addrs=*e;
646 for(const auto& addr: addrs) {
647 // this makes sure we catch all forms of the address
648 if(ComboAddress(addr.first,0)==ip6)
649 return addr.second;
650 }
651 }
1bc56192 652
c9d40b1f 653 string dashed=ip6.toString();
654 boost::replace_all(dashed, ":", "-");
1bc56192 655
c9d40b1f 656 for(int i=31; i>=0; --i)
657 fmt % labels[i];
658 fmt % dashed;
659
660 for(const auto& quad : quads)
661 fmt % quad;
1bc56192 662
c9d40b1f 663 return fmt.str();
664 }
665 catch(std::exception& e) {
f5f3d7f5 666 g_log<<Logger::Error<<"LUA Record xception: "<<e.what()<<endl;
c9d40b1f 667 }
668 catch(PDNSException& e) {
f5f3d7f5 669 g_log<<Logger::Error<<"LUA Record exception: "<<e.reason<<endl;
c9d40b1f 670 }
671 return std::string("unknown");
672 });
673
1bc56192
CHB
674
675 /*
676 * Simplistic test to see if an IP address listens on a certain port
677 * Will return a single IP address from the set of available IP addresses. If
678 * no IP address is available, will return a random element of the set of
679 * addresses suppplied for testing.
680 *
681 * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
682 */
4a913a28 683 lua.writeFunction("ifportup", [&bestwho](int port, const vector<pair<int, string> >& ips, const boost::optional<std::unordered_map<string,string>> options) {
1bc56192
CHB
684 vector<ComboAddress> candidates, unavailables;
685 opts_t opts;
686 vector<ComboAddress > conv;
5daed657 687 std::string selector;
1bc56192 688
25bcfaec 689 if(options)
690 opts = *options;
b7edebf8 691 for(const auto& i : ips) {
692 ComboAddress rem(i.second, port);
1bc56192 693 if(g_up.isUp(rem, opts)) {
b7edebf8 694 candidates.push_back(rem);
1bc56192
CHB
695 }
696 else {
697 unavailables.push_back(rem);
698 }
b7edebf8 699 }
5daed657
CHB
700 if(!candidates.empty()) {
701 // use regular selector
702 selector = getOptionValue(options, "selector", "random");
703 } else {
704 // All units are down, apply backupSelector on all candidates
1bc56192 705 candidates = std::move(unavailables);
5daed657 706 selector = getOptionValue(options, "backupSelector", "random");
b7edebf8 707 }
b7edebf8 708
5daed657 709 vector<ComboAddress> res = useSelector(selector, bestwho, candidates);
2fe7bbf8 710 return convIpListToString(res);
1bc56192 711 });
b7edebf8 712
5ca4872a 713 lua.writeFunction("ifurlup", [&bestwho](const std::string& url,
1bc56192
CHB
714 const boost::variant<iplist_t, ipunitlist_t>& ips,
715 boost::optional<opts_t> options) {
b7edebf8 716
717 vector<vector<ComboAddress> > candidates;
1bc56192 718 opts_t opts;
b7edebf8 719 if(options)
720 opts = *options;
1bc56192
CHB
721 if(auto simple = boost::get<iplist_t>(&ips)) {
722 vector<ComboAddress> unit = convIplist(*simple);
b7edebf8 723 candidates.push_back(unit);
724 } else {
1bc56192 725 auto units = boost::get<ipunitlist_t>(ips);
b7edebf8 726 for(const auto& u : units) {
1bc56192 727 vector<ComboAddress> unit = convIplist(u.second);
b7edebf8 728 candidates.push_back(unit);
729 }
730 }
731
b7edebf8 732 for(const auto& unit : candidates) {
733 vector<ComboAddress> available;
1bc56192
CHB
734 for(const auto& c : unit) {
735 if(g_up.isUp(c, url, opts)) {
b7edebf8 736 available.push_back(c);
1bc56192 737 }
b7edebf8 738 }
1bc56192 739 if(!available.empty()) {
2fe7bbf8
OV
740 vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), bestwho, available);
741 return convIpListToString(res);
1bc56192
CHB
742 }
743 }
744
5daed657 745 // All units down, apply backupSelector on all candidates
1bc56192 746 vector<ComboAddress> ret{};
b7edebf8 747 for(const auto& unit : candidates) {
1bc56192 748 ret.insert(ret.end(), unit.begin(), unit.end());
b7edebf8 749 }
750
5daed657 751 vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), bestwho, ret);
2fe7bbf8
OV
752 return convIpListToString(res);
753 });
b7edebf8 754
975f0839 755
975f0839 756 /* idea: we have policies on vectors of ComboAddresses, like
1bc56192 757 random, pickwrandom, pickwhashed, pickclosest. In C++ this is ComboAddress in,
975f0839 758 ComboAddress out. In Lua, vector string in, string out */
1bc56192
CHB
759
760 /*
761 * Returns a random IP address from the supplied list
762 * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
763 */
764 lua.writeFunction("pickrandom", [](const iplist_t& ips) {
6d7e1fd3 765 vector<ComboAddress> conv = convIplist(ips);
1bc56192
CHB
766
767 return pickrandom(conv).toString();
b7edebf8 768 });
769
b7edebf8 770
1bc56192
CHB
771 /*
772 * Returns a random IP address from the supplied list, as weighted by the
773 * various ``weight`` parameters
774 * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
775 */
776 lua.writeFunction("pickwrandom", [](std::unordered_map<int, wiplist_t> ips) {
777 vector<pair<int,ComboAddress> > conv = convWIplist(ips);
778
779 return pickwrandom(conv).toString();
975f0839 780 });
781
1bc56192
CHB
782 /*
783 * Based on the hash of `bestwho`, returns an IP address from the list
784 * supplied, as weighted by the various `weight` parameters
785 * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
786 */
787 lua.writeFunction("pickwhashed", [&bestwho](std::unordered_map<int, wiplist_t > ips) {
975f0839 788 vector<pair<int,ComboAddress> > conv;
1bc56192
CHB
789
790 for(auto& i : ips)
975f0839 791 conv.emplace_back(atoi(i.second[1].c_str()), ComboAddress(i.second[2]));
1bc56192
CHB
792
793 return pickwhashed(bestwho, conv).toString();
975f0839 794 });
795
796
1bc56192
CHB
797 lua.writeFunction("pickclosest", [&bestwho](const iplist_t& ips) {
798 vector<ComboAddress > conv = convIplist(ips);
799
800 return pickclosest(bestwho, conv).toString();
801
b7edebf8 802 });
803
1bc56192 804
17e300d7 805 lua.writeFunction("report", [](string event, boost::optional<string> line){
b7edebf8 806 throw std::runtime_error("Script took too long");
807 });
af68014f
CHB
808 if (g_luaRecordExecLimit > 0) {
809 lua.executeCode(boost::str(boost::format("debug.sethook(report, '', %d)") % g_luaRecordExecLimit));
810 }
b7edebf8 811
1d56dac6 812 // TODO: make this better. Accept netmask/CA objects; provide names for the attr constants
f9423419 813 lua.writeFunction("geoiplookup", [](const string &ip, const GeoIPInterface::GeoIPQueryAttribute attr) {
1d56dac6
PD
814 return getGeo(ip, attr);
815 });
816
b7edebf8 817 typedef const boost::variant<string,vector<pair<int,string> > > combovar_t;
818 lua.writeFunction("continent", [&bestwho](const combovar_t& continent) {
f9423419 819 string res=getGeo(bestwho.toString(), GeoIPInterface::Continent);
b7edebf8 820 return doCompare(continent, res, [](const std::string& a, const std::string& b) {
821 return !strcasecmp(a.c_str(), b.c_str());
822 });
823 });
824
825 lua.writeFunction("asnum", [&bestwho](const combovar_t& asns) {
f9423419 826 string res=getGeo(bestwho.toString(), GeoIPInterface::ASn);
b7edebf8 827 return doCompare(asns, res, [](const std::string& a, const std::string& b) {
b7edebf8 828 return !strcasecmp(a.c_str(), b.c_str());
829 });
830 });
1bc56192 831
b7edebf8 832 lua.writeFunction("country", [&bestwho](const combovar_t& var) {
f9423419 833 string res = getGeo(bestwho.toString(), GeoIPInterface::Country2);
b7edebf8 834 return doCompare(var, res, [](const std::string& a, const std::string& b) {
835 return !strcasecmp(a.c_str(), b.c_str());
836 });
1bc56192 837
b7edebf8 838 });
839
1bc56192 840 lua.writeFunction("netmask", [bestwho](const iplist_t& ips) {
b7edebf8 841 for(const auto& i :ips) {
842 Netmask nm(i.second);
843 if(nm.match(bestwho))
844 return true;
845 }
846 return false;
847 });
848
849 /* {
850 {
1bc56192 851 {'192.168.0.0/16', '10.0.0.0/8'},
b7edebf8 852 {'192.168.20.20', '192.168.20.21'}
853 },
854 {
855 {'0.0.0.0/0'}, {'192.0.2.1'}
856 }
857 }
1bc56192
CHB
858 */
859 lua.writeFunction("view", [bestwho](const vector<pair<int, vector<pair<int, iplist_t> > > >& in) {
b7edebf8 860 for(const auto& rule : in) {
861 const auto& netmasks=rule.second[0].second;
862 const auto& destinations=rule.second[1].second;
863 for(const auto& nmpair : netmasks) {
864 Netmask nm(nmpair.second);
865 if(nm.match(bestwho)) {
b51ef4f9 866 return destinations[dns_random(destinations.size())].second;
b7edebf8 867 }
868 }
869 }
870 return std::string();
871 }
872 );
1bc56192
CHB
873
874
b7edebf8 875 lua.writeFunction("include", [&lua,zone,zoneid](string record) {
876 try {
c9d40b1f 877 vector<DNSZoneRecord> drs = lookup(DNSName(record) +zone, QType::LUA, zoneid);
878 for(const auto& dr : drs) {
b7edebf8 879 auto lr = getRR<LUARecordContent>(dr.dr);
880 lua.executeCode(lr->getCode());
881 }
25bcfaec 882 }
883 catch(std::exception& e) {
f5f3d7f5 884 g_log<<Logger::Error<<"Failed to load include record for LUArecord "<<(DNSName(record)+zone)<<": "<<e.what()<<endl;
25bcfaec 885 }
b7edebf8 886 });
887
b7edebf8 888 try {
889 string actual;
890 if(!code.empty() && code[0]!=';')
6180eab4
PD
891 actual = "return " + code;
892 else
893 actual = code.substr(1);
c9d40b1f 894
b7edebf8 895 auto content=lua.executeCode<boost::variant<string, vector<pair<int, string> > > >(actual);
c9d40b1f 896
b7edebf8 897 vector<string> contents;
898 if(auto str = boost::get<string>(&content))
899 contents.push_back(*str);
900 else
901 for(const auto& c : boost::get<vector<pair<int,string>>>(content))
902 contents.push_back(c.second);
1bc56192 903
b7edebf8 904 for(const auto& content: contents) {
905 if(qtype==QType::TXT)
98fa3598 906 ret.push_back(DNSRecordContent::mastermake(qtype, QClass::IN, '"'+content+'"' ));
b7edebf8 907 else
98fa3598 908 ret.push_back(DNSRecordContent::mastermake(qtype, QClass::IN, content ));
b7edebf8 909 }
1bc56192 910 } catch(std::exception &e) {
3217f4ea
PD
911 g_log<<Logger::Error<<"Lua record reported: "<<e.what();
912 try {
913 std::rethrow_if_nested(e);
914 g_log<<endl;
915 } catch(const std::exception& ne) {
916 g_log << ": " << ne.what() << std::endl;
917 }
918 catch(const PDNSException& ne) {
919 g_log << ": " << ne.reason << std::endl;
920 }
1bc56192 921 throw ;
b7edebf8 922 }
923
924 return ret;
925}