tools/ndns-daemon: fix bug that validator cannot fetch certificate

refs #2206

Change-Id: I10c69d95e6a5fd020038211ce419763fd62afcb3
diff --git a/ndns.conf.sample.in b/ndns.conf.sample.in
index 71e26a2..b2b968a 100644
--- a/ndns.conf.sample.in
+++ b/ndns.conf.sample.in
@@ -1,6 +1,7 @@
 zones
 {
-  ; dbFile /usr/local/var/ndns/ndns.db
+  ; dbFile @DEFAULT_DATABASE_PATH@/ndns.db
+  ; validatorConfigFile @DEFAULT_CONFIG_PATH@/validator.conf
 
   zone
   {
@@ -18,6 +19,7 @@
              ; omit cert to select the default certificate of above identity
   ; }
 }
+
 hints
 {
   ; hint /ucla
diff --git a/src/daemon/name-server.cpp b/src/daemon/name-server.cpp
index 072e889..b02a423 100644
--- a/src/daemon/name-server.cpp
+++ b/src/daemon/name-server.cpp
@@ -137,7 +137,9 @@
     m_validator.validate(*data,
                          bind(&NameServer::doUpdate, this, interest.shared_from_this(), data),
                          [this] (const shared_ptr<const Data>& data, const std::string& msg) {
-                           NDNS_LOG_WARN("Ignoring update that did not pass the verification");
+                           NDNS_LOG_WARN("Ignoring update that did not pass the verification. "
+                                         << "Validator cannot fetch certificate from the face "
+                                         << "that used by validator itself to send Interest");
                          });
   }
 }
@@ -213,7 +215,8 @@
     blk.push_back(nonNegativeIntegerBlock(ndn::ndns::tlv::UpdateReturnCode, UPDATE_FAILURE));
     blk.encode(); // must
     answer->setContent(blk);
-    NDNS_LOG_INFO("Error processing the update: " << e.what());
+    NDNS_LOG_INFO("Error processing the update: " << e.what()
+                  << ". Update may need sudo privilege to write DbFile");
     NDNS_LOG_TRACE("exception happens and answer update with UPDATE_FAILURE");
   }
   m_keyChain.sign(*answer, m_certName);
diff --git a/tests/unit/daemon/name-server.cpp b/tests/unit/daemon/name-server.cpp
index 578533d..f721a5e 100644
--- a/tests/unit/daemon/name-server.cpp
+++ b/tests/unit/daemon/name-server.cpp
@@ -41,7 +41,7 @@
 public:
   NameServerFixture()
     : face(ndn::util::makeDummyClientFace({ false, true }))
-    , zone(m_net.getName())
+    , zone(m_root.getName())
     , validator(*face)
     , server(zone, m_certName, *face, m_session, m_keyChain, validator)
   {
@@ -69,7 +69,7 @@
 BOOST_AUTO_TEST_CASE(NdnsQuery)
 {
   Query q(hint, zone, ndns::label::NDNS_ITERATIVE_QUERY);
-  q.setRrLabel(Name("ndnsim"));
+  q.setRrLabel(Name("net"));
   q.setRrType(ndns::label::NS_RR_TYPE);
 
   bool hasDataBack = false;
@@ -125,7 +125,7 @@
     BOOST_CHECK_EQUAL(resp.getNdnsType(), NDNS_RAW);
   };
 
-  q.setRrLabel("dsk-3");
+  q.setRrLabel("dsk-1");
 
   face->receive(q.toInterest());
   run();
@@ -138,7 +138,7 @@
   Response re;
   re.setZone(zone);
   re.setQueryType(label::NDNS_ITERATIVE_QUERY);
-  re.setRrLabel(Name("ndnsim"));
+  re.setRrLabel(Name("net"));
   re.setRrType(label::NS_RR_TYPE);
   re.setNdnsType(NDNS_RESP);
 
@@ -191,7 +191,7 @@
   Response re;
   re.setZone(zone);
   re.setQueryType(label::NDNS_ITERATIVE_QUERY);
-  re.setRrLabel(Name("ndnsim-XYZ")); // insert new records
+  re.setRrLabel(Name("net-XYZ")); // insert new records
   re.setRrType(label::NS_RR_TYPE);
   re.setNdnsType(NDNS_RESP);
 
@@ -239,6 +239,188 @@
   BOOST_CHECK_EQUAL(hasDataBack, true);
 }
 
