]> granicus.if.org Git - pdns/commitdiff
implement remote control with libsodium
authorbert hubert <bert.hubert@netherlabs.nl>
Thu, 26 Feb 2015 16:19:08 +0000 (17:19 +0100)
committerbert hubert <bert.hubert@netherlabs.nl>
Thu, 26 Feb 2015 16:19:08 +0000 (17:19 +0100)
.travis.yml
configure.ac
pdns/Makefile.am
pdns/dnsdist.cc
pdns/dnsdistconf.lua
pdns/sodcrypto.cc [new file with mode: 0644]
pdns/sodcrypto.hh [new file with mode: 0644]

index 94bdf6e8c6020d3cd568a4ec615b63e9bb3b84e1..3a55b2ac2bdf8ef6c7c1c6ff77d0a01534ff36fa 100644 (file)
@@ -76,6 +76,8 @@ before_script:
  - sudo chmod 0644 /etc/softhsm/softhsm.conf
  - sudo chmod 0777 /var/lib/softhsm
  - p11-kit -l # ensure it's ok
+ - wget https://xs.powerdns.com/tmp/libsodium_1.0.2-1_amd64.deb
+ - sudo dpkg -i libsodium_1.0.2-1_amd64.deb
 script:
  - ./bootstrap
  - ./configure
index d1c175aa35fd8046215582f62856941e9846db55..8b03630f344c17f26f407b2b27254a1adadef1b4 100644 (file)
@@ -103,6 +103,8 @@ AC_CHECK_HEADERS(
 )
 
 PDNS_CHECK_RAGEL
+PKG_CHECK_MODULES([libsodium], [libsodium])
+
 
 BOOST_REQUIRE([1.35])
 BOOST_FOREACH
index 257afb0be5ed41d2e8789d69903f80fc97a44721..e8214988c417dcc6efcfb0d706ee0b2d63e226c2 100644 (file)
@@ -575,6 +575,7 @@ dnsdist_SOURCES = \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
        sillyrecords.cc \
+       sodcrypto.cc sodcrypto.hh \
        sstuff.hh \
        statbag.cc \
        unix_utility.cc
@@ -585,7 +586,7 @@ dnsdist_LDFLAGS = \
 
 dnsdist_LDADD = \
        $(POLARSSL_LIBS) -lreadline \
-       $(BOOST_PROGRAM_OPTIONS_LIBS) $(LUA_LIBS)
+       $(BOOST_PROGRAM_OPTIONS_LIBS) $(LUA_LIBS) ${libsodium_LIBS}
 
 nsec3dig_SOURCES = \
        base32.cc \
index 98dbe9fcd7a898c131c26aee3cb666368be4702a..a07a7a3fb3020036c798808483fce4022649c22a 100644 (file)
 #include <readline/history.h>
 #include "dnsname.hh"
 #include "dnswriter.hh"
+#include "base64.hh"
 #include <fstream>
-
+#include <sodium.h>
+#include "sodcrypto.hh"
 #undef L
 
 
@@ -188,7 +190,6 @@ struct IDState
   atomic<uint64_t> age;
 };
 
-
 struct DownstreamState
 {
   DownstreamState(const ComboAddress& remote_);
@@ -326,7 +327,7 @@ shared_ptr<DownstreamState> roundrobin(const ComboAddress& remote, const DNSName
 }
 
 
-#if 0
+
 static void daemonize(void)
 {
   if(fork())
@@ -344,12 +345,12 @@ static void daemonize(void)
     close(i);
   }
 }
-#endif
 
 SuffixMatchNode g_suffixMatchNodeFilter;
 SuffixMatchNode g_abuseSMN;
 NetmaskGroup g_abuseNMG;
 shared_ptr<DownstreamState> g_abuseDSS;
+ComboAddress g_serverControl;
 
 // listens to incoming queries, sends out to downstream servers, noting the intended return path 
 void* udpClientThread(ClientState* cs)
@@ -533,7 +534,6 @@ public:
   // Should not be called simultaneously!
   void addTCPClientThread()
   {  
-    
     vinfolog("Adding TCP Client thread");
 
     int pipefds[2];
@@ -712,9 +712,68 @@ void* maintThread()
   return 0;
 }
 
