phosphor-ldap-conf: add application to configure LDAP

The application implements the xyz.openbmc_project.User.Ldap.Config
and xyz.openbmc_project.User.Ldap.Create D-Bus interfaces to create
LDAP config file(for example generate nslcd.conf)

Change-Id: Idc7cc643c4143f9bc51182019926e1dd6125da2f
Signed-off-by: Nagaraju Goruganti <ngorugan@in.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index 915eedc..4413b84 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -23,4 +23,5 @@
                                  -DBOOST_SYSTEM_NO_DEPRECATED \
                                  -DBOOST_ERROR_CODE_HEADER_ONLY
 
-SUBDIRS = . test phosphor-ldap-mapper
+SUBDIRS = . test phosphor-ldap-mapper phosphor-ldap-config
+
diff --git a/configure.ac b/configure.ac
index c190307..5d15fe8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -63,6 +63,26 @@
     AC_SUBST([OESDK_TESTCASE_FLAGS], [$testcase_flags])
 )
 
+AC_ARG_VAR(LDAP_CONFIG_FILE, [Path of LDAP configuration file])
+AS_IF([test "x$LDAP_CONFIG_FILE" == "x"], [LDAP_CONFIG_FILE="/etc/nslcd.conf"])
+AC_DEFINE_UNQUOTED([LDAP_CONFIG_FILE], ["$LDAP_CONFIG_FILE"], [Path of LDAP configuration file])
+
+AC_ARG_VAR(LDAP_CONFIG_ROOT, [LDAP configuration root])
+AS_IF([test "x$LDAP_CONFIG_ROOT" == "x"], [LDAP_CONFIG_ROOT="/xyz/openbmc_project/user/ldap"])
+AC_DEFINE_UNQUOTED([LDAP_CONFIG_ROOT], ["$LDAP_CONFIG_ROOT"], [LDAP configuration root])
+
+AC_ARG_VAR(LDAP_CONFIG_DBUS_OBJ_PATH, [D-Bus path of LDAP config object])
+AS_IF([test "x$LDAP_CONFIG_DBUS_OBJ_PATH" == "x"], [LDAP_CONFIG_DBUS_OBJ_PATH="/xyz/openbmc_project/user/ldap/config"])
+AC_DEFINE_UNQUOTED([LDAP_CONFIG_DBUS_OBJ_PATH], ["$LDAP_CONFIG_DBUS_OBJ_PATH"], [D-Bus path of LDAP config object])
+
+AC_ARG_VAR(LDAP_CONFIG_BUSNAME, [D-Bus busname of LDAP config service])
+AS_IF([test "x$LDAP_CONFIG_BUSNAME" == "x"], [LDAP_CONFIG_BUSNAME="xyz.openbmc_project.Ldap.Config"])
+AC_DEFINE_UNQUOTED([LDAP_CONFIG_BUSNAME], ["$LDAP_CONFIG_BUSNAME"], [D-Bus busname of LDAP config service])
+
+AC_DEFINE(SYSTEMD_BUSNAME, "org.freedesktop.systemd1", [systemd busname.])
+AC_DEFINE(SYSTEMD_PATH, "/org/freedesktop/systemd1", [systemd path.])
+AC_DEFINE(SYSTEMD_INTERFACE, "org.freedesktop.systemd1.Manager", [systemd interface.])
+
 # Checks for typedefs, structures, and compiler characteristics.
 AX_CXX_COMPILE_STDCXX_17([noext])
 AX_APPEND_COMPILE_FLAGS([-Wall -Werror], [CXXFLAGS])
@@ -71,5 +91,5 @@
 LT_INIT
 
 # Create configured output
-AC_CONFIG_FILES([Makefile test/Makefile phosphor-ldap-mapper/Makefile])
+AC_CONFIG_FILES([Makefile test/Makefile phosphor-ldap-mapper/Makefile phosphor-ldap-config/Makefile])
 AC_OUTPUT
diff --git a/phosphor-ldap-config/Makefile.am b/phosphor-ldap-config/Makefile.am
new file mode 100644
index 0000000..ed0853c
--- /dev/null
+++ b/phosphor-ldap-config/Makefile.am
@@ -0,0 +1,17 @@
+sbin_PROGRAMS = phosphor-ldap-conf
+
+noinst_HEADERS = ldap_configuration.hpp
+
+phosphor_ldap_conf_SOURCES = \
+                main.cpp \
+                ldap_configuration.cpp
+
+phosphor_ldap_conf_LDFLAGS = $(SDBUSPLUS_LIBS) \
+                             $(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+                             $(PHOSPHOR_LOGGING_LIBS)\
+                             -lstdc++fs
+
+phosphor_ldap_conf_CXXFLAGS = $(SYSTEMD_CFLAGS) \
+                              $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+                              $(PHOSPHOR_LOGGING_CFLAGS) \
+                              -flto
diff --git a/phosphor-ldap-config/ldap_configuration.cpp b/phosphor-ldap-config/ldap_configuration.cpp
new file mode 100644
index 0000000..06a4d5d
--- /dev/null
+++ b/phosphor-ldap-config/ldap_configuration.cpp
@@ -0,0 +1,234 @@
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include "ldap_configuration.hpp"
+#include "config.h"
+#include <fstream>
+#include <sstream>
+
+namespace phosphor
+{
+namespace ldap
+{
+constexpr auto nslcdService = "nslcd.service";
+
+Config::Config(sdbusplus::bus::bus& bus, const char* path, const char* filePath,
+               bool secureLDAP, std::string lDAPServerURI,
+               std::string lDAPBindDN, std::string lDAPBaseDN,
+               std::string lDAPBindDNpassword,
+               ldap_base::Config::SearchScope lDAPSearchScope,
+               ldap_base::Config::Type lDAPType, ConfigMgr& parent) :
+    ConfigIface(bus, path, true),
+    configFilePath(filePath), bus(bus), parent(parent)
+{
+    ConfigIface::secureLDAP(secureLDAP);
+    ConfigIface::lDAPServerURI(lDAPServerURI);
+    ConfigIface::lDAPBindDN(lDAPBindDN);
+    ConfigIface::lDAPBaseDN(lDAPBaseDN);
+    ConfigIface::lDAPBINDDNpassword(lDAPBindDNpassword);
+    ConfigIface::lDAPSearchScope(lDAPSearchScope);
+    ConfigIface::lDAPType(lDAPType);
+    writeConfig();
+    parent.restartService(nslcdService);
+    // Emit deferred signal.
+    this->emit_object_added();
+}
+
+void Config::writeConfig()
+{
+    std::fstream stream(configFilePath.c_str(), std::fstream::out);
+    std::stringstream confData;
+    confData << "uid root\n";
+    confData << "gid root\n\n";
+    confData << "ldap_version 3\n\n";
+    confData << "timelimit 30\n";
+    confData << "bind_timelimit 30\n";
+    confData << "pagesize 1000\n";
+    confData << "referrals off\n\n";
+    confData << "uri " << lDAPServerURI() << "\n\n";
+    confData << "base " << lDAPBaseDN() << "\n\n";
+    confData << "binddn " << lDAPBindDN() << "\n";
+    confData << "bindpw " << lDAPBINDDNpassword() << "\n\n";
+    switch (lDAPSearchScope())
+    {
+        case ldap_base::Config::SearchScope::sub:
+            confData << "scope sub\n\n";
+            break;
+        case ldap_base::Config::SearchScope::one:
+            confData << "scope one\n\n";
+            break;
+        case ldap_base::Config::SearchScope::base:
+            confData << "scope base\n\n";
+            break;
+    }
+    confData << "base passwd " << lDAPBaseDN() << "\n";
+    confData << "base shadow " << lDAPBaseDN() << "\n\n";
+    if (secureLDAP() == true)
+    {
+        confData << "ssl on\n";
+        confData << "tls_reqcert allow\n";
+        confData << "tls_cert /etc/nslcd/certs/cert.pem\n";
+    }
+    else
+    {
+        confData << "ssl off\n\n";
+    }
+    if (lDAPType() == ldap_base::Config::Type::ActiveDirectory)
+    {
+        confData << "filter passwd (&(objectClass=user)(objectClass=person)"
+                    "(!(objectClass=computer)))\n";
+        confData
+            << "filter group (|(objectclass=group)(objectclass=groupofnames) "
+               "(objectclass=groupofuniquenames))\n";
+        confData << "map passwd uid              sAMAccountName\n";
+        confData << "map passwd uidNumber        "
+                    "objectSid:S-1-5-21-3623811015-3361044348-30300820\n";
+        confData << "map passwd gidNumber        primaryGroupID\n";
+        confData << "map passwd homeDirectory    \"/home/$sAMAccountName\"\n";
+        confData << "map passwd gecos            displayName\n";
+        confData << "map passwd loginShell       \"/bin/bash\"\n";
+        confData << "map group gidNumber         primaryGroupID\n";
+        confData << "map group gidNumber         "
+                    "objectSid:S-1-5-21-3623811015-3361044348-30300820\n";
+        confData << "map group cn                sAMAccountName\n";
+    }
+    else if (lDAPType() == ldap_base::Config::Type::OpenLdap)
+    {
+        confData << "filter passwd (objectclass=*)\n";
+        confData << "map passwd uid cn\n";
+        confData << "map passwd gecos displayName\n";
+    }
+    stream << confData.str();
+    stream.flush();
+    stream.close();
+    return;
+}
+
+bool Config::secureLDAP(bool value)
+{
+    if (value == secureLDAP())
+    {
+        return value;
+    }
+
+    auto val = ConfigIface::secureLDAP(value);
+    writeConfig();
+    parent.restartService(nslcdService);
+
+    return val;
+}
+
+std::string Config::lDAPServerURI(std::string value)
+{
+    if (value == lDAPServerURI())
+    {
+        return value;
+    }
+
+    auto val = ConfigIface::lDAPServerURI(value);
+    writeConfig();
+    parent.restartService(nslcdService);
+
+    return val;
+}
+
+std::string Config::lDAPBindDN(std::string value)
+{
+    if (value == lDAPBindDN())
+    {
+        return value;
+    }
+
+    auto val = ConfigIface::lDAPBindDN(value);
+    writeConfig();
+    parent.restartService(nslcdService);
+
+    return val;
+}
+
+std::string Config::lDAPBaseDN(std::string value)
+{
+    if (value == lDAPBaseDN())
+    {
+        return value;
+    }
+
+    auto val = ConfigIface::lDAPBaseDN(value);
+    writeConfig();
+    parent.restartService(nslcdService);
+
+    return val;
+}
+
+std::string Config::lDAPBINDDNpassword(std::string value)
+{
+    if (value == lDAPBINDDNpassword())
+    {
+        return value;
+    }
+
+    auto val = ConfigIface::lDAPBINDDNpassword(value);
+    writeConfig();
+    parent.restartService(nslcdService);
+
+    return val;
+}
+
+ldap_base::Config::SearchScope
+    Config::lDAPSearchScope(ldap_base::Config::SearchScope value)
+{
+    if (value == lDAPSearchScope())
+    {
+        return value;
+    }
+
+    auto val = ConfigIface::lDAPSearchScope(value);
+    writeConfig();
+    parent.restartService(nslcdService);
+
+    return val;
+}
+
+ldap_base::Config::Type Config::lDAPType(ldap_base::Config::Type value)
+{
+    if (value == lDAPType())
+    {
+        return value;
+    }
+
+    auto val = ConfigIface::lDAPType(value);
+    writeConfig();
+    parent.restartService(nslcdService);
+
+    return val;
+}
+
+void ConfigMgr::restartService(const std::string& service)
+{
+    auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
+                                      SYSTEMD_INTERFACE, "RestartUnit");
+    method.append(service.c_str(), "replace");
+    bus.call_noreply(method);
+}
+
+std::string
+    ConfigMgr::createConfig(bool secureLDAP, std::string lDAPServerURI,
+                            std::string lDAPBindDN, std::string lDAPBaseDN,
+                            std::string lDAPBINDDNpassword,
+                            ldap_base::Create::SearchScope lDAPSearchScope,
+                            ldap_base::Create::Type lDAPType)
+{
+    // With current implementation we support only one LDAP server.
+    configPtr.reset(nullptr);
+
+    auto objPath = std::string(LDAP_CONFIG_DBUS_OBJ_PATH);
+    configPtr = std::make_unique<Config>(
+        bus, objPath.c_str(), LDAP_CONFIG_FILE, secureLDAP, lDAPServerURI,
+        lDAPBindDN, lDAPBaseDN, lDAPBINDDNpassword,
+        static_cast<ldap_base::Config::SearchScope>(lDAPSearchScope),
+        static_cast<ldap_base::Config::Type>(lDAPType), *this);
+
+    return objPath;
+}
+
+} // namespace ldap
+} // namespace phosphor
diff --git a/phosphor-ldap-config/ldap_configuration.hpp b/phosphor-ldap-config/ldap_configuration.hpp
new file mode 100644
index 0000000..f2bf02a
--- /dev/null
+++ b/phosphor-ldap-config/ldap_configuration.hpp
@@ -0,0 +1,184 @@
+#pragma once
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <xyz/openbmc_project/User/Ldap/Config/server.hpp>
+#include <xyz/openbmc_project/User/Ldap/Create/server.hpp>
+#include <string>
+
+namespace phosphor
+{
+namespace ldap
+{
+static constexpr auto defaultNslcdFile = "/etc/nslcd.conf.default";
+static constexpr auto nsSwitchFile = "/etc/nsswitch.conf";
+static constexpr auto LDAPNsSwitchFile = "/etc/nsswitch_ldap.conf";
+static constexpr auto linuxNsSwitchFile = "/etc/nsswitch_linux.conf";
+
+namespace ldap_base = sdbusplus::xyz::openbmc_project::User::Ldap::server;
+using ConfigIface = sdbusplus::server::object::object<ldap_base::Config>;
+using CreateIface = sdbusplus::server::object::object<ldap_base::Create>;
+
+class ConfigMgr;
+
+/** @class Config
+ *  @brief Configuration for LDAP.
+ *  @details concrete implementation of xyz.openbmc_project.User.Ldap.Config
+ *  API, in order to provide LDAP configuration.
+ */
+class Config : public ConfigIface
+{
+  public:
+    Config() = delete;
+    ~Config() = default;
+    Config(const Config&) = delete;
+    Config& operator=(const Config&) = delete;
+    Config(Config&&) = default;
+    Config& operator=(Config&&) = default;
+
+    /** @brief Constructor to put object onto bus at a D-Bus path.
+     *  @param[in] bus - Bus to attach to.
+     *  @param[in] path - The D-Bus object path to attach at.
+     *  @param[in] filePath - LDAP configuration file.
+     *  @param[in] secureLDAP - Specifies whether to use SSL or not.
+     *  @param[in] lDAPServerURI - LDAP URI of the server.
+     *  @param[in] lDAPBindDN - distinguished name with which to bind.
+     *  @param[in] lDAPBaseDN -  distinguished name to use as search base.
+     *  @param[in] lDAPBindDNpassword - credentials with which to bind.
+     *  @param[in] lDAPSearchScope - the search scope.
+     *  @param[in] lDAPType - Specifies the LDAP server type which can be AD
+            or openLDAP.
+     *  @param[in] parent - parent of config object.
+     */
+
+    Config(sdbusplus::bus::bus& bus, const char* path, const char* filePath,
+           bool secureLDAP, std::string lDAPServerURI, std::string lDAPBindDN,
+           std::string lDAPBaseDN, std::string lDAPBindDNpassword,
+           ldap_base::Config::SearchScope lDAPSearchScope,
+           ldap_base::Config::Type lDAPType, ConfigMgr& parent);
+
+    using ConfigIface::lDAPBaseDN;
+    using ConfigIface::lDAPBindDN;
+    using ConfigIface::lDAPBINDDNpassword;
+    using ConfigIface::lDAPSearchScope;
+    using ConfigIface::lDAPServerURI;
+    using ConfigIface::lDAPType;
+    using ConfigIface::secureLDAP;
+    using ConfigIface::setPropertyByName;
+
+    /** @brief Update the secure LDAP property.
+     *  @param[in] value - secureLDAP value to be updated.
+     *  @returns value of changed secureLDAP.
+     */
+    bool secureLDAP(bool value) override;
+
+    /** @brief Update the Server URI property.
+     *  @param[in] value - lDAPServerURI value to be updated.
+     *  @returns value of changed lDAPServerURI.
+     */
+    std::string lDAPServerURI(std::string value) override;
+
+    /** @brief Update the BindDN property.
+     *  @param[in] value - lDAPBindDN value to be updated.
+     *  @returns value of changed lDAPBindDN.
+     */
+    std::string lDAPBindDN(std::string value) override;
+
+    /** @brief Update the BaseDN property.
+     *  @param[in] value - lDAPBaseDN value to be updated.
+     *  @returns value of changed lDAPBaseDN.
+     */
+    std::string lDAPBaseDN(std::string value) override;
+
+    /** @brief Update the BindDN password property.
+     *  @param[in] value - lDAPBINDDNpassword value to be updated.
+     *  @returns value of changed lDAPBINDDNpassword.
+     */
+    std::string lDAPBINDDNpassword(std::string value) override;
+
+    /** @brief Update the Search scope property.
+     *  @param[in] value - lDAPSearchScope value to be updated.
+     *  @returns value of changed lDAPSearchScope.
+     */
+    ldap_base::Config::SearchScope
+        lDAPSearchScope(ldap_base::Config::SearchScope value) override;
+
+    /** @brief Update the LDAP Type property.
+     *  @param[in] value - lDAPType value to be updated.
+     *  @returns value of changed lDAPType.
+     */
+    ldap_base::Config::Type lDAPType(ldap_base::Config::Type value) override;
+
+  private:
+    std::string configFilePath{};
+
+    /** @brief Persistent sdbusplus D-Bus bus connection. */
+    sdbusplus::bus::bus& bus;
+
+    /** @brief Create a new LDAP config file.
+     */
+    virtual void writeConfig();
+
+    /** @brief reference to config manager object */
+    ConfigMgr& parent;
+};
+
+/** @class ConfigMgr
+ *  @brief Creates LDAP server configuration.
+ *  @details concrete implementation of xyz.openbmc_project.User.Ldap.Create
+ *  APIs, in order to create LDAP configuration.
+ */
+class ConfigMgr : public CreateIface
+{
+  public:
+    ConfigMgr() = delete;
+    ~ConfigMgr() = default;
+    ConfigMgr(const ConfigMgr&) = delete;
+    ConfigMgr& operator=(const ConfigMgr&) = delete;
+    ConfigMgr(ConfigMgr&&) = delete;
+    ConfigMgr& operator=(ConfigMgr&&) = delete;
+
+    /** @brief ConfigMgr 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] filePath - LDAP configuration file.
+     */
+    ConfigMgr(sdbusplus::bus::bus& bus, const char* path) :
+        CreateIface(bus, path), bus(bus)
+    {
+        // TODO  restore config object if config file exists.
+    }
+
+    /** @brief concrete implementation of the pure virtual funtion
+            xyz.openbmc_project.User.Ldap.Create.createConfig.
+     *  @param[in] secureLDAP - Specifies whether to use SSL or not.
+     *  @param[in] lDAPServerURI - LDAP URI of the server.
+     *  @param[in] lDAPBindDN - distinguished name with which bind to bind
+            to the directory server for lookups.
+     *  @param[in] lDAPBaseDN -  distinguished name to use as search base.
+     *  @param[in] lDAPBindDNpassword - credentials with which to bind.
+     *  @param[in] lDAPSearchScope - the search scope.
+     *  @param[in] lDAPType - Specifies the LDAP server type which can be AD
+            or openLDAP.
+     *  @returns the object path of the D-Bus object created.
+     */
+    std::string createConfig(bool secureLDAP, std::string lDAPServerURI,
+                             std::string lDAPBindDN, std::string lDAPBaseDN,
+                             std::string lDAPBindDNpassword,
+                             ldap_base::Create::SearchScope lDAPSearchScope,
+                             ldap_base::Create::Type lDAPType) override;
+
+    /** @brief restarts given service
+     *  @param[in] service - Service to be restarted.
+     */
+    virtual void restartService(const std::string& service);
+
+  private:
+    /** @brief Persistent sdbusplus D-Bus bus connection. */
+    sdbusplus::bus::bus& bus;
+
+    /** @brief Pointer to a Config D-Bus object */
+    std::unique_ptr<Config> configPtr = nullptr;
+};
+} // namespace ldap
+} // namespace phosphor
diff --git a/phosphor-ldap-config/main.cpp b/phosphor-ldap-config/main.cpp
new file mode 100644
index 0000000..6bba619
--- /dev/null
+++ b/phosphor-ldap-config/main.cpp
@@ -0,0 +1,40 @@
+#include "config.h"
+#include "ldap_configuration.hpp"
+#include <experimental/filesystem>
+#include <phosphor-logging/log.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <sdbusplus/bus.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+int main(int argc, char* argv[])
+{
+    using namespace phosphor::logging;
+    using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+    namespace fs = std::experimental::filesystem;
+
+    if (!fs::exists(phosphor::ldap::defaultNslcdFile) ||
+        !fs::exists(phosphor::ldap::nsSwitchFile) ||
+        (!fs::exists(phosphor::ldap::LDAPNsSwitchFile) &&
+         !fs::exists(phosphor::ldap::linuxNsSwitchFile)))
+    {
+        log<level::ERR>("Error starting LDAP Config App, configfile(s) are "
+                        "missing, exiting!!!");
+        elog<InternalFailure>();
+    }
+    auto bus = sdbusplus::bus::new_default();
+
+    // Add sdbusplus ObjectManager for the 'root' path of the LDAP config.
+    sdbusplus::server::manager::manager objManager(bus, LDAP_CONFIG_ROOT);
+
+    phosphor::ldap::ConfigMgr mgr(bus, LDAP_CONFIG_ROOT);
+
+    bus.request_name(LDAP_CONFIG_BUSNAME);
+
+    while (true)
+    {
+        bus.process_discard();
+        bus.wait();
+    }
+
+    return 0;
+}