]> granicus.if.org Git - pdns/commitdiff
make entries uncacheable, fix locking, add multi-line syntax, add options for ifupurl
authorbert hubert <bert.hubert@netherlabs.nl>
Tue, 1 Nov 2016 14:18:33 +0000 (15:18 +0100)
committerbert hubert <bert.hubert@netherlabs.nl>
Sun, 19 Nov 2017 19:48:19 +0000 (20:48 +0100)
pdns/Makefile.am
pdns/dnsrecords.cc
pdns/dnsrecords.hh
pdns/lua-auth4.cc
pdns/minicurl.cc [new file with mode: 0644]
pdns/minicurl.hh [new file with mode: 0644]
pdns/packethandler.cc

index b570a0ffb265036b4d2a746b02c7e3e17d8048e1..a37181f3a5ee0a06634d2b2fc9b1eafe00e30503 100644 (file)
@@ -182,6 +182,7 @@ pdns_server_SOURCES = \
        lua-auth.cc lua-auth.hh \
        lua-auth4.cc lua-auth4.hh \
        lua-pdns.cc lua-pdns.hh lua-iputils.cc \
+       minicurl.cc minicurl.hh \
        mastercommunicator.cc \
        md5.hh \
        misc.cc misc.hh \
@@ -232,7 +233,7 @@ pdns_server_LDADD = \
        $(YAHTTP_LIBS) \
        $(JSON11_LIBS) \
        $(LIBCRYPTO_LIBS) \
-       $(SYSTEMD_LIBS)
+       $(SYSTEMD_LIBS) -lcurl
 
 if BOTAN
 pdns_server_SOURCES += botansigners.cc
index 2feebc990a6771b98c7b507d18123d610433c925..c4bcbac356126f039e82cd3291b956c3c63207d2 100644 (file)
@@ -156,6 +156,19 @@ boilerplate_conv(OPT, QType::OPT,
                    conv.xfrBlob(d_data)
                  );
 
+string LUARecordContent::getCode()
+{
+  // in d_code, series of "part1" "part2"
+  vector<string> parts;
+  stringtok(parts, d_code, "\"");
+  string ret;
+  for(const auto& p : parts) {
+    ret += p;
+    ret.append(1, ' ');
+  }
+  return ret;
+}
+
 void OPTRecordContent::getData(vector<pair<uint16_t, string> >& options)
 {
   string::size_type pos=0;
index 8e9ec7b0160fa194f620c39adb8e568fbbe96f6c..c220aa78b6b07981803804f6b99cb99c35f3f7b4 100644 (file)
@@ -189,7 +189,7 @@ class LUARecordContent : public DNSRecordContent
 {
 public:
   includeboilerplate(LUA)
-
+  string getCode();
   uint16_t d_type;
   string d_code;
 };
index e7912080c52c9557330a5527bc2bd70a44197167..2fc519ca0d3e76d0ad644e1f8375bfb72b9377e5 100644 (file)
@@ -8,6 +8,8 @@
 #include <unordered_set>
 #include "sstuff.hh"
 #include <thread>
+#include <mutex>
+#include "minicurl.hh"
 
 #if !defined(HAVE_LUA)
 
@@ -287,45 +289,143 @@ AuthLua4::~AuthLua4() { }
 
 class IsUpOracle
 {
+private:
+  typedef std::unordered_map<string,string> opts_t;
+  struct CheckDesc
+  {
+    ComboAddress rem;
+    string url;
+    opts_t opts;
+    bool operator<(const CheckDesc& rhs) const
+    {
+      return std::make_tuple(rem, url) <
+        std::make_tuple(rhs.rem, rhs.url);
+    }
+  };
 public:
   bool isUp(const ComboAddress& remote);
-  
-  
+  bool isUp(const ComboAddress& remote, const std::string& url, opts_t opts=opts_t());
+    
 private:
-  void checkThread(const ComboAddress& rem) {
-    d_statuses[rem].second=false;
-    for(;;) {
+  void checkURLThread(ComboAddress rem, std::string url, opts_t opts);
+  void checkTCPThread(const ComboAddress& rem) {
+    CheckDesc cd{rem};
+    setDown(cd);
+    for(bool first=true;;first=false) {
       try {
         Socket s(rem.sin4.sin_family, SOCK_STREAM);
         s.setNonBlocking();
         s.connect(rem, 1);
-        if(!d_statuses[rem].second)
+        if(!d_statuses[{rem,string()}].second)
           cout<<"Declaring "<<rem.toStringWithPort()<<" UP!"<<endl;
-        d_statuses[rem].second=true;
+        setUp(cd);
       }
       catch(NetworkError& ne) {
-        if(d_statuses[rem].second)
+        if(d_statuses[{rem,string()}].second || first)
           cout<<"Failed to connect to "<<rem.toStringWithPort()<<", setting DOWN"<<endl;
-        d_statuses[rem].second=false;
+        setDown(cd);
       }
       sleep(1);
     }
   }
-  map<ComboAddress, pair<std::thread*, bool>> d_statuses;
+
+
+  map<CheckDesc, pair<std::thread*, bool>> d_statuses;
+
+  std::mutex d_mutex;
+
+  void setStatus(const CheckDesc& cd, bool status) 
+  {
+    std::lock_guard<std::mutex> l(d_mutex);
+    d_statuses[cd].second=status;
+  }
+
+  void setDown(const ComboAddress& rem, const std::string& url=std::string(), opts_t opts=opts_t())
+  {
+    CheckDesc cd{rem, url, opts};
+    setStatus(cd, false);
+  }
+
+  void setUp(const ComboAddress& rem, const std::string& url=std::string(), opts_t opts=opts_t())
+  {
+    CheckDesc cd{rem, url, opts};
+    setStatus(cd, true);
+  }
+
+  void setDown(const CheckDesc& cd)
+  {
+    setStatus(cd, false);
+  }
+
+  void setUp(const CheckDesc& cd)
+  {
+    setStatus(cd, true);
+  }
+
+  bool upStatus(const ComboAddress& rem, const std::string& url=std::string(), opts_t opts=opts_t())
+  {
+    CheckDesc cd{rem, url, opts};
+    std::lock_guard<std::mutex> l(d_mutex);
+    return d_statuses[cd].second;
+  }
+
 };
 
 bool IsUpOracle::isUp(const ComboAddress& remote)
 {
-  auto iter = d_statuses.find(remote);
+  std::lock_guard<std::mutex> l(d_mutex);
+  CheckDesc cd{remote};
+  auto iter = d_statuses.find(cd);
   if(iter == d_statuses.end()) {
     cout<<"First ever query for "<<remote.toStringWithPort()<<", launching checker"<<endl;
-    std::thread* checker = new std::thread(&IsUpOracle::checkThread, this, remote);
-    d_statuses[remote]={checker, false};
+    std::thread* checker = new std::thread(&IsUpOracle::checkTCPThread, this, remote);
+    d_statuses[cd]={checker, false};
     return false;
   }
   return iter->second.second;
 }
 
+bool IsUpOracle::isUp(const ComboAddress& remote, const std::string& url, std::unordered_map<string,string> opts)
+{
+  CheckDesc cd{remote, url, opts};
+  std::lock_guard<std::mutex> l(d_mutex);
+  auto iter = d_statuses.find(cd);
+  if(iter == d_statuses.end()) {
+    cout<<"First ever query for "<<remote.toString()<<" and url "<<url<<", launching checker"<<endl;
+    std::thread* checker = new std::thread(&IsUpOracle::checkURLThread, this, remote, url, opts);
+    d_statuses[cd]={checker, false};
+    return false;
+  }
+  return iter->second.second;
+}
+
+void IsUpOracle::checkURLThread(ComboAddress rem, std::string url, opts_t opts) 
+{
+  setDown(rem, url, opts);
+  for(bool first=true;;first=false) {
+    try {
+      MiniCurl mc;
+      cout<<"Checking URL "<<url<<" at "<<rem.toString()<<endl;
+      string content=mc.getURL(url, &rem);
+      if(opts.count("stringmatch") && content.find(opts["stringmatch"]) == string::npos) {
+        cout<<"URL "<<url<<" is up at "<<rem.toString()<<", but could not find stringmatch "<<opts["stringmatch"]<<" in page content, setting DOWN"<<endl;
+        setDown(rem, url, opts);
+        continue;
+      }
+      if(!upStatus(rem,url))
+        cout<<"Declaring "<<rem.toString()<<" UP for URL "<<url<<"!"<<endl;
+      setUp(rem, url);
+    }
+    catch(std::exception& ne) {
+      if(upStatus(rem,url,opts) || first)
+        cout<<"Failed to connect to "<<rem.toString()<<" for URL "<<url<<", setting DOWN, error: "<<ne.what()<<endl;
+      setDown(rem,url);
+    }
+    sleep(5);
+  }
+}
+
+
 IsUpOracle g_up;
 
 std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, const DNSName& query, const ComboAddress& who, uint16_t qtype) 
@@ -350,12 +450,45 @@ std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, cons
       for(const auto& c : candidates)
         cout<<c.toString()<<" ";
       cout<<endl;
+      vector<string> ret;
+      if(candidates.empty()) {
+        cout<<"Everything is down. Returning all of them"<<endl;
+        for(const auto& i : ips) 
+          ret.push_back(i.second);
+      }
+      else
+        ret.push_back(candidates[random() % candidates.size()].toString());
+      return ret;
+    });
+
+
+  lua.writeFunction("ifurlup", [](const std::string& url, const vector<pair<int, string> >& ips, boost::optional<std::unordered_map<string,string>> options) {
+
+      vector<ComboAddress> candidates;
+      for(const auto& i : ips) {
+        ComboAddress rem(i.second, 80);
+        std::unordered_map<string,string> opts;
+        if(options)
+          opts = *options;
+        if(g_up.isUp(rem, url, opts))
+          candidates.push_back(rem);
+      }
+      cout<<"Have "<<candidates.size()<<" candidate IP addresses: ";
+      for(const auto& c : candidates)
+        cout<<c.toString()<<" ";
+      cout<<endl;
+
+      vector<string> ret;
       if(candidates.empty()) {
-        cout<<"Picking a random one, everything is down. Should return all of them"<<endl;
-        return ips[random() % ips.size()].second;
+        cout<<"Everything is down, returning all IP addresses"<<endl;
+        for(const auto& i : ips) 
+          ret.push_back(i.second);
       }
-      return candidates[random() % candidates.size()].toString();
+      else
+        ret.push_back(candidates[random() % candidates.size()].toString());
+      return ret;
     });
+
   
   lua.writeFunction("pickRandom", [](const vector<pair<int, string> >& ips) {
       return ips[random()%ips.size()].second;
@@ -377,11 +510,21 @@ std::vector<shared_ptr<DNSRecordContent>> luaSynth(const std::string& code, cons
     });
 
   
-  string content=lua.executeCode<string>(strip);
-  if(qtype==QType::TXT)
-    content = '"'+content+'"';
-  
-  ret.push_back(std::shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(qtype, 1, content )));
+  auto content=lua.executeCode<boost::variant<string, vector<pair<int, string> > > >(strip);
+
+  vector<string> contents;
+  if(auto str = boost::get<string>(&content))
+    contents.push_back(*str);
+  else
+    for(const auto& c : boost::get<vector<pair<int,string>>>(content))
+      contents.push_back(c.second);
+
+  for(const auto& content: contents) {
+    if(qtype==QType::TXT)
+      ret.push_back(std::shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(qtype, 1, '"'+content+'"' )));
+    else
+      ret.push_back(std::shared_ptr<DNSRecordContent>(DNSRecordContent::mastermake(qtype, 1, content )));
+  }
 
   return ret;
 }
diff --git a/pdns/minicurl.cc b/pdns/minicurl.cc
new file mode 100644 (file)
index 0000000..50477ca
--- /dev/null
@@ -0,0 +1,94 @@
+#include "minicurl.hh"
+#include <curl/curl.h>
+#include <stdexcept>
+
+MiniCurl::MiniCurl()
+{
+  d_curl = curl_easy_init();
+}
+
+MiniCurl::~MiniCurl()
+{
+  curl_easy_cleanup(d_curl);
+}
+
+size_t MiniCurl::write_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
+{
+  MiniCurl* us = (MiniCurl*)userdata;
+  us->d_data.append(ptr, size*nmemb);
+  return size*nmemb;
+}
+
+static string extractHostFromURL(const std::string& url)
+{
+  auto pos = url.find("://");
+  if(pos == string::npos)
+    throw runtime_error("Can't find host part of '"+url+"'");
+  pos += 3;
+  auto endpos = url.find('/', pos);
+  if(endpos == string::npos)
+    return url.substr(pos);
+
+  return url.substr(pos, endpos-pos);
+}
+
+void MiniCurl::setupURL(const std::string& str, const ComboAddress* rem)
+{
+  if(rem) {
+    struct curl_slist *hostlist = NULL;
+
+    // url = http://hostname.enzo/url 
+
+    string host4=extractHostFromURL(str);
+    cout<<"Host name: '"<<host4<<"'"<<endl;
+    string hcode=(host4+":80:"+rem->toString());
+    //cout<<"Setting hardcoded IP: "<<hcode<<endl;
+    hostlist = curl_slist_append(NULL, hcode.c_str());
+    hcode=(host4+":443:"+rem->toString());
+    //    cout<<"Setting hardcoded IP: "<<hcode<<endl;;
+    hostlist = curl_slist_append(hostlist, hcode.c_str());
+
+    curl_easy_setopt(d_curl, CURLOPT_RESOLVE, hostlist);
+  }
+  curl_easy_setopt(d_curl, CURLOPT_FOLLOWLOCATION, true);
+  /* only allow HTTP, TFTP and SFTP */
+  curl_easy_setopt(d_curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
+  curl_easy_setopt(d_curl, CURLOPT_SSL_VERIFYPEER, false);
+  curl_easy_setopt(d_curl, CURLOPT_SSL_VERIFYHOST, false);
+  curl_easy_setopt(d_curl, CURLOPT_FAILONERROR, true);
+  curl_easy_setopt(d_curl, CURLOPT_URL, str.c_str());
+  curl_easy_setopt(d_curl, CURLOPT_WRITEFUNCTION, write_callback);
+  curl_easy_setopt(d_curl, CURLOPT_WRITEDATA, this);
+
+  
+  d_data.clear();
+}
+std::string MiniCurl::getURL(const std::string& str, const ComboAddress* rem)
+{
+  setupURL(str, rem);
+  auto res = curl_easy_perform(d_curl);
+  long http_code = 0;
+  curl_easy_getinfo(d_curl, CURLINFO_RESPONSE_CODE, &http_code);
+
+  if(res != CURLE_OK || http_code != 200)  {
+    throw std::runtime_error("Unable to retrieve URL ("+std::to_string(http_code)+"): "+string(curl_easy_strerror(res)));
+  }
+  std::string ret=d_data;
+  d_data.clear();
+  return ret;
+}
+
+std::string MiniCurl::postURL(const std::string& str, const std::string& postdata)
+{
+  setupURL(str);
+  curl_easy_setopt(d_curl, CURLOPT_POSTFIELDS, postdata.c_str());
+
+  auto res = curl_easy_perform(d_curl);
+  if(res != CURLE_OK) 
+    throw std::runtime_error("Unable to post URL");
+
+  std::string ret=d_data;
+
+  d_data.clear();
+  return ret;
+}
diff --git a/pdns/minicurl.hh b/pdns/minicurl.hh
new file mode 100644 (file)
index 0000000..45edd4b
--- /dev/null
@@ -0,0 +1,20 @@
+#pragma once
+#include <string>
+#include <curl/curl.h>
+#include "iputils.hh"
+// turns out 'CURL' is currently typedef for void which means we can't easily forward declare it
+
+class MiniCurl
+{
+public:
+  MiniCurl();
+  ~MiniCurl();
+  MiniCurl& operator=(const MiniCurl&) = delete;
+  std::string getURL(const std::string& str, const ComboAddress* rem=0);
+  std::string postURL(const std::string& str, const std::string& postdata);
+private:
+  CURL *d_curl;
+  static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata);
+  std::string d_data;
+  void setupURL(const std::string& str, const ComboAddress* rem=0);
+};
index 0ba3e64a2d6339551c5efa8e7d2306ff30807568..3c34485b4ec341217bcf95595def681154324cc4 100644 (file)
@@ -1288,11 +1288,12 @@ DNSPacket *PacketHandler::doQuestion(DNSPacket *p)
         if(rec->d_type != p->qtype.getCode())
           continue;
         
-        auto recvec=luaSynth(rec->d_code, target, p->getRemote(), p->qtype.getCode());
+        auto recvec=luaSynth(rec->getCode(), target, p->getRemote(), p->qtype.getCode());
         if(!recvec.empty()) {
           for(const auto& r : recvec) {
             rr.dr.d_type = p->qtype.getCode();
             rr.dr.d_content = r;
+            rr.scopeMask = 32;
             rrset.push_back(rr);
           }
           weDone = 1;