+struct {
+  string pub;
+  string sec;
+} g_accessKeys, g_serverKeys;
 
+void controlClientThread(int fd, ComboAddress client)
+try
+{
+  for(;;) {
+    uint16_t len;
+    getMsgLen(fd, &len);
+    char msg[len];
+    readn2(fd, msg, len);
+    
+    string line(msg, len);
+    line = sodDecrypt(line, g_accessKeys.pub, g_serverKeys.sec);
+    //    cerr<<"Have decrypted line: "<<line<<endl;
+    string response;
+    try {
+      std::lock_guard<std::mutex> lock(g_luamutex);
+      auto ret=g_lua.executeCode<boost::optional<string>>("return "+line);
+      if(ret)
+       response=*ret;
+    }
+    catch(std::exception& e) {
+      cerr<<"Error: "<<e.what()<<endl;
+    }
+    response = sodEncrypt(response, g_serverKeys.sec, g_accessKeys.pub);
+    putMsgLen(fd, response.length());
+    writen2(fd, response.c_str(), (uint16_t)response.length());
+  }
+  infolog("Closed control connection from %s", client.toStringWithPort());
+  close(fd);
+  fd=-1;
+}
+catch(std::exception& e)
+{
+  errlog("Got an exception in client connection from %s: %s", client.toStringWithPort(), e.what());
+  if(fd >= 0)
+    close(fd);
+}
+
+
+void controlThread(int fd, ComboAddress local)
+try
+{
+  ComboAddress client;
+  int sock;
+  warnlog("Accepting control connections on %s", local.toStringWithPort());
+  while((sock=SAccept(fd, client)) >= 0) {
+    warnlog("Got control connection from %s", client.toStringWithPort());
+    thread t(controlClientThread, sock, client);
+    t.detach();
+  }
+}
+catch(std::exception& e) 
+{
+  close(fd);
+  errlog("Control connection died: %s", e.what());
+}
 
-void setupLua()
+void setupLua(bool client)
 {
   g_lua.writeFunction("newServer", 
                      [](const std::string& address, boost::optional<int> qps)
@@ -771,18 +830,15 @@ void setupLua()
   g_lua.writeFunction("addDomainBlock", [](const std::string& domain) { g_suffixMatchNodeFilter.add(DNSName(domain)); });
   g_lua.writeFunction("listServers", []() {  
       try {
-      string ret;
+      ostringstream ret;
       
       boost::format fmt("%1$-3d %2% %|30t|%3$5s %|36t|%4$7.1f %|41t|%5$7d %|48t|%6$10d %|59t|%7$7d %|69t|%8$2.1f %|78t|%9$5.1f" );
 
-      cout << (fmt % "#" % "Address" % "State" % "Qps" % "Qlim" % "Queries" % "Drops" % "Drate" % "Lat") << endl;
+      ret << (fmt % "#" % "Address" % "State" % "Qps" % "Qlim" % "Queries" % "Drops" % "Drate" % "Lat") << endl;
 
       uint64_t totQPS{0}, totQueries{0}, totDrops{0};
       int counter=0;
       for(auto& s : g_dstates) {
-       if(!ret.empty()) ret+="\n";
-       ret+=s->remote.toStringWithPort() + " " + std::to_string(s->queries.load()) + " " + std::to_string(s->outstanding.load());
-
        string status;
        if(s->availability == DownstreamState::Availability::Up) 
          status = "UP";
@@ -791,7 +847,7 @@ void setupLua()
        else 
          status = (s->upStatus ? "up" : "down");
 
-       cout<< (fmt % counter % s->remote.toStringWithPort() % 
+       ret << (fmt % counter % s->remote.toStringWithPort() % 
                status % 
                s->queryLoad % s->qps.getRate() % s->queries.load() % s->reuseds.load() % (s->dropRate) % (s->latencyUsec/1000.0)) << endl;
 
@@ -800,11 +856,11 @@ void setupLua()
        totDrops += s->reuseds.load();
        ++counter;
       }
-      cout<< (fmt % "All" % "" % "" 
+      ret<< (fmt % "All" % "" % "" 
                % 
              (double)totQPS % "" % totQueries % totDrops % "" % "") << endl;
 
-      return ret;
+      return ret.str();
       }catch(std::exception& e) { cerr<<e.what()<<endl; throw; }
     });
 
@@ -843,8 +899,27 @@ void setupLua()
   g_lua.registerFunction("add",(void (SuffixMatchNode::*)(const DNSName&)) &SuffixMatchNode::add);
   g_lua.registerFunction("check",(bool (SuffixMatchNode::*)(const DNSName&) const) &SuffixMatchNode::check);
 
+  g_lua.writeFunction("controlSocket", [client](const std::string& str) {
+      ComboAddress local(str, 5199);
 
-
+      if(client) {
+       g_serverControl = local;
+       return;
+      }
+      
+      try {
+       int sock = socket(local.sin4.sin_family, SOCK_STREAM, 0);
+       SSetsockopt(sock, SOL_SOCKET, SO_REUSEADDR, 1);
+       SBind(sock, local);
+       SListen(sock, 5);
+       thread t(controlThread, sock, local);
+       t.detach();
+      }
+      catch(std::exception& e) {
+       errlog("Unable to bind to control socket on %s: %s", local.toStringWithPort(), e.what());
+      }
+    });
+  
   g_lua.writeFunction("newQPSLimiter", [](int rate, int burst) { return QPSLimiter(rate, burst); });
   g_lua.registerFunction("check", &QPSLimiter::check);
 
@@ -862,21 +937,169 @@ void setupLua()
       g_abuseDSS=dss;
     });
 
+  g_lua.writeFunction("newKeypair", newKeypair);
+  g_lua.writeFunction("makeKeys", []() {
+      string server(newKeypair()), client(newKeypair());
+      return "serverKeys("+server+")\naccessKeys("+client+")";
+    });
+  
+  g_lua.writeFunction("accessKeys", [](std::unordered_map<int, std::string> params) {
+      if(B64Decode(params[1], g_accessKeys.pub)) 
+       throw std::runtime_error("Unable to decode "+params[1]+" as Base64");
+      if(B64Decode(params[2], g_accessKeys.sec))
+       throw std::runtime_error("Unable to decode "+params[2]+" as Base64");
+    });
+
+  g_lua.writeFunction("serverKeys", [](std::unordered_map<int, std::string> params) {
+      if(B64Decode(params[1], g_serverKeys.pub))
+       throw std::runtime_error("Unable to decode "+params[1]+" as Base64");
+      if(B64Decode(params[2], g_serverKeys.sec))
+       throw std::runtime_error("Unable to decode "+params[2]+" as Base64");
+    });
+
+  
+  g_lua.writeFunction("testCrypto", [](string testmsg)
+   {
+     string encrypted = sodEncrypt(testmsg, g_accessKeys.sec, g_serverKeys.pub);
+     string decrypted = sodDecrypt(encrypted, g_accessKeys.pub, g_serverKeys.sec);
+     
+     if(testmsg == decrypted)
+       cerr<<"Everything is ok!"<<endl;
+     else
+       cerr<<"Crypto failed.."<<endl;
+     
+   });
+
+
+
   g_lua.executeCode(ifs);
 }
 
 
+void doClient(ComboAddress server)
+{
+  cout<<"Connecting to "<<server.toStringWithPort()<<endl;
+  int fd=socket(server.sin4.sin_family, SOCK_STREAM, 0);
+  SConnect(fd, server);
+
+  set<string> dupper;
+  {
+    ifstream history(".history");
+    string line;
+    while(getline(history, line))
+      add_history(line.c_str());
+  }
+  ofstream history(".history", std::ios_base::app);
+  string lastline;
+  for(;;) {
+    char* sline = readline("> ");
+    if(!sline)
+      break;
+
+    string line(sline);
+    if(!line.empty() && line != lastline) {
+      add_history(sline);
+      history << sline <<endl;
+      history.flush();
+    }
+    lastline=line;
+    free(sline);
+    
+    if(line=="quit")
+      break;
+
+    string response;
+    string msg=sodEncrypt(line, g_accessKeys.sec, g_serverKeys.pub);
+    putMsgLen(fd, msg.length());
+    writen2(fd, msg);
+    uint16_t len;
+    getMsgLen(fd, &len);
+    char resp[len];
+    readn2(fd, resp, len);
+    msg.assign(resp, len);
+    msg=sodDecrypt(msg, g_serverKeys.pub, g_accessKeys.sec);
+    cout<<msg<<endl;
+  }
+
+
+}
+
+void doConsole()
+{
+  set<string> dupper;
+  {
+    ifstream history(".history");
+    string line;
+    while(getline(history, line))
+      add_history(line.c_str());
+  }
+  ofstream history(".history", std::ios_base::app);
+  string lastline;
+  for(;;) {
+    char* sline = readline("> ");
+    if(!sline)
+      break;
+
+    string line(sline);
+    if(!line.empty() && line != lastline) {
+      add_history(sline);
+      history << sline <<endl;
+      history.flush();
+    }
+    lastline=line;
+    free(sline);
+    
+    if(line=="quit")
+      break;
+
+    string response;
+    try {
+      std::lock_guard<std::mutex> lock(g_luamutex);
+      auto ret=g_lua.executeCode<
+       boost::optional<
+         boost::variant<
+           string, 
+           shared_ptr<DownstreamState>
+           >
+         >
+       >("return "+line);
+
+      if(ret) {
+       if (const auto strValue = boost::get<shared_ptr<DownstreamState>>(&*ret)) {
+         cout<<(*strValue)->remote.toStringWithPort()<<endl;
+       }
+       else if (const auto strValue = boost::get<string>(&*ret)) {
+         cout<<*strValue<<endl;
+       }
+      }
+
+    }
+    catch(std::exception& e) {
+      cerr<<"Error: "<<e.what()<<endl;
+    }
+   
+  }
+}
+
 int main(int argc, char** argv)
 try
 {
   signal(SIGPIPE, SIG_IGN);
+  signal(SIGCHLD, SIG_IGN);
   openlog("dnsdist", LOG_PID, LOG_DAEMON);
   g_console=true;
+
+  if (sodium_init() == -1) {
+    cerr<<"Unable to initialize crypto library"<<endl;
+    exit(EXIT_FAILURE);
+  }
+
   po::options_description desc("Allowed options"), hidden, alloptions;
   desc.add_options()
     ("help,h", "produce help message")
     ("config", po::value<string>()->default_value("dnsdistconf.lua"), "Filename with our configuration")
-    //    ("daemon", po::value<bool>()->default_value(true), "run in background")
+    ("client", "be a client")
+    ("daemon", po::value<bool>()->default_value(true), "run in background")
     ("local", po::value<vector<string> >(), "Listen on which addresses")
     ("max-outstanding", po::value<uint16_t>()->default_value(65535), "maximum outstanding queries per downstream")
     ("regex-drop", po::value<string>(), "If set, block queries matching this regex. Mind trailing dot!")
@@ -902,17 +1125,13 @@ try
   g_maxOutstanding = g_vm["max-outstanding"].as<uint16_t>();
 
   g_policy = firstAvailable;
-  setupLua();
 
-  if(g_vm.count("remotes")) {
-    for(const auto& address : g_vm["remotes"].as<vector<string>>()) {
-      auto ret=std::shared_ptr<DownstreamState>(new DownstreamState(ComboAddress(address, 53)));
-      ret->tid = move(thread(responderThread, ret));
-      g_dstates.push_back(ret);
-    }
-  }
 
-  /*
+  if(g_vm.count("client")) {
+    setupLua(true);
+    doClient(g_serverControl);
+    exit(EXIT_SUCCESS);
+  }
   if(g_vm["daemon"].as<bool>())  {
     g_console=false;
     daemonize();
@@ -920,7 +1139,18 @@ try
   else {
     vinfolog("Running in the foreground");
   }
-  */
+
+
+  setupLua(false);
+  if(g_vm.count("remotes")) {
+    for(const auto& address : g_vm["remotes"].as<vector<string>>()) {
+      auto ret=std::shared_ptr<DownstreamState>(new DownstreamState(ComboAddress(address, 53)));
+      ret->tid = move(thread(responderThread, ret));
+      g_dstates.push_back(ret);
+    }
+  }
+
+  
 
   for(auto& dss : g_dstates) {
     if(dss->availability==DownstreamState::Availability::Auto) {
@@ -972,53 +1202,22 @@ try
   }
 
   thread stattid(maintThread);
-  stattid.detach();
-
-  set<string> dupper;
-  {
-    ifstream history(".history");
-    string line;
-    while(getline(history, line))
-      add_history(line.c_str());
+  
+  if(!g_vm["daemon"].as<bool>())  {
+    stattid.detach();
+    doConsole();
   }
-  ofstream history(".history", std::ios_base::app);
-  string lastline;
-  for(;;) {
-    char* sline = readline("> ");
-    if(!sline)
-      break;
-
-    string line(sline);
-    if(!line.empty() && line != lastline) {
-      add_history(sline);
-      history << sline <<endl;
-      history.flush();
-    }
-    lastline=line;
-    free(sline);
-    
-    if(line=="quit")
-      break;
-
-    try {
-      std::lock_guard<std::mutex> lock(g_luamutex);
-      g_lua.executeCode(line);
-    }
-    catch(std::exception& e) {
-      cerr<<"Error: "<<e.what()<<endl;
-    }
-    
+  else {
+    stattid.join();
   }
   _exit(EXIT_SUCCESS);
-  // stattid.join();
-}
 
+}
 catch(std::exception &e)
 {
-  errlog("Fatal: %s", e.what());
+  errlog("Fatal error: %s", e.what());
 }
-
 catch(PDNSException &ae)
 {
-  errlog("Fatal: %s", ae.reason);
+  errlog("Fatal pdns error: %s", ae.reason);
 }
index ef95a51d05afa109374a497e1fbc8f62a5597d5c..5c360494d9c31fa05f308be0c66fcbc8eac3326c 100644 (file)
@@ -1,3 +1,8 @@
+serverKeys({"oYhvA4N2a+PfWJ1aBVG3OFD/BBO/8sdkzRgGQoDxVz0=","2JjfJbIH/2g+1cIxj7IXhv4j38+rCiXbpdjtn91p/04="})
+accessKeys({"9RM9r+olHDJU+87hBXT9DCCej/DUS1XjIKWTq84AfTs=","ghv/LTqRTOgVvK8A/XEWrFks+F5fng1Wn14Xe9Rblgg="})
+
+controlSocket("0.0.0.0")
+
 -- define the good servers
 newServer("8.8.8.8", 2)  -- 2 qps
 newServer("8.8.4.4", 2) 
@@ -18,9 +23,6 @@ abuseShuntSMN("xxx.")
 abuseShuntNM("192.168.1.0/24")
 
 
-
-
-
 block=newDNSName("powerdns.org.")
 -- called before we distribute a question
 function blockFilter(remote, qname, qtype)
diff --git a/pdns/sodcrypto.cc b/pdns/sodcrypto.cc
new file mode 100644 (file)
index 0000000..f9c63a5
--- /dev/null
@@ -0,0 +1,94 @@
+#include <sodium.h>
+#include <iostream>
+#include "namespaces.hh"
+#include "misc.hh"
+#include "base64.hh"
+
+string newKeypair()
+{
+  unsigned char alice_publickey[crypto_box_PUBLICKEYBYTES];
+  unsigned char alice_secretkey[crypto_box_SECRETKEYBYTES];
+  crypto_box_keypair(alice_publickey, alice_secretkey);
+  
+  string ret("{\"");
+  ret+=Base64Encode(string((char*)alice_publickey, crypto_box_PUBLICKEYBYTES));
+  ret+="\",\"";
+  ret+=Base64Encode(string((char*)alice_secretkey, crypto_box_SECRETKEYBYTES));
+  ret+="\"}";
+  return ret;
+}
+
+// return: nonce + ciphertext
+
+std::string sodEncrypt(const std::string& msg, const std::string& secretSource,
+                 const std::string& publicDest)
+{
+  unsigned char nonce[crypto_box_NONCEBYTES];
+  unsigned char ciphertext[msg.length() + crypto_box_MACBYTES];
+  randombytes_buf(nonce, sizeof nonce);
+  /*
+  cerr<<"Encrypt plen: "<<msg.length()<<endl;
+  cerr<<"Encrypt nonce: "<<makeHexDump(string((const char*)nonce, sizeof nonce))<<endl;
+  cerr<<"keylen: "<<secretSource.length()<<", "<<publicDest.length()<<endl;
+  */
+  crypto_box_easy(ciphertext, (const unsigned char*)msg.c_str(), msg.length(), 
+                 nonce,  (const unsigned char*)publicDest.c_str(),   // bob_pub
+                 (const unsigned char*) secretSource.c_str());       // alice_sec
+  //  cerr<<"MAC: "<<makeHexDump(string((const char*)ciphertext, crypto_box_MACBYTES))<<endl;
+  string ret((const char*)nonce, crypto_box_NONCEBYTES);
+  ret.append((const char*)ciphertext, sizeof(ciphertext));
+  return ret;
+}
+
+std::string sodDecrypt(const std::string& msg, const std::string& publicSource,
+                 const std::string& secretDest)
+{
+  auto plen = msg.size() - crypto_box_NONCEBYTES - crypto_box_MACBYTES;
+  /*
+  cerr<<"Payload len: "<<plen<<endl;
+  cerr<<"Nonce: "<<makeHexDump(msg.substr(0, crypto_box_NONCEBYTES))<<endl;
+  cerr<<"MAC: "<<makeHexDump(msg.substr(crypto_box_NONCEBYTES, crypto_box_MACBYTES))<<endl;
+  cerr<<"keylen: "<<publicSource.length()<<", "<<secretDest.length()<<endl;
+  */
+  unsigned char decrypted[plen];
+  if (crypto_box_open_easy(decrypted, (const unsigned char*)msg.c_str() + crypto_box_NONCEBYTES, plen + crypto_box_MACBYTES, (const unsigned char*)msg.c_str(),
+                          (const unsigned char*)publicSource.c_str(),   // alice_pub
+                          (const unsigned char*)secretDest.c_str()) != 0) {  // bob_sec
+    /* message for Bob pretending to be from Alice has been forged! */
+    throw runtime_error("Could not decrypt!");
+  }
+
+  return string((char*)decrypted, plen);
+}
+
+
+void sodTest()
+{
+#define MESSAGE (const unsigned char *) "test"
+#define MESSAGE_LEN 4
+#define CIPHERTEXT_LEN (crypto_box_MACBYTES + MESSAGE_LEN)
+
+  unsigned char alice_publickey[crypto_box_PUBLICKEYBYTES];
+  unsigned char alice_secretkey[crypto_box_SECRETKEYBYTES];
+  crypto_box_keypair(alice_publickey, alice_secretkey);
+  
+  unsigned char bob_publickey[crypto_box_PUBLICKEYBYTES];
+  unsigned char bob_secretkey[crypto_box_SECRETKEYBYTES];
+  crypto_box_keypair(bob_publickey, bob_secretkey);
+  
+  unsigned char nonce[crypto_box_NONCEBYTES];
+  unsigned char ciphertext[CIPHERTEXT_LEN];
+  randombytes_buf(nonce, sizeof nonce);
+  crypto_box_easy(ciphertext, MESSAGE, MESSAGE_LEN, nonce,
+                 bob_publickey, alice_secretkey);
+  
+  unsigned char decrypted[MESSAGE_LEN];
+  if (crypto_box_open_easy(decrypted, ciphertext, CIPHERTEXT_LEN, nonce,
+                          alice_publickey, bob_secretkey) != 0) {
+    /* message for Bob pretending to be from Alice has been forged! */
+    cerr<<"BAD!"<<endl;
+  }
+  else 
+    cerr<<"Decrypted: "<<string((char*)decrypted, MESSAGE_LEN)<<endl;
+}
diff --git a/pdns/sodcrypto.hh b/pdns/sodcrypto.hh
new file mode 100644 (file)
index 0000000..e314c66
--- /dev/null
@@ -0,0 +1,14 @@
+#pragma once
+#include <string>
+
+void sodTest();
+std::string newKeypair();
+
+std::string sodEncrypt(const std::string& msg, const std::string& secretSource,
+                      const std::string& publicDest);
+
+
+std::string sodDecrypt(const std::string& msg, const std::string& publicSource,
+                      const std::string& secretDest);
+
+