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