Add repo output to command line tool ndncert-ca-server

refs: #4455

Change-Id: I8e451af5c636bb52e6171ad2d82b43e6ce77f07f
diff --git a/src/ca-config.hpp b/src/ca-config.hpp
index 2192248..a0e66ae 100644
--- a/src/ca-config.hpp
+++ b/src/ca-config.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017, Regents of the University of California.
+ * Copyright (c) 2017-2018, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -46,9 +46,13 @@
                                      const std::list<Name>&/*related CA list*/)>;
 
 /**
- * @brief The function would be invoked whenever the certificate request gets update
+ * @brief The function would be invoked whenever the certificate request status gets update
+ *
+ * The callback is used to notice the CA application or CA command line tool. The callback is
+ * fired whenever a request instance is created, challenge status is updated, and when certificate
+ * is issued.
  */
-using RequestUpdateCallback = function<void (const CertificateRequest&/*the latest request info*/)>;
+using StatusUpdateCallback = function<void (const CertificateRequest&/*the latest request info*/)>;
 
 class CaItem
 {
@@ -72,7 +76,7 @@
   // callbacks
   ProbeHandler m_probeHandler;
   RecommendCaHandler m_recommendCaHandler;
-  RequestUpdateCallback m_requestUpdateCallback;
+  StatusUpdateCallback m_statusUpdateCallback;
 };
 
 /**
diff --git a/src/ca-module.cpp b/src/ca-module.cpp
index f2aa498..a6954a8 100644
--- a/src/ca-module.cpp
+++ b/src/ca-module.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017, Regents of the University of California.
+ * Copyright (c) 2017-2018, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -110,34 +110,40 @@
   }
 }
 
-void
+bool
 CaModule::setProbeHandler(const Name caName, const ProbeHandler& handler)
 {
   for (auto& entry : m_config.m_caItems) {
     if (entry.m_caName == caName) {
       entry.m_probeHandler = handler;
+      return true;
     }
   }
+  return false;
 }
 
-void
+bool
 CaModule::setRecommendCaHandler(const Name caName, const RecommendCaHandler& handler)
 {
   for (auto& entry : m_config.m_caItems) {
     if (entry.m_caName == caName) {
       entry.m_recommendCaHandler = handler;
+      return true;
     }
   }
+  return false;
 }
 
-void
-CaModule::setRequestUpdateCallback(const Name caName, const RequestUpdateCallback& onUpateCallback)
+bool
+CaModule::setStatusUpdateCallback(const Name caName, const StatusUpdateCallback& onUpateCallback)
 {
   for (auto& entry : m_config.m_caItems) {
     if (entry.m_caName == caName) {
-      entry.m_requestUpdateCallback = onUpateCallback;
+      entry.m_statusUpdateCallback = onUpateCallback;
+      return true;
     }
   }
+  return false;
 }
 
 void
@@ -330,8 +336,8 @@
   m_keyChain.sign(result, signingByIdentity(caItem.m_caName));
   m_face.put(result);
 
-  if (caItem.m_requestUpdateCallback) {
-    caItem.m_requestUpdateCallback(certRequest);
+  if (caItem.m_statusUpdateCallback) {
+    caItem.m_statusUpdateCallback(certRequest);
   }
 }
 
@@ -386,8 +392,8 @@
   m_keyChain.sign(result, signingByIdentity(caItem.m_caName));
   m_face.put(result);
 
-  if (caItem.m_requestUpdateCallback) {
-    caItem.m_requestUpdateCallback(certRequest);
+  if (caItem.m_statusUpdateCallback) {
+    caItem.m_statusUpdateCallback(certRequest);
   }
 }
 
@@ -433,12 +439,21 @@
   m_keyChain.sign(result, signingByIdentity(caItem.m_caName));
   m_face.put(result);
 
-  if (caItem.m_requestUpdateCallback) {
-    caItem.m_requestUpdateCallback(certRequest);
-  }
-
   if (certRequest.getStatus() == ChallengeModule::SUCCESS) {
-    issueCertificate(certRequest, caItem);
+    auto issuedCert = issueCertificate(certRequest, caItem);
+    if (caItem.m_statusUpdateCallback) {
+      certRequest.setCert(issuedCert);
+      caItem.m_statusUpdateCallback(certRequest);
+    }
+    try {
+      m_storage->addCertificate(certRequest.getRequestId(), issuedCert);
+      m_storage->deleteRequest(certRequest.getRequestId());
+      _LOG_TRACE("New Certificate Issued " << issuedCert.getName());
+    }
+    catch (const std::exception& e) {
+      _LOG_ERROR("Cannot add issued cert and remove the request " << e.what());
+      return;
+    }
   }
 }
 
@@ -533,7 +548,7 @@
   m_face.put(result);
 }
 
-void
+security::v2::Certificate
 CaModule::issueCertificate(const CertificateRequest& certRequest, const CaItem& caItem)
 {
   Name certName = certRequest.getCert().getKeyName();
@@ -552,15 +567,7 @@
 
   m_keyChain.sign(newCert, signingInfo);
   _LOG_TRACE("new cert got signed" << newCert);
-  try {
-    m_storage->addCertificate(certRequest.getRequestId(), newCert);
-    m_storage->deleteRequest(certRequest.getRequestId());
-    _LOG_TRACE("New Certificate Issued " << certName);
-  }
-  catch (const std::exception& e) {
-    _LOG_ERROR("Cannot add issued cert and remove the request " << e.what());
-    return;
-  }
+  return newCert;
 }
 
 CertificateRequest
diff --git a/src/ca-module.hpp b/src/ca-module.hpp
index 5ef9ced..23386a2 100644
--- a/src/ca-module.hpp
+++ b/src/ca-module.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017, Regents of the University of California.
+ * Copyright (c) 2017-2018, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -58,14 +58,14 @@
     return m_storage;
   }
 
-  void
+  bool
   setProbeHandler(const Name caName, const ProbeHandler& handler);
 
-  void
+  bool
   setRecommendCaHandler(const Name caName, const RecommendCaHandler& handler);
 
-  void
-  setRequestUpdateCallback(const Name caName, const RequestUpdateCallback& onUpateCallback);
+  bool
+  setStatusUpdateCallback(const Name caName, const StatusUpdateCallback& onUpateCallback);
 
 PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   void
@@ -98,7 +98,7 @@
   CertificateRequest
   getCertificateRequest(const Interest& request, const Name& caName);
 
-  void
+  security::v2::Certificate
   issueCertificate(const CertificateRequest& certRequest, const CaItem& caItem);
 
   static JsonSection
diff --git a/src/certificate-request.hpp b/src/certificate-request.hpp
index 29e3823..d14b74d 100644
--- a/src/certificate-request.hpp
+++ b/src/certificate-request.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017, Regents of the University of California.
+ * Copyright (c) 2017-2018, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -86,6 +86,12 @@
   }
 
   void
+  setCert(security::v2::Certificate cert)
+  {
+    m_cert = std::move(cert);
+  }
+
+  void
   setStatus(const std::string& status)
   {
     m_status = status;
diff --git a/tools/ndncert-ca-server.cpp b/tools/ndncert-ca-server.cpp
index 7377fc1..9e25555 100644
--- a/tools/ndncert-ca-server.cpp
+++ b/tools/ndncert-ca-server.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2017, Regents of the University of California.
+ * Copyright (c) 2017-2018, Regents of the University of California.
  *
  * This file is part of ndncert, a certificate management system based on NDN.
  *
@@ -19,26 +19,45 @@
  */
 
 #include "ca-module.hpp"
+#include "challenge-module.hpp"
 
 #include <iostream>
-
+#include <sstream>
+#include <string>
+#include <ndn-cxx/util/io.hpp>
 #include <boost/program_options/options_description.hpp>
 #include <boost/program_options/variables_map.hpp>
 #include <boost/program_options/parsers.hpp>
+#include <boost/date_time/posix_time/posix_time_duration.hpp>
+#include <boost/asio.hpp>
 
 namespace ndn {
 namespace ndncert {
 
-
 int
 main(int argc, char* argv[])
 {
-  namespace po = boost::program_options;
   std::string configFilePath = std::string(SYSCONFDIR) + "/ndncert/ca.conf";
-  po::options_description description("General Usage\n  ndncert-ca [-h] [-f] configFilePath-file\n");
+  std::string repoPrefix;
+  std::string repoCaIdentity;
+  std::string repoHost;
+  std::string repoPort;
+  bool isRepoOut = false;
+
+  namespace po = boost::program_options;
+  po::options_description description("General Usage\n  ndncert-ca [-h] [-f] [-r] [-c]\n");
   description.add_options()
-    ("help,h", "produce help message")
-    ("config-file,f", po::value<std::string>(&configFilePath), "config file name");
+    ("help,h",
+     "produce help message")
+    ("config-file,f", po::value<std::string>(&configFilePath),
+     "config file name")
+    ("repo-output,r",
+     "when enabled, all issued certificates will be published to repo-ng")
+    ("repo-host,H", po::value<std::string>(&repoHost)->default_value("localhost"),
+     "repo-ng host")
+    ("repo-port,P", po::value<std::string>(&repoPort)->default_value("7376"),
+     "repo-ng port");
+
   po::positional_options_description p;
   po::variables_map vm;
   try {
@@ -46,13 +65,17 @@
     po::notify(vm);
   }
   catch (const std::exception& e) {
-    std::cerr << "ERROR: " << e.what() << std::endl;
+    std::cerr << "ERROR: " << e.what()
+              << "\n" << description << std::endl;
     return 1;
   }
   if (vm.count("help") != 0) {
     std::cerr << description << std::endl;
     return 0;
   }
+  if (vm.count("repo-ng-output") != 0) {
+    isRepoOut = true;
+  }
 
   Face face;
   security::v2::KeyChain keyChain;
@@ -73,6 +96,30 @@
       return std::make_tuple(recommendedCa, identity);
     });
 
+  if (isRepoOut) {
+    auto config = ca.getCaConf();
+    for (const auto& caItem : config.m_caItems) {
+      ca.setStatusUpdateCallback(caItem.m_caName,
+        [&] (const CertificateRequest& request) {
+          if (request.getStatus() == ChallengeModule::SUCCESS) {
+            auto issuedCert = request.getCert();
+            using namespace boost::asio::ip;
+            tcp::iostream requestStream;
+            requestStream.expires_from_now(boost::posix_time::seconds(3));
+            requestStream.connect(repoHost, repoPort);
+            if (!requestStream) {
+              std::cerr << "ERROR: Cannot publish certificate to repo-ng"
+                        << " (" << requestStream.error().message() << ")"
+                        << std::endl;
+              return;
+            }
+            requestStream.write(reinterpret_cast<const char*>(issuedCert.wireEncode().wire()),
+                                issuedCert.wireEncode().size());
+          }
+        });
+    }
+  }
+
   face.processEvents();
   return 0;
 }