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