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