]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/lua-record.cc
rec: ensure correct service user on debian
[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 {
b7edebf8 493 bestwho=dnsp.getRemote();
494 }
495
5a6d5b08
PD
496 lua.writeVariable("bestwho", bestwho);
497
b7edebf8 498 lua.writeFunction("latlon", [&bestwho]() {
0540d223 499 double lat, lon;
500 getLatLon(bestwho.toString(), lat, lon);
501 return std::to_string(lat)+" "+std::to_string(lon);
502 });
503
504 lua.writeFunction("latlonloc", [&bestwho]() {
505 string loc;
506 getLatLon(bestwho.toString(), loc);
0540d223 507 return loc;
b7edebf8 508 });
975f0839 509
1bc56192 510
6d7e1fd3 511 lua.writeFunction("closestMagic", [&bestwho,&query]() {
b7edebf8 512 vector<ComboAddress> candidates;
cc5c4f6b 513 // Getting something like 192-0-2-1.192-0-2-2.198-51-100-1.example.org
b7edebf8 514 for(auto l : query.getRawLabels()) {
515 boost::replace_all(l, "-", ".");
b7edebf8 516 try {
517 candidates.emplace_back(l);
6d7e1fd3 518 } catch (const PDNSException& e) {
cc5c4f6b
CHB
519 // no need to continue as we most likely reached the end of the ip list
520 break ;
b7edebf8 521 }
522 }
1bc56192 523 return pickclosest(bestwho, candidates).toString();
b7edebf8 524 });
1bc56192 525
7fedb52a 526 lua.writeFunction("latlonMagic", [&query](){
527 auto labels= query.getRawLabels();
528 if(labels.size()<4)
529 return std::string("unknown");
530 double lat, lon;
531 getLatLon(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], lat, lon);
532 return std::to_string(lat)+" "+std::to_string(lon);
533 });
c9d40b1f 534
1bc56192 535
c9d40b1f 536 lua.writeFunction("createReverse", [&bestwho,&query,&zone](string suffix, boost::optional<std::unordered_map<string,string>> e){
537 try {
538 auto labels= query.getRawLabels();
539 if(labels.size()<4)
540 return std::string("unknown");
541
542 vector<ComboAddress> candidates;
543
544 // exceptions are relative to zone
545 // so, query comes in for 4.3.2.1.in-addr.arpa, zone is called 2.1.in-addr.arpa
546 // e["1.2.3.4"]="bert.powerdns.com" - should match, easy enough to do
1bc56192 547 // the issue is with classless delegation..
c9d40b1f 548 if(e) {
549 ComboAddress req(labels[3]+"."+labels[2]+"."+labels[1]+"."+labels[0], 0);
550 const auto& uom = *e;
551 for(const auto& c : uom)
552 if(ComboAddress(c.first, 0) == req)
553 return c.second;
554 }
1bc56192 555
c9d40b1f 556
557 boost::format fmt(suffix);
558 fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit ) );
559 fmt % labels[3] % labels[2] % labels[1] % labels[0];
560
561 fmt % (labels[3]+"-"+labels[2]+"-"+labels[1]+"-"+labels[0]);
562
563 boost::format fmt2("%02x%02x%02x%02x");
564 for(int i=3; i>=0; --i)
565 fmt2 % atoi(labels[i].c_str());
566
567 fmt % (fmt2.str());
568
569 return fmt.str();
570 }
571 catch(std::exception& e) {
f5f3d7f5 572 g_log<<Logger::Error<<"error: "<<e.what()<<endl;
c9d40b1f 573 }
574 return std::string("error");
575 });
576
577 lua.writeFunction("createForward", [&zone, &query]() {
578 DNSName rel=query.makeRelative(zone);
579 auto parts = rel.getRawLabels();
580 if(parts.size()==4)
581 return parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3];
582 if(parts.size()==1) {
583 // either hex string, or 12-13-14-15
cb6bd1a9 584 // cout<<parts[0]<<endl;
df6ad4ee 585 unsigned int x1, x2, x3, x4;
c9d40b1f 586 if(sscanf(parts[0].c_str()+2, "%02x%02x%02x%02x", &x1, &x2, &x3, &x4)==4) {
587 return std::to_string(x1)+"."+std::to_string(x2)+"."+std::to_string(x3)+"."+std::to_string(x4);
588 }
1bc56192
CHB
589
590
c9d40b1f 591 }
592 return std::string("0.0.0.0");
593 });
594
595 lua.writeFunction("createForward6", [&query,&zone]() {
596 DNSName rel=query.makeRelative(zone);
597 auto parts = rel.getRawLabels();
598 if(parts.size()==8) {
599 string tot;
600 for(int i=0; i<8; ++i) {
601 if(i)
602 tot.append(1,':');
603 tot+=parts[i];
604 }
605 ComboAddress ca(tot);
606 return ca.toString();
607 }
608 else if(parts.size()==1) {
609 boost::replace_all(parts[0],"-",":");
610 ComboAddress ca(parts[0]);
611 return ca.toString();
612 }
613
614 return std::string("::");
615 });
616
1bc56192 617
c9d40b1f 618 lua.writeFunction("createReverse6", [&bestwho,&query,&zone](string suffix, boost::optional<std::unordered_map<string,string>> e){
619 vector<ComboAddress> candidates;
620
621 try {
622 auto labels= query.getRawLabels();
623 if(labels.size()<32)
624 return std::string("unknown");
c9d40b1f 625 boost::format fmt(suffix);
626 fmt.exceptions( boost::io::all_error_bits ^ ( boost::io::too_many_args_bit | boost::io::too_few_args_bit ) );
1bc56192 627
c9d40b1f 628
629 string together;
630 vector<string> quads;
631 for(int i=0; i<8; ++i) {
632 if(i)
633 together+=":";
634 string quad;
635 for(int j=0; j <4; ++j) {
636 quad.append(1, labels[31-i*4-j][0]);
637 together += labels[31-i*4-j][0];
638 }
639 quads.push_back(quad);
640 }
641 ComboAddress ip6(together,0);
642
643 if(e) {
644 auto& addrs=*e;
645 for(const auto& addr: addrs) {
646 // this makes sure we catch all forms of the address
647 if(ComboAddress(addr.first,0)==ip6)
648 return addr.second;
649 }
650 }
1bc56192 651
c9d40b1f 652 string dashed=ip6.toString();
653 boost::replace_all(dashed, ":", "-");
1bc56192 654
c9d40b1f 655 for(int i=31; i>=0; --i)
656 fmt % labels[i];
657 fmt % dashed;
658
659 for(const auto& quad : quads)
660 fmt % quad;
1bc56192 661
c9d40b1f 662 return fmt.str();
663 }
664 catch(std::exception& e) {
f5f3d7f5 665 g_log<<Logger::Error<<"LUA Record xception: "<<e.what()<<endl;
c9d40b1f 666 }
667 catch(PDNSException& e) {
f5f3d7f5 668 g_log<<Logger::Error<<"LUA Record exception: "<<e.reason<<endl;
c9d40b1f 669 }
670 return std::string("unknown");
671 });
672
1bc56192
CHB
673
674 /*
675 * Simplistic test to see if an IP address listens on a certain port
676 * Will return a single IP address from the set of available IP addresses. If
677 * no IP address is available, will return a random element of the set of
678 * addresses suppplied for testing.
679 *
680 * @example ifportup(443, { '1.2.3.4', '5.4.3.2' })"
681 */
4a913a28 682 lua.writeFunction("ifportup", [&bestwho](int port, const vector<pair<int, string> >& ips, const boost::optional<std::unordered_map<string,string>> options) {
1bc56192
CHB
683 vector<ComboAddress> candidates, unavailables;
684 opts_t opts;
685 vector<ComboAddress > conv;
5daed657 686 std::string selector;
1bc56192 687
25bcfaec 688 if(options)
689 opts = *options;
b7edebf8 690 for(const auto& i : ips) {
691 ComboAddress rem(i.second, port);
1bc56192 692 if(g_up.isUp(rem, opts)) {
b7edebf8 693 candidates.push_back(rem);
1bc56192
CHB
694 }
695 else {
696 unavailables.push_back(rem);
697 }
b7edebf8 698 }
5daed657
CHB
699 if(!candidates.empty()) {
700 // use regular selector
701 selector = getOptionValue(options, "selector", "random");
702 } else {
703 // All units are down, apply backupSelector on all candidates
1bc56192 704 candidates = std::move(unavailables);
5daed657 705 selector = getOptionValue(options, "backupSelector", "random");
b7edebf8 706 }
b7edebf8 707
5daed657 708 vector<ComboAddress> res = useSelector(selector, bestwho, candidates);
2fe7bbf8 709 return convIpListToString(res);
1bc56192 710 });
b7edebf8 711
5ca4872a 712 lua.writeFunction("ifurlup", [&bestwho](const std::string& url,
1bc56192
CHB
713 const boost::variant<iplist_t, ipunitlist_t>& ips,
714 boost::optional<opts_t> options) {
b7edebf8 715
716 vector<vector<ComboAddress> > candidates;
1bc56192 717 opts_t opts;
b7edebf8 718 if(options)
719 opts = *options;
1bc56192
CHB
720 if(auto simple = boost::get<iplist_t>(&ips)) {
721 vector<ComboAddress> unit = convIplist(*simple);
b7edebf8 722 candidates.push_back(unit);
723 } else {
1bc56192 724 auto units = boost::get<ipunitlist_t>(ips);
b7edebf8 725 for(const auto& u : units) {
1bc56192 726 vector<ComboAddress> unit = convIplist(u.second);
b7edebf8 727 candidates.push_back(unit);
728 }
729 }
730
b7edebf8 731 for(const auto& unit : candidates) {
732 vector<ComboAddress> available;
1bc56192
CHB
733 for(const auto& c : unit) {
734 if(g_up.isUp(c, url, opts)) {
b7edebf8 735 available.push_back(c);
1bc56192 736 }
b7edebf8 737 }
1bc56192 738 if(!available.empty()) {
2fe7bbf8
OV
739 vector<ComboAddress> res = useSelector(getOptionValue(options, "selector", "random"), bestwho, available);
740 return convIpListToString(res);
1bc56192
CHB
741 }
742 }
743
5daed657 744 // All units down, apply backupSelector on all candidates
1bc56192 745 vector<ComboAddress> ret{};
b7edebf8 746 for(const auto& unit : candidates) {
1bc56192 747 ret.insert(ret.end(), unit.begin(), unit.end());
b7edebf8 748 }
749
5daed657 750 vector<ComboAddress> res = useSelector(getOptionValue(options, "backupSelector", "random"), bestwho, ret);
2fe7bbf8
OV
751 return convIpListToString(res);
752 });
b7edebf8 753
975f0839 754
975f0839 755 /* idea: we have policies on vectors of ComboAddresses, like
1bc56192 756 random, pickwrandom, pickwhashed, pickclosest. In C++ this is ComboAddress in,
975f0839 757 ComboAddress out. In Lua, vector string in, string out */
1bc56192
CHB
758
759 /*
760 * Returns a random IP address from the supplied list
761 * @example pickrandom({ '1.2.3.4', '5.4.3.2' })"
762 */
763 lua.writeFunction("pickrandom", [](const iplist_t& ips) {
6d7e1fd3 764 vector<ComboAddress> conv = convIplist(ips);
1bc56192
CHB
765
766 return pickrandom(conv).toString();
b7edebf8 767 });
768
b7edebf8 769
1bc56192
CHB
770 /*
771 * Returns a random IP address from the supplied list, as weighted by the
772 * various ``weight`` parameters
773 * @example pickwrandom({ {100, '1.2.3.4'}, {50, '5.4.3.2'}, {1, '192.168.1.0'} })
774 */
775 lua.writeFunction("pickwrandom", [](std::unordered_map<int, wiplist_t> ips) {
776 vector<pair<int,ComboAddress> > conv = convWIplist(ips);
777
778 return pickwrandom(conv).toString();
975f0839 779 });
780
1bc56192
CHB
781 /*
782 * Based on the hash of `bestwho`, returns an IP address from the list
783 * supplied, as weighted by the various `weight` parameters
784 * @example pickwhashed({ {15, '1.2.3.4'}, {50, '5.4.3.2'} })
785 */
786 lua.writeFunction("pickwhashed", [&bestwho](std::unordered_map<int, wiplist_t > ips) {
975f0839 787 vector<pair<int,ComboAddress> > conv;
1bc56192
CHB
788
789 for(auto& i : ips)
975f0839 790 conv.emplace_back(atoi(i.second[1].c_str()), ComboAddress(i.second[2]));
1bc56192
CHB
791
792 return pickwhashed(bestwho, conv).toString();
975f0839 793 });
794
795
1bc56192
CHB
796 lua.writeFunction("pickclosest", [&bestwho](const iplist_t& ips) {
797 vector<ComboAddress > conv = convIplist(ips);
798
799 return pickclosest(bestwho, conv).toString();
800
b7edebf8 801 });
802
1bc56192 803
b7edebf8 804 int counter=0;
805 lua.writeFunction("report", [&counter](string event, boost::optional<string> line){
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}