Implementation of certificate install interface

- Copy the certificate and private Key file to the service
  specific path based on a configuration file.

- Reload the listed service for which the certificate is
  updated.

Change-Id: Iae7d340a0a2381502aef33762eb79b57ddeda07d
Signed-off-by: Jayanth Othayoth <ojayanth@in.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index 824d780..a597c38 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,6 +2,7 @@
 
 # Build these headers, don't install them
 noinst_HEADERS = \
+	certs_manager.hpp \
 	argument.hpp
 
 sbin_PROGRAMS = \
@@ -9,4 +10,14 @@
 
 phosphor_certificate_manager_SOURCES = \
 	mainapp.cpp \
+	certs_manager.cpp \
 	argument.cpp
+
+phosphor_certificate_manager_LDFLAGS = \
+	$(SDBUSPLUS_LIBS) \
+	$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+	-lstdc++fs
+
+phosphor_certificate_manager_CXXFLAGS = \
+	$(SYSTEMD_CFLAGS) \
+	$(PHOSPHOR_DBUS_INTERFACES_CFLAGS)
diff --git a/certs_manager.cpp b/certs_manager.cpp
new file mode 100644
index 0000000..d1ff25d
--- /dev/null
+++ b/certs_manager.cpp
@@ -0,0 +1,93 @@
+#include "certs_manager.hpp"
+
+#include <experimental/filesystem>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/bus.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+namespace phosphor
+{
+namespace certs
+{
+
+using namespace phosphor::logging;
+using InternalFailure =
+    sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+
+void Manager::install(const std::string path)
+{
+    // TODO Validate the certificate file
+
+    // Copy the certificate file
+    copy(path, certPath);
+
+    // Invoke type specific install function.
+    auto iter = typeFuncMap.find(type);
+    if (iter == typeFuncMap.end())
+    {
+        log<level::ERR>("Unsupported Type", entry("TYPE=%s", type.c_str()));
+        elog<InternalFailure>();
+    }
+    iter->second();
+}
+
+void Manager::serverInstall()
+{
+    if (!unit.empty())
+    {
+        reload(unit);
+    }
+}
+
+void Manager::clientInstall()
+{
+    // Do nothing now
+}
+
+void Manager::reload(const std::string& unit)
+{
+    constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1";
+    constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1";
+    constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";
+
+    try
+    {
+        auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
+                                          SYSTEMD_INTERFACE, "ReloadUnit");
+
+        method.append(unit, "replace");
+
+        bus.call_noreply(method);
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        log<level::ERR>("Failed to reload service", entry("ERR=%s", e.what()),
+                        entry("UNIT=%s", unit.c_str()));
+        elog<InternalFailure>();
+    }
+}
+
+void Manager::copy(const std::string& src, const std::string& dst)
+{
+    namespace fs = std::experimental::filesystem;
+
+    try
+    {
+        auto path = fs::path(dst).parent_path();
+        // create dst path folder by default
+        fs::create_directories(path);
+        fs::copy_file(src, dst, fs::copy_options::overwrite_existing);
+    }
+    catch (fs::filesystem_error& e)
+    {
+        log<level::ERR>("Failed to copy certificate", entry("ERR=%s", e.what()),
+                        entry("SRC=%s", src.c_str()),
+                        entry("DST=%s", dst.c_str()));
+        elog<InternalFailure>();
+    }
+}
+
+} // namespace certs
+} // namespace phosphor
diff --git a/certs_manager.hpp b/certs_manager.hpp
new file mode 100644
index 0000000..a23f044
--- /dev/null
+++ b/certs_manager.hpp
@@ -0,0 +1,107 @@
+#pragma once
+#include <cstring>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <unordered_map>
+#include <xyz/openbmc_project/Certs/Install/server.hpp>
+
+namespace phosphor
+{
+namespace certs
+{
+
+// Supported Types.
+static constexpr auto SERVER = "server";
+static constexpr auto CLIENT = "client";
+
+using CreateIface = sdbusplus::server::object::object<
+    sdbusplus::xyz::openbmc_project::Certs::server::Install>;
+using InstallFunc = std::function<void()>;
+using InputType = std::string;
+
+class Manager : public CreateIface
+{
+  public:
+    /* Define all of the basic class operations:
+     *     Not allowed:
+     *         - Default constructor is not possible due to member
+     *           reference
+     *         - Move operations due to 'this' being registered as the
+     *           'context' with sdbus.
+     *     Allowed:
+     *         - copy
+     *         - Destructor.
+     */
+    Manager() = delete;
+    Manager(const Manager&) = default;
+    Manager& operator=(const Manager&) = delete;
+    Manager(Manager&&) = delete;
+    Manager& operator=(Manager&&) = delete;
+    virtual ~Manager() = default;
+
+    /** @brief Constructor to put object onto bus at a dbus path.
+     *  @param[in] bus - Bus to attach to.
+     *  @param[in] path - Path to attach at.
+     *  @param[in] type - Type of the certificate.
+     *  @param[in] unit - Unit consumed by this certificate.
+     *  @param[in] certpath - Certificate installation path.
+     */
+    Manager(sdbusplus::bus::bus& bus, const char* path, const std::string& type,
+            std::string&& unit, std::string&& certPath) :
+        CreateIface(bus, path),
+        bus(bus), path(path), type(type), unit(std::move(unit)),
+        certPath(std::move(certPath))
+    {
+        typeFuncMap[SERVER] =
+            std::bind(&phosphor::certs::Manager::serverInstall, this);
+        typeFuncMap[CLIENT] =
+            std::bind(&phosphor::certs::Manager::clientInstall, this);
+    }
+
+    /** @brief Implementation for Install
+     *  Replace the existing certificate key file with another
+     *  (possibly CA signed) Certificate key file.
+     *
+     *  @param[in] path - Certificate key file path.
+     */
+    void install(const std::string path) override;
+
+  private:
+    /** @brief Client certificate Installation helper function **/
+    void clientInstall();
+
+    /** @brief Server certificate Installation helper function **/
+    void serverInstall();
+
+    /** @brief systemd unit reload helper function
+     * @param[in] unit - service need to reload.
+     */
+    void reload(const std::string& unit);
+
+    /** @brief helper function to copy the file.
+     *  @param[in] src - Source file path to copy
+     *  @param[in] dst - Destination path to copy
+     */
+    void copy(const std::string& src, const std::string& dst);
+
+    /** @brief sdbusplus handler */
+    sdbusplus::bus::bus& bus;
+
+    /** @brief object path */
+    std::string path;
+
+    /** @brief Type of the certificate **/
+    InputType type;
+
+    /** @brief Unit name associated to the service **/
+    std::string unit;
+
+    /** @brief Certificate file installation path **/
+    std::string certPath;
+
+    /** @brief Type specific function pointer map **/
+    std::unordered_map<InputType, InstallFunc> typeFuncMap;
+};
+
+} // namespace certs
+} // namespace phosphor
diff --git a/configure.ac b/configure.ac
index e89eea5..02a0a94 100644
--- a/configure.ac
+++ b/configure.ac
@@ -18,6 +18,24 @@
 AX_CXX_COMPILE_STDCXX([17], [noext], [mandatory])
 AX_APPEND_COMPILE_FLAGS([-Wall -Werror], [CXXFLAGS])
 
+# Check for libraries
+PKG_CHECK_MODULES([PHOSPHOR_DBUS_INTERFACES], [phosphor-dbus-interfaces],,\
+    AC_MSG_ERROR(["Requires phosphor-dbus-interfaces package."]))
+PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus],,
+    AC_MSG_ERROR(["Requires sdbusplus package."]))
+PKG_CHECK_MODULES([PHOSPHOR_LOGGING], [phosphor-logging],,\
+    AC_MSG_ERROR(["Requires phosphor-logging package."]))
+
+# Code coverage
+AX_CODE_COVERAGE
+
+AC_ARG_VAR(BUSNAME, [The D-Bus busname to own])
+AS_IF([test "x$BUSNAME" == "x"], [BUSNAME="xyz.openbmc_project.Certs.Manager"])
+AC_DEFINE_UNQUOTED([BUSNAME], ["$BUSNAME"], [The D-Bus busname to own])
+AC_ARG_VAR(OBJPATH, [The certificate manager D-Bus root])
+AS_IF([test "x$OBJPATH" == "x"], [OBJPATH="/xyz/openbmc_project/certs"])
+AC_DEFINE_UNQUOTED([OBJPATH], ["$OBJPATH"], [The certificate manager D-Bus root])
+
 # Create configured output
 AC_CONFIG_FILES([Makefile])
 AC_OUTPUT
diff --git a/mainapp.cpp b/mainapp.cpp
index bbe2a85..5b0f29f 100644
--- a/mainapp.cpp
+++ b/mainapp.cpp
@@ -14,9 +14,13 @@
  * limitations under the License.
  */
 
+#include "config.h"
+
 #include "argument.hpp"
+#include "certs_manager.hpp"
 
 #include <iostream>
+#include <locale>
 #include <string>
 
 static void ExitWithError(const char* err, char** argv)
@@ -27,6 +31,11 @@
     exit(EXIT_FAILURE);
 }
 
+inline void capitalize(std::string& s)
+{
+    s[0] = std::toupper(s[0]);
+}
+
 int main(int argc, char** argv)
 {
     // Read arguments.
@@ -34,9 +43,11 @@
 
     // Parse arguments
     auto type = std::move((options)["type"]);
-    if (type == phosphor::certs::util::ArgumentParser::empty_string)
+    if ((type == phosphor::certs::util::ArgumentParser::empty_string) ||
+        !((type == phosphor::certs::SERVER) ||
+          (type == phosphor::certs::CLIENT)))
     {
-        ExitWithError("type not specified.", argv);
+        ExitWithError("type not specified or invalid.", argv);
     }
 
     auto endpoint = std::move((options)["endpoint"]);
@@ -51,8 +62,27 @@
         ExitWithError("path not specified.", argv);
     }
 
-    // unit is an optional parametr
+    // unit is an optional parameter
     auto unit = std::move((options)["unit"]);
 
+    auto bus = sdbusplus::bus::new_default();
+
+    auto objPath = std::string(OBJPATH) + '/' + type + '/' + endpoint;
+
+    phosphor::certs::Manager manager(bus, objPath.c_str(), type,
+                                     std::move(unit), std::move(path));
+
+    // Adjusting Interface name as per std convention
+    capitalize(type);
+    capitalize(endpoint);
+    auto busName = std::string(BUSNAME) + '.' + type + '.' + endpoint;
+    bus.request_name(busName.c_str());
+
+    while (true)
+    {
+        // process dbus calls / signals discarding unhandled
+        bus.process_discard();
+        bus.wait();
+    }
     return 0;
 }