intel-ipmi-oem: Add support - control bmc services

Support added for get & set control bmc services, which will
use srvcfg-manager daemon's Masked property to mask or
unmask a property.

Tested:
1. Verified ipmitool 0x30 0xB2 for the control bmc service values
2. Verified masking RMCP+ service using ipmitool 0x30 0xB1 0x0 0x8 0x0

Change-Id: I25762e09ff50935919684bb490b729c2ca861a1b
Signed-off-by: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com>
diff --git a/src/bmccontrolservices.cpp b/src/bmccontrolservices.cpp
index 2bebab5..b446886 100644
--- a/src/bmccontrolservices.cpp
+++ b/src/bmccontrolservices.cpp
@@ -16,47 +16,51 @@
 
 #include "oemcommands.hpp"
 
-#include <openssl/hmac.h>
-
+#include <boost/algorithm/string.hpp>
 #include <ipmid/api.hpp>
 #include <ipmid/utils.hpp>
 #include <phosphor-logging/log.hpp>
-#include <sdbusplus/bus.hpp>
+#include <variant>
 
+namespace ipmi
+{
 void register_netfn_bmc_control_functions() __attribute__((constructor));
 
-enum ipmi_bmc_control_services_return_codes
-{
-    ipmiCCBmcControlInvalidBitMask = 0xCC,
-    ipmiCCBmcControlPasswdInvalid = 0xCD,
-    ipmiCCBmcControlInvalidChannel = 0xD4,
+static constexpr uint8_t rmcpServiceBitPos = 3;
+static constexpr uint8_t webServiceBitPos = 5;
+static constexpr uint8_t solServiceBitPos = 6;
+static constexpr uint8_t kvmServiceBitPos = 15;
+
+static const std::unordered_map<uint8_t, std::string> bmcServices = {
+    // {bit position for service, service object path}
+    {rmcpServiceBitPos,
+     "/xyz/openbmc_project/control/service/phosphor_2dipmi_2dnet"},
+    {webServiceBitPos, "/xyz/openbmc_project/control/service/bmcweb"},
+    {solServiceBitPos, "/xyz/openbmc_project/control/service/obmc_2dconsole"},
+    {kvmServiceBitPos, "/xyz/openbmc_project/control/service/start_2dipkvm"},
 };
 
-// TODO: Add other services, once they are supported
-static const std::unordered_map<uint8_t, std::string> bmcServices = {
-    {3, "netipmid"},
-    {5, "web"},
-    {6, "ssh"},
-};
+static constexpr uint16_t maskBit15 = 0xF000;
 
 static constexpr const char* objectManagerIntf =
     "org.freedesktop.DBus.ObjectManager";
+static constexpr const char* dBusPropIntf = "org.freedesktop.DBus.Properties";
 static constexpr const char* serviceConfigBasePath =
     "/xyz/openbmc_project/control/service";
 static constexpr const char* serviceConfigAttrIntf =
     "xyz.openbmc_project.Control.Service.Attributes";
-static constexpr const char* serviceStateProperty = "State";
-static std::string disableServiceValue = "disabled";
+static constexpr const char* getMgdObjMethod = "GetManagedObjects";
+static constexpr const char* propMasked = "Masked";
 
-static ipmi_ret_t disableBmcServices(const std::string& objName)
+std::string getServiceConfigMgrName()
 {
-    std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
     static std::string serviceCfgMgr{};
     if (serviceCfgMgr.empty())
     {
         try
         {
-            serviceCfgMgr = ipmi::getService(*dbus, objectManagerIntf,
+            auto sdbusp = getSdBus();
+            serviceCfgMgr = ipmi::getService(*sdbusp, objectManagerIntf,
                                              serviceConfigBasePath);
         }
         catch (const sdbusplus::exception::SdBusError& e)
@@ -64,90 +68,147 @@
             serviceCfgMgr.clear();
             phosphor::logging::log<phosphor::logging::level::ERR>(
                 "Error: In fetching disabling service manager name");
-            return IPMI_CC_UNSPECIFIED_ERROR;
+            return serviceCfgMgr;
         }
     }
-    auto path = std::string(serviceConfigBasePath) + "/" + objName;
-    try
+    return serviceCfgMgr;
+}
+
+static inline void checkAndThrowError(boost::system::error_code& ec,
+                                      const std::string& msg)
+{
+    if (ec)
     {
-        ipmi::setDbusProperty(*dbus, serviceCfgMgr, path, serviceConfigAttrIntf,
-                              serviceStateProperty,
-                              ipmi::Value(disableServiceValue));
-        phosphor::logging::log<phosphor::logging::level::INFO>(
-            "Disabling service",
-            phosphor::logging::entry("PATH=%s", path.c_str()),
-            phosphor::logging::entry("MGR_NAME=%s", serviceCfgMgr.c_str()));
-        return IPMI_CC_OK;
+        std::string msgToLog = ec.message() + (msg.empty() ? "" : " - " + msg);
+        phosphor::logging::log<phosphor::logging::level::ERR>(msgToLog.c_str());
+        throw sdbusplus::exception::SdBusError(-EIO, msgToLog.c_str());
     }
-    catch (const sdbusplus::exception_t&)
+    return;
+}
+
+static inline bool getEnabledValue(const DbusInterfaceMap& intfMap)
+{
+    for (const auto& intf : intfMap)
     {
-        phosphor::logging::log<phosphor::logging::level::ERR>(
-            "Error: Disabling service",
-            phosphor::logging::entry("PATH=%s", path.c_str()),
-            phosphor::logging::entry("MGR_NAME=%s", serviceCfgMgr.c_str()));
-        return IPMI_CC_UNSPECIFIED_ERROR;
+        if (intf.first == serviceConfigAttrIntf)
+        {
+            auto it = intf.second.find(propMasked);
+            if (it == intf.second.end())
+            {
+                phosphor::logging::log<phosphor::logging::level::ERR>(
+                    "Error: in getting Masked property value");
+                throw sdbusplus::exception::SdBusError(
+                    -EIO, "ERROR in reading Masked property value");
+            }
+            // return !Masked value
+            return !std::get<bool>(it->second);
+        }
     }
 }
 
-static constexpr size_t controlPasswdSize = 32;
-
-ipmi::RspType<> bmcIntelControlServices(
-    ipmi::Context::ptr ctx,
-    const std::array<uint8_t, controlPasswdSize>& passwd, uint8_t stdServices,
-    uint8_t oemServices)
+ipmi::RspType<> setBmcControlServices(boost::asio::yield_context yield,
+                                      uint8_t state, uint16_t serviceValue)
 {
-    // Execute this command only in KCS interface
-    if (ctx->channel != interfaceKCS)
+    constexpr uint16_t servicesRsvdMask = 0x3F97;
+    constexpr uint8_t enableService = 0x1;
+
+    if ((state > enableService) || (serviceValue & servicesRsvdMask) ||
+        !serviceValue)
     {
-        return ipmi::response(ipmiCCBmcControlInvalidChannel);
+        return ipmi::responseInvalidFieldRequest();
     }
-
-    static std::string hashData("Intel 0penBMC");
-    static std::vector<uint8_t> hashedValue = {
-        0x89, 0x6A, 0xAB, 0x7D, 0xB0, 0x5A, 0x2D, 0x92, 0x41, 0xAD, 0x92,
-        0xEE, 0xD4, 0x82, 0xDE, 0x62, 0x66, 0x16, 0xC1, 0x08, 0xFD, 0x23,
-        0xC6, 0xD8, 0x75, 0xB3, 0x52, 0x53, 0x31, 0x3C, 0x7F, 0x69};
-    std::vector<uint8_t> hashedOutput(EVP_MAX_MD_SIZE, 0);
-    unsigned int outputLen = 0;
-    HMAC(EVP_sha256(), passwd.data(), passwd.size(),
-         reinterpret_cast<const uint8_t*>(hashData.c_str()), hashData.length(),
-         &hashedOutput[0], &outputLen);
-    hashedOutput.resize(outputLen);
-
-    if (hashedOutput != hashedValue)
+    try
     {
-        return ipmi::response(ipmiCCBmcControlPasswdInvalid);
-    }
+        auto sdbusp = getSdBus();
+        boost::system::error_code ec;
+        auto objectMap = sdbusp->yield_method_call<ObjectValueTree>(
+            yield, ec, getServiceConfigMgrName().c_str(), serviceConfigBasePath,
+            objectManagerIntf, getMgdObjMethod);
+        checkAndThrowError(ec, "GetMangagedObjects for service cfg failed");
 
-    if (stdServices == 0 && oemServices == 0)
-    {
-        return ipmi::response(ipmiCCBmcControlInvalidBitMask);
-    }
-
-    ipmi_ret_t retVal = IPMI_CC_OK;
-    for (size_t bitIndex = 0; bitIndex < 8; ++bitIndex)
-    {
-        if (stdServices & (1 << bitIndex))
+        for (const auto& services : bmcServices)
         {
-            auto it = bmcServices.find(bitIndex);
-            if (it == bmcServices.end())
+            // services.first holds the bit position of the service, check
+            // whether it has to be updated.
+            const uint16_t serviceMask = 1 << services.first;
+            if (!(serviceValue & serviceMask))
             {
-                return ipmi::response(ipmiCCBmcControlInvalidBitMask);
+                continue;
             }
-            retVal = disableBmcServices(it->second);
-            if (retVal != IPMI_CC_OK)
+            for (const auto& obj : objectMap)
             {
-                return ipmi::response(retVal);
+                if (boost::algorithm::starts_with(obj.first.str,
+                                                  services.second))
+                {
+                    if (state != getEnabledValue(obj.second))
+                    {
+                        ec.clear();
+                        sdbusp->yield_method_call<>(
+                            yield, ec, getServiceConfigMgrName().c_str(),
+                            obj.first.str, dBusPropIntf, "Set",
+                            serviceConfigAttrIntf, propMasked,
+                            std::variant<bool>(!state));
+                        checkAndThrowError(ec, "Set Masked property failed");
+                        // Multiple instances may be present, so continue
+                    }
+                }
             }
         }
     }
+    catch (sdbusplus::exception::SdBusError& e)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
     return ipmi::responseSuccess();
 }
 
+ipmi::RspType<uint16_t> getBmcControlServices(boost::asio::yield_context yield)
+{
+    uint16_t serviceValue = 0;
+    try
+    {
+        auto sdbusp = getSdBus();
+        boost::system::error_code ec;
+        auto objectMap = sdbusp->yield_method_call<ObjectValueTree>(
+            yield, ec, getServiceConfigMgrName().c_str(), serviceConfigBasePath,
+            objectManagerIntf, getMgdObjMethod);
+        checkAndThrowError(ec, "GetMangagedObjects for service cfg failed");
+
+        for (const auto& services : bmcServices)
+        {
+            for (const auto& obj : objectMap)
+            {
+                if (boost::algorithm::starts_with(obj.first.str,
+                                                  services.second))
+                {
+                    serviceValue |= getEnabledValue(obj.second)
+                                    << services.first;
+                    break;
+                }
+            }
+        }
+    }
+    catch (sdbusplus::exception::SdBusError& e)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+    // Bit 14 should match bit 15 as single service maintains Video & USB
+    // redirection
+    serviceValue |= (serviceValue & maskBit15) >> 1;
+    return ipmi::responseSuccess(serviceValue);
+}
+
 void register_netfn_bmc_control_functions()
 {
     ipmi::registerHandler(ipmi::prioOpenBmcBase, netfnIntcOEMGeneral,
                           static_cast<ipmi_cmd_t>(
-                              IPMINetFnIntelOemGeneralCmds::BmcControlServices),
-                          ipmi::Privilege::User, bmcIntelControlServices);
+                              IPMINetFnIntelOemGeneralCmds::controlBmcServices),
+                          ipmi::Privilege::Admin, setBmcControlServices);
+
+    ipmi::registerHandler(
+        ipmi::prioOpenBmcBase, netfnIntcOEMGeneral,
+        static_cast<ipmi_cmd_t>(
+            IPMINetFnIntelOemGeneralCmds::getBmcServiceStatus),
+        ipmi::Privilege::User, getBmcControlServices);
 }
+} // namespace ipmi