Initialize Cipher Suite Privilege Levels cfg file

IPMI Spec 13.17 provides a way to pick a "highest level matching
proposed algorithms" during RMCPP Open Session Request, and effective
privilege levels are decided taking Cipher Suite Privilege Levels into
account. This patch ships default Cipher Suite Privilege Levels file
from root filesystem. By default, admin privileges are
given to all Cipher Suites across all channels.

Tested:
1. By default, cs_privilege_levels.json is present in location
   /usr/share/ipmi-providers/
Provided support from below patch.
https://gerrit.openbmc-project.xyz/#/c/openbmc/meta-phosphor/+/28923/
2. File data is as expected and persistent across reboots.
3. Manually deleted the json file while phosphor-ipmi-host was running
and rebooted and the file was re-created.

Change-Id: I526b8708f63659210768c77e8e19fa5a76df1f0d
Signed-off-by: Sumanth Bhat <sumanth.bhat@linux.intel.com>
Signed-off-by: vijayabharathix shetty <vijayabharathix.shetty@intel.com>
diff --git a/user_channel/Makefile.am b/user_channel/Makefile.am
index 747c4c8..bcd8573 100644
--- a/user_channel/Makefile.am
+++ b/user_channel/Makefile.am
@@ -39,7 +39,8 @@
 lib_LTLIBRARIES += libchannellayer.la
 libchannellayer_la_SOURCES = \
 	channel_mgmt.cpp \
-	channel_layer.cpp
+	channel_layer.cpp \
+	cipher_mgmt.cpp
 libchannellayer_la_LDFLAGS = \
 	$(SYSTEMD_LIBS) \
 	$(libmapper_LIBS) \
diff --git a/user_channel/channel_layer.cpp b/user_channel/channel_layer.cpp
index 9336596..faca91e 100644
--- a/user_channel/channel_layer.cpp
+++ b/user_channel/channel_layer.cpp
@@ -17,6 +17,7 @@
 #include "channel_layer.hpp"
 
 #include "channel_mgmt.hpp"
+#include "cipher_mgmt.hpp"
 
 #include <phosphor-logging/log.hpp>
 
@@ -81,6 +82,7 @@
 Cc ipmiChannelInit()
 {
     getChannelConfigObject();
+    getCipherConfigObject(csPrivFileName, csPrivDefaultFileName);
     return ccSuccess;
 }
 
diff --git a/user_channel/channel_layer.hpp b/user_channel/channel_layer.hpp
index eaf631c..0fefe2e 100644
--- a/user_channel/channel_layer.hpp
+++ b/user_channel/channel_layer.hpp
@@ -14,6 +14,7 @@
 // limitations under the License.
 */
 #pragma once
+#include <array>
 #include <ipmid/api.hpp>
 #include <string>
 
@@ -25,6 +26,11 @@
 static constexpr uint8_t invalidChannel = 0xff;
 
 /**
+ * @array of privilege levels
+ */
+extern const std::array<std::string, PRIVILEGE_OEM + 1> privList;
+
+/**
  * @enum IPMI return codes specific to channel (refer spec se 22.22 response
  * data)
  */
diff --git a/user_channel/channel_mgmt.cpp b/user_channel/channel_mgmt.cpp
index 116521d..70e2c7d 100644
--- a/user_channel/channel_mgmt.cpp
+++ b/user_channel/channel_mgmt.cpp
@@ -133,7 +133,7 @@
 static std::array<std::string, 4> sessionSupportList = {
     "session-less", "single-session", "multi-session", "session-based"};
 
-static std::array<std::string, PRIVILEGE_OEM + 1> privList = {
+const std::array<std::string, PRIVILEGE_OEM + 1> privList = {
     "priv-reserved", "priv-callback", "priv-user",
     "priv-operator", "priv-admin",    "priv-oem"};
 
diff --git a/user_channel/cipher_mgmt.cpp b/user_channel/cipher_mgmt.cpp
new file mode 100644
index 0000000..19dca48
--- /dev/null
+++ b/user_channel/cipher_mgmt.cpp
@@ -0,0 +1,177 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#include "cipher_mgmt.hpp"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <filesystem>
+#include <fstream>
+#include <include/ipmid/api-types.hpp>
+#include <phosphor-logging/log.hpp>
+
+namespace ipmi
+{
+
+using namespace phosphor::logging;
+using Json = nlohmann::json;
+namespace fs = std::filesystem;
+
+CipherConfig& getCipherConfigObject(const std::string& csFileName,
+                                    const std::string& csDefaultFileName)
+{
+    static CipherConfig cipherConfig(csFileName, csDefaultFileName);
+    return cipherConfig;
+}
+
+CipherConfig::CipherConfig(const std::string& csFileName,
+                           const std::string& csDefaultFileName) :
+    cipherSuitePrivFileName(csFileName),
+    cipherSuiteDefaultPrivFileName(csDefaultFileName)
+{
+    loadCSPrivilegesToMap();
+}
+
+void CipherConfig::loadCSPrivilegesToMap()
+{
+    if (!fs::exists(cipherSuiteDefaultPrivFileName))
+    {
+        log<level::ERR>("CS privilege levels default file does not exist...");
+    }
+    else
+    {
+        // read default privileges
+        Json data = readCSPrivilegeLevels(cipherSuiteDefaultPrivFileName);
+
+        // load default privileges
+        updateCSPrivilegesMap(data);
+
+        // check for user-saved privileges
+        if (fs::exists(cipherSuitePrivFileName))
+        {
+            data = readCSPrivilegeLevels(cipherSuitePrivFileName);
+            if (data != nullptr)
+            {
+                // update map with user-saved privileges by merging (overriding)
+                // values from the defaults
+                updateCSPrivilegesMap(data);
+            }
+        }
+    }
+}
+
+void CipherConfig::updateCSPrivilegesMap(const Json& jsonData)
+{
+    for (uint8_t chNum = 0; chNum < ipmi::maxIpmiChannels; chNum++)
+    {
+        std::string chKey = "Channel" + std::to_string(chNum);
+        for (uint8_t csNum = 0; csNum < maxCSRecords; csNum++)
+        {
+            auto csKey = "CipherID" + std::to_string(csNum);
+
+            if (jsonData.find(chKey) != jsonData.end())
+            {
+                csPrivilegeMap[{chNum, csNum}] = convertToPrivLimitIndex(
+                    static_cast<std::string>(jsonData[chKey][csKey]));
+            }
+        }
+    }
+}
+
+Json CipherConfig::readCSPrivilegeLevels(const std::string& csFileName)
+{
+    std::ifstream jsonFile(csFileName);
+    if (!jsonFile.good())
+    {
+        log<level::ERR>("JSON file not found");
+        return nullptr;
+    }
+
+    Json data = nullptr;
+    try
+    {
+        data = Json::parse(jsonFile, nullptr, false);
+    }
+    catch (Json::parse_error& e)
+    {
+        log<level::ERR>("Corrupted cipher suite privilege levels config file.",
+                        entry("MSG: %s", e.what()));
+    }
+
+    return data;
+}
+
+int CipherConfig::writeCSPrivilegeLevels(const Json& jsonData)
+{
+    std::string tmpFile =
+        static_cast<std::string>(cipherSuitePrivFileName) + "_tmpXXXXXX";
+
+    char tmpRandomFile[tmpFile.length() + 1];
+    strncpy(tmpRandomFile, tmpFile.c_str(), tmpFile.length() + 1);
+
+    int fd = mkstemp(tmpRandomFile);
+    fchmod(fd, 0644);
+
+    if (fd < 0)
+    {
+        log<level::ERR>("Error opening CS privilege level config file",
+                        entry("FILE_NAME=%s", tmpFile.c_str()));
+        return -EIO;
+    }
+    const auto& writeData = jsonData.dump();
+    if (write(fd, writeData.c_str(), writeData.size()) !=
+        static_cast<ssize_t>(writeData.size()))
+    {
+        close(fd);
+        log<level::ERR>("Error writing CS privilege level config file",
+                        entry("FILE_NAME=%s", tmpFile.c_str()));
+        unlink(tmpRandomFile);
+        return -EIO;
+    }
+    close(fd);
+
+    if (std::rename(tmpRandomFile, cipherSuitePrivFileName.c_str()))
+    {
+        log<level::ERR>("Error renaming CS privilege level config file",
+                        entry("FILE_NAME=%s", tmpFile.c_str()));
+        unlink(tmpRandomFile);
+        return -EIO;
+    }
+
+    return 0;
+}
+
+uint4_t CipherConfig::convertToPrivLimitIndex(const std::string& value)
+{
+    auto iter = std::find(ipmi::privList.begin(), ipmi::privList.end(), value);
+    if (iter == privList.end())
+    {
+        log<level::ERR>("Invalid privilege.",
+                        entry("PRIV_STR=%s", value.c_str()));
+        return ccUnspecifiedError;
+    }
+
+    return static_cast<uint4_t>(std::distance(ipmi::privList.begin(), iter));
+}
+
+std::string CipherConfig::convertToPrivLimitString(const uint4_t& value)
+{
+    return ipmi::privList.at(static_cast<size_t>(value));
+}
+
+} // namespace ipmi
diff --git a/user_channel/cipher_mgmt.hpp b/user_channel/cipher_mgmt.hpp
new file mode 100644
index 0000000..79afc23
--- /dev/null
+++ b/user_channel/cipher_mgmt.hpp
@@ -0,0 +1,103 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#pragma once
+#include "channel_layer.hpp"
+
+#include <ipmid/message/types.hpp>
+#include <map>
+#include <nlohmann/json.hpp>
+
+namespace ipmi
+{
+static const std::string csPrivDefaultFileName =
+    "/usr/share/ipmi-providers/cs_privilege_levels.json";
+
+static const std::string csPrivFileName =
+    "/var/lib/ipmi/cs_privilege_levels.json";
+
+static const size_t maxCSRecords = 16;
+
+using ChannelNumCipherIDPair = std::pair<uint8_t, uint8_t>;
+using privMap = std::map<ChannelNumCipherIDPair, uint4_t>;
+
+/** @class CipherConfig
+ *  @brief Class to provide cipher suite functionalities
+ */
+class CipherConfig
+{
+  public:
+    ~CipherConfig() = default;
+    explicit CipherConfig(const std::string& csFileName,
+                          const std::string& csDefaultFileName);
+    CipherConfig() = delete;
+
+  private:
+    std::string cipherSuitePrivFileName, cipherSuiteDefaultPrivFileName;
+
+    privMap csPrivilegeMap;
+
+    /** @brief function to read json config file
+     *
+     *  @return nlohmann::json object
+     */
+    nlohmann::json readCSPrivilegeLevels(const std::string& csFileName);
+
+    /** @brief function to write json config file
+     *
+     *  @param[in] jsonData - json object
+     *
+     *  @return 0 for success, -errno for failure.
+     */
+    int writeCSPrivilegeLevels(const nlohmann::json& jsonData);
+
+    /** @brief convert to cipher suite privilege from string to value
+     *
+     *  @param[in] value - privilege value
+     *
+     *  @return cipher suite privilege index
+     */
+    uint4_t convertToPrivLimitIndex(const std::string& value);
+
+    /** @brief function to convert privilege value to string
+     *
+     *  @param[in] value - privilege value
+     *
+     *  @return privilege in string
+     */
+    std::string convertToPrivLimitString(const uint4_t& value);
+
+    /** @brief function to load CS Privilege Levels from json file/files to map
+     *
+     */
+    void loadCSPrivilegesToMap();
+
+    /** @brief function to update CS privileges map from json object data,
+     * jsonData
+     *
+     */
+    void updateCSPrivilegesMap(const nlohmann::json& jsonData);
+};
+
+/** @brief function to create static CipherConfig object
+ *
+ *  @param[in] csFileName - user setting cipher suite privilege file name
+ *  @param[in] csDefaultFileName - default cipher suite privilege file name
+ *
+ *  @return static CipherConfig object
+ */
+CipherConfig& getCipherConfigObject(const std::string& csFileName,
+                                    const std::string& csDefaultFileName);
+} // namespace ipmi