+BOOST_AUTO_TEST_CASE(UpdateValidatorCannotFetchCert)
+{
+  Name dskName = m_keyChain.generateRsaKeyPair(TEST_IDENTITY_NAME, false);
+  std::vector<CertificateSubjectDescription> desc;
+  time::system_clock::TimePoint notBefore = time::system_clock::now();
+  time::system_clock::TimePoint notAfter = notBefore + time::days(365);
+  shared_ptr<IdentityCertificate> dskCert =
+    m_keyChain.prepareUnsignedIdentityCertificate(dskName, m_certName,
+                                                  notBefore, notAfter, desc);
+
+  m_keyChain.sign(*dskCert, m_certName);
+  m_keyChain.addCertificateAsKeyDefault(*dskCert);
+  NDNS_LOG_TRACE("KeyChain: add cert: " << dskCert->getName() << ". KeyLocator: "
+                 << dskCert->getSignature().getKeyLocator().getName());
+
+  Rrset rrset(&m_root);
+  Name label = dskCert->getName().getPrefix(-2).getSubName(m_root.getName().size() + 1);
+  rrset.setLabel(label);
+  rrset.setType(label::CERT_RR_TYPE);
+  rrset.setVersion(dskCert->getName().get(-1));
+  rrset.setTtl(m_root.getTtl());
+  rrset.setData(dskCert->wireEncode());
+  m_session.insert(rrset);
+  NDNS_LOG_TRACE("DB: zone " << m_root << " add a ID-CERT RR with name="
+                 << dskCert->getName() << " rrLabel=" << label);
+
+  Response re;
+  re.setZone(zone);
+  re.setQueryType(label::NDNS_ITERATIVE_QUERY);
+  re.setRrLabel(Name("ndnsim-XYZ")); // insert new records
+  re.setRrType(label::NS_RR_TYPE);
+  re.setNdnsType(NDNS_RESP);
+
+  std::string str = "ns1.ndnsim.net";
+  re.addRr(dataBlock(ndns::tlv::RrData, str.c_str(), str.size()));
+  str = "ns2.ndnsim.net";
+  re.addRr(dataBlock(ndns::tlv::RrData, str.c_str(), str.size()));
+
+  shared_ptr<Data> data = re.toData();
+  m_keyChain.sign(*data, dskCert->getName());
+
+  Query q(Name(hint), Name(zone), ndns::label::NDNS_ITERATIVE_QUERY);
+  const Block& block = data->wireEncode();
+  Name name;
+  name.append(block);
+
+  q.setRrLabel(name);
+  q.setRrType(label::NDNS_UPDATE_LABEL);
+
+  bool hasDataBack = false;
+
+  // no data back, since the Update cannot pass verification
+  face->onData += [&] (const Data& data) {
+    hasDataBack = true;
+    BOOST_FAIL("UNEXPECTED");
+  };
+
+  face->receive(q.toInterest());
+  run();
+
+  BOOST_CHECK_EQUAL(hasDataBack, false);
+}
+
+class NameServerFixture2 : public DbTestData
+{
+public:
+  NameServerFixture2()
+    : face(ndn::util::makeDummyClientFace(io, { false, true }))
+    , validatorFace(ndn::util::makeDummyClientFace(io, { false, true }))
+    , zone(m_root.getName())
+    , validator(*validatorFace) // different face for validator
+    , server(zone, m_certName, *face, m_session, m_keyChain, validator)
+  {
+    // ensure prefix is registered
+    run();
+    validatorFace->onInterest += [&] (const Interest& interest) {
+      NDNS_LOG_TRACE("validatorFace get Interest: " << interest.getName());
+      face->receive(interest);
+    };
+  }
+
+  void
+  run()
+  {
+    io.poll();
+    io.reset();
+  }
+
+public:
+  boost::asio::io_service io;
+  shared_ptr<ndn::util::DummyClientFace> face;
+  shared_ptr<ndn::util::DummyClientFace> validatorFace;
+  Name hint;
+  const Name& zone;
+  Validator validator;
+  ndns::NameServer server;
+};
+
+BOOST_FIXTURE_TEST_CASE(UpdateValidatorFetchCert, NameServerFixture2)
+{
+  Name dskName = m_keyChain.generateRsaKeyPair(TEST_IDENTITY_NAME, false);
+  std::vector<CertificateSubjectDescription> desc;
+  time::system_clock::TimePoint notBefore = time::system_clock::now();
+  time::system_clock::TimePoint notAfter = notBefore + time::days(365);
+  shared_ptr<IdentityCertificate> dskCert =
+    m_keyChain.prepareUnsignedIdentityCertificate(dskName, m_certName,
+                                                  notBefore, notAfter, desc);
+
+  m_keyChain.sign(*dskCert, m_certName);
+  m_keyChain.addCertificateAsKeyDefault(*dskCert);
+  NDNS_LOG_TRACE("KeyChain: add cert: " << dskCert->getName() << ". KeyLocator: "
+                 << dskCert->getSignature().getKeyLocator().getName());
+
+  Rrset rrset(&m_root);
+  Name label = dskCert->getName().getPrefix(-2).getSubName(m_root.getName().size() + 1);
+  rrset.setLabel(label);
+  rrset.setType(label::CERT_RR_TYPE);
+  rrset.setVersion(dskCert->getName().get(-1));
+  rrset.setTtl(m_root.getTtl());
+  rrset.setData(dskCert->wireEncode());
+  m_session.insert(rrset);
+  NDNS_LOG_TRACE("DB: zone " << m_root << " add a ID-CERT RR with name="
+                 << dskCert->getName() << " rrLabel=" << label);
+
+  Response re;
+  re.setZone(zone);
+  re.setQueryType(label::NDNS_ITERATIVE_QUERY);
+  re.setRrLabel(Name("ndnsim-XYZ")); // insert new records
+  re.setRrType(label::NS_RR_TYPE);
+  re.setNdnsType(NDNS_RESP);
+
+  std::string str = "ns1.ndnsim.net";
+  re.addRr(dataBlock(ndns::tlv::RrData, str.c_str(), str.size()));
+  str = "ns2.ndnsim.net";
+  re.addRr(dataBlock(ndns::tlv::RrData, str.c_str(), str.size()));
+
+  shared_ptr<Data> data = re.toData();
+  m_keyChain.sign(*data, dskCert->getName());
+
+  Query q(Name(hint), Name(zone), ndns::label::NDNS_ITERATIVE_QUERY);
+  const Block& block = data->wireEncode();
+  Name name;
+  name.append(block);
+
+  q.setRrLabel(name);
+  q.setRrType(label::NDNS_UPDATE_LABEL);
+
+  bool hasDataBack = false;
+
+  shared_ptr<Regex> regex = make_shared<Regex>("(<>*)<KEY>(<>+)<ID-CERT><>");
+  face->onData += [&] (const Data& data) {
+    if (regex->match(data.getName())) {
+      validatorFace->receive(data); // It's data requested by validator
+    }
+    else {
+      // cert is requested by validator
+      hasDataBack = true;
+      NDNS_LOG_TRACE("get Data back");
+      BOOST_CHECK_EQUAL(data.getName().getPrefix(-1), q.toInterest().getName());
+      Response resp;
+
+      BOOST_CHECK_NO_THROW(resp.fromData(hint, zone, data));
+      std::cout << resp << std::endl;
+      BOOST_CHECK_EQUAL(resp.getNdnsType(), NDNS_RESP); // by default NDNS_RAW is enough
+      BOOST_CHECK_GT(resp.getRrs().size(), 0);
+      Block block = resp.getRrs()[0];
+      block.parse();
+      int ret = -1;
+      BOOST_CHECK_EQUAL(block.type(), ndns::tlv::RrData);
+      Block::element_const_iterator val = block.elements_begin();
+      BOOST_CHECK_EQUAL(val->type(), ndns::tlv::UpdateReturnCode); // the first must be return code
+      ret = readNonNegativeInteger(*val);
+      BOOST_CHECK_EQUAL(ret, 0);
+    }
+  };
+
+  face->receive(q.toInterest());
+  run();
+
+  BOOST_CHECK_EQUAL(hasDataBack, true);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // namespace tests
diff --git a/tools/ndns-daemon.cpp b/tools/ndns-daemon.cpp
index 463022a..5937562 100644
--- a/tools/ndns-daemon.cpp
+++ b/tools/ndns-daemon.cpp
@@ -36,18 +36,17 @@
  */
 class NdnsDaemon : noncopyable
 {
+public:
   DEFINE_ERROR(Error, std::runtime_error);
 
-public:
   explicit
-  NdnsDaemon(const std::string& configFile, Face& face)
-    : m_configFile(configFile)
-    , m_face(face)
-    , m_validator(face)
+  NdnsDaemon(const std::string& configFile, Face& face, Face& validatorFace)
+    : m_face(face)
+    , m_validatorFace(validatorFace)
   {
     try {
       ConfigFile config;
-      NDNS_LOG_TRACE("configFile: " << configFile);
+      NDNS_LOG_INFO("NnsnDaemon ConfigFile = " << configFile);
 
       config.addSectionHandler("zones",
                                bind(&NdnsDaemon::processZonesSection, this, _1, _3));
@@ -88,16 +87,21 @@
       throw Error("zones section is empty");
     }
 
+    std::string dbFile = DEFAULT_DATABASE_PATH "/" "ndns.db";
     ConfigSection::const_assoc_iterator item = section.find("dbFile");
     if (item != section.not_found()) {
-      m_dbFile = item->second.get_value<std::string>();
+      dbFile = item->second.get_value<std::string>();
     }
-    else {
-      m_dbFile = "/usr/local/var/ndns/ndns.db";
-    }
-    NDNS_LOG_TRACE("dbFile " << m_dbFile);
+    NDNS_LOG_INFO("DbFile = " << dbFile);
+    m_dbMgr = unique_ptr<DbMgr>(new DbMgr(dbFile));
 
-    m_dbMgr = make_shared<DbMgr>(m_dbFile);
+    std::string validatorConfigFile = DEFAULT_CONFIG_PATH "/" "validator.conf";
+    item = section.find("validatorConfigFile");
+    if (item != section.not_found()) {
+      validatorConfigFile = item->second.get_value<std::string>();
+    }
+    NDNS_LOG_INFO("ValidatorConfigFile = " << validatorConfigFile);
+    m_validator = unique_ptr<Validator>(new Validator(m_validatorFace, validatorConfigFile));
 
     for (const auto& option : section) {
       Name name;
@@ -117,8 +121,21 @@
           ;
         }
 
+
+        if (!m_keyChain.doesIdentityExist(name)) {
+          NDNS_LOG_FATAL("Identity: " << name << " does not exist in the KeyChain");
+          throw Error("Identity does not exist in the KeyChain");
+        }
+
         if (cert.empty()) {
-          cert = m_keyChain.getDefaultCertificateNameForIdentity(name);
+          try {
+            cert = m_keyChain.getDefaultCertificateNameForIdentity(name);
+          }
+          catch (std::exception& e) {
+            NDNS_LOG_FATAL("Identity: " << name << " does not have default certificate. "
+                           << e.what());
+            throw Error("identity does not have default certificate");
+          }
         }
         else {
           if (!m_keyChain.doesCertificateExist(cert)) {
@@ -127,18 +144,17 @@
         }
         NDNS_LOG_TRACE("name = " << name << " cert = " << cert);
         m_servers.push_back(make_shared<NameServer>(name, cert, m_face, *m_dbMgr,
-                                                    m_keyChain, m_validator));
+                                                    m_keyChain, *m_validator));
       }
     } // for
   }
 
 private:
-  std::string m_configFile;
   Face& m_face;
-  Validator m_validator;
-  std::string m_dbFile;
-  shared_ptr<DbMgr> m_dbMgr;
-  std::vector<shared_ptr<NameServer> > m_servers;
+  Face& m_validatorFace;
+  unique_ptr<Validator> m_validator;
+  unique_ptr<DbMgr> m_dbMgr;
+  std::vector<shared_ptr<NameServer>> m_servers;
   KeyChain m_keyChain;
 };
 
@@ -194,21 +210,24 @@
     return 1;
   }
 
+  boost::asio::io_service io;
+  ndn::Face face(io);
+  ndn::Face validatorFace(io);
+
   try {
-    ndn::Face face;
-    NdnsDaemon nsd(configFile, face);
+    // NFD does not to forward Interests to the face it was received from.
+    // If the name server and its validator share same face,
+    // the validator cannot be forwarded to the name server itself
+    // refs: http://redmine.named-data.net/issues/2206
+    // @TODO enhance validator to get the certificate from the local db if it has
 
-    boost::asio::signal_set signalSet(face.getIoService(), SIGINT, SIGTERM);
-
-    signalSet.async_wait([&face] (const boost::system::error_code&, const int) {
-        face.getIoService().stop();
-      });
+    NdnsDaemon daemon(configFile, face, validatorFace);
 
     face.processEvents();
   }
   catch (std::exception& e) {
     NDNS_LOG_FATAL("ERROR: " << e.what());
-    return 2;
+    return 1;
   }
 
   return 0;
diff --git a/wscript b/wscript
index dd3807e..26f4fc8 100644
--- a/wscript
+++ b/wscript
@@ -98,6 +98,8 @@
         name='validator-sample',
         ANCHORPATH='anchors/root.cert',
         RELATION='is-prefix-of',
+        DEFAULT_CONFIG_PATH="%s/ndns" % bld.env['SYSCONFDIR'],
+        DEFAULT_DATABASE_PATH="%s/ndns" % bld.env['LOCALSTATEDIR'],
         help='the validator configuration of ndns',
     )