Replace reset BMC authentication with factory reset

Due to security concerns, replace the command that performs a
reset of the BMC authentication mechanism to perform a full
BMC reset so that any potential sensitive data is erased when
the authentication mechanism needs to be reset.

Tested: Verified the host was powered off and the BMC was
factory reset as part of this command.

Change-Id: I71dead237538d410e88710d2b7a53cea30de1426
Signed-off-by: Adriana Kobylak <anoo@us.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index a59ec07..65d6856 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3,7 +3,6 @@
 
 liboemhandler_la_SOURCES = oemhandler.cpp \
                            host-interface.cpp \
-                           local_users.cpp \
                            org/open_power/OCC/Metrics/error.cpp \
                            inventory-sensor.cpp
 
diff --git a/README.md b/README.md
index 86bee27..85a3369 100644
--- a/README.md
+++ b/README.md
@@ -15,5 +15,33 @@
 ## Supported Commands
 - Partial Add
 - Prepare for host update
-- Reset BMC authentication
+- BMC Factory Reset
+
+## Command Documentation
+
+### BMC Factory Reset
+Netfun: 0x3a
+Command: 0x11
+
+This command will call to reset the BMC to its factory default. See [here][0]
+for the factory reset implementation details.
+
+This includes:
+1. Power the chassis off. The host needs to be powered off because the factory
+reset deletes the inventory items, which are needed for the BMC during the power
+on path, causing the power on to fail. The inventory items are repopulated
+during a host power on.
+An enhancement to OpenBMC would be to handle missing inventory items during
+a BMC reboot when the host is on.
+2. Set the BMC to perform factory reset on reboot.
+3. Reboot the BMC for the factory reset to take effect.
+
+Because the chassis is powered off, the host does not receive a return code
+when successful.
+
+This command is not allowed when the IPMI restriction mode is set to
+'Whitelist'.  See [here][1] for more information.
+
+[0]: https://github.com/openbmc/phosphor-dbus-interfaces/tree/master/xyz/openbmc_project/Common/FactoryReset#xyzopenbmc_projectsoftwarebmcupdater
+[1]: https://github.com/openbmc/phosphor-dbus-interfaces/blob/master/xyz/openbmc_project/Control/Security/RestrictionMode.interface.yaml
 
diff --git a/local_users.cpp b/local_users.cpp
deleted file mode 100644
index ecf6024..0000000
--- a/local_users.cpp
+++ /dev/null
@@ -1,109 +0,0 @@
-#include "config.h"
-
-#include "local_users.hpp"
-
-#include <ipmid-host/cmd.hpp>
-#include <phosphor-logging/log.hpp>
-
-namespace local
-{
-namespace users
-{
-
-using namespace phosphor::logging;
-
-constexpr auto userIface = "xyz.openbmc_project.User.Attributes";
-constexpr auto propIface = "org.freedesktop.DBus.Properties";
-
-using DbusObjectPath = std::string;
-using DbusService = std::string;
-using DbusInterface = std::string;
-using ObjectTree =
-    std::map<DbusObjectPath, std::map<DbusService, std::vector<DbusInterface>>>;
-
-/**
- * @brief Gets a list of all local users in the form of GetSubTree
- *        results.
- *
- * @param[out] users - Filled in with the results of a
- *                     GetSubTree call that returns all users.
- */
-void getUsers(ObjectTree& users)
-{
-    auto& bus = ipmid_get_sdbus_plus_handler();
-
-    try
-    {
-        auto method = bus->new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ,
-                                           MAPPER_IFACE, "GetSubTree");
-        method.append("/xyz/openbmc_project/user/", 0,
-                      std::vector<std::string>{userIface});
-
-        auto reply = bus->call(method);
-        if (reply.is_method_error())
-        {
-            throw std::runtime_error("Method error on GetSubTree call");
-        }
-
-        reply.read(users);
-    }
-    catch (sdbusplus::exception::SdBusError& e)
-    {
-        throw std::runtime_error(e.what());
-    }
-}
-
-/**
- * @brief Enables the user passed in
- *
- * @param[in] path - The user object path
- * @param[in] service - The service hosting the user
- */
-void enableUser(const std::string& path, const std::string& service)
-{
-    auto& bus = ipmid_get_sdbus_plus_handler();
-
-    try
-    {
-        auto method = bus->new_method_call(service.c_str(), path.c_str(),
-                                           propIface, "Set");
-        sdbusplus::message::variant<bool> enabled{true};
-        method.append(userIface, "UserEnabled", enabled);
-
-        auto reply = bus->call(method);
-        if (reply.is_method_error())
-        {
-            throw std::runtime_error("Method error on property set call");
-        }
-    }
-    catch (sdbusplus::exception::SdBusError& e)
-    {
-        throw std::runtime_error(e.what());
-    }
-}
-
-ipmi_ret_t enableUsers()
-{
-    ObjectTree users;
-
-    try
-    {
-        getUsers(users);
-
-        for (const auto& user : users)
-        {
-            enableUser(user.first, user.second.begin()->first);
-        }
-    }
-    catch (std::runtime_error& e)
-    {
-        log<level::ERR>("Failed enabling local users",
-                        entry("ERROR=%s", e.what()));
-        return IPMI_CC_UNSPECIFIED_ERROR;
-    }
-
-    return IPMI_CC_OK;
-}
-
-} // namespace users
-} // namespace local
diff --git a/local_users.hpp b/local_users.hpp
deleted file mode 100644
index b04156e..0000000
--- a/local_users.hpp
+++ /dev/null
@@ -1,21 +0,0 @@
-#pragma once
-
-#include <ipmid/api.h>
-
-namespace local
-{
-namespace users
-{
-
-/**
- * @brief Enable all local BMC users
- *
- * Sets the UserEnabled property on all
- * /xyz/openbmc_project/user/<user> objects.
- *
- * @return ipmi_ret_t - IPMI CC
- */
-ipmi_ret_t enableUsers();
-
-} // namespace users
-} // namespace local
diff --git a/oemhandler.cpp b/oemhandler.cpp
index e35de77..e76405c 100644
--- a/oemhandler.cpp
+++ b/oemhandler.cpp
@@ -3,7 +3,6 @@
 #include "oemhandler.hpp"
 
 #include "elog-errors.hpp"
-#include "local_users.hpp"
 
 #include <endian.h>
 #include <ipmid/api.h>
@@ -19,6 +18,7 @@
 #include <org/open_power/Host/error.hpp>
 #include <org/open_power/OCC/Metrics/error.hpp>
 #include <sdbusplus/bus.hpp>
+#include <sdbusplus/exception.hpp>
 
 void register_netfn_ibm_oem_commands() __attribute__((constructor));
 
@@ -79,6 +79,40 @@
     return {};
 }
 
+std::string getService(sdbusplus::bus::bus& bus, const std::string& path,
+                       const std::string& interface)
+{
+    auto method = bus.new_method_call(MAPPER_BUS_NAME, MAPPER_OBJ, MAPPER_IFACE,
+                                      "GetObject");
+
+    method.append(path);
+    method.append(std::vector<std::string>({interface}));
+
+    std::map<std::string, std::vector<std::string>> response;
+
+    try
+    {
+        auto reply = bus.call(method);
+
+        reply.read(response);
+        if (response.empty())
+        {
+            log<level::ERR>("Error in mapper response for getting service name",
+                            entry("PATH=%s", path.c_str()),
+                            entry("INTERFACE=%s", interface.c_str()));
+            return std::string{};
+        }
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        log<level::ERR>("Error in mapper method call",
+                        entry("ERROR=%s", e.what()));
+        return std::string{};
+    }
+
+    return response.begin()->first;
+}
+
 std::string readESEL(const char* fileName)
 {
     std::string content{};
@@ -305,17 +339,87 @@
     return ipmi_rc;
 }
 
-ipmi_ret_t ipmi_ibm_oem_reset_bmc_auth(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
-                                       ipmi_request_t request,
-                                       ipmi_response_t response,
-                                       ipmi_data_len_t data_len,
-                                       ipmi_context_t context)
+ipmi_ret_t ipmi_ibm_oem_bmc_factory_reset(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
+                                          ipmi_request_t request,
+                                          ipmi_response_t response,
+                                          ipmi_data_len_t data_len,
+                                          ipmi_context_t context)
 {
-    ipmi_ret_t rc;
+    sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
+    // The timeout for IPMI commands is 5s
+    constexpr auto powerOffWait = std::chrono::seconds(1);
+    constexpr auto setFactoryWait = std::chrono::seconds(3);
 
-    rc = local::users::enableUsers();
+    // Power Off Chassis
+    auto service = getService(bus, stateChassisPath, stateChassisIntf);
+    if (service.empty())
+    {
+        return IPMI_CC_UNSPECIFIED_ERROR;
+    }
+    sdbusplus::message::variant<std::string> off =
+        "xyz.openbmc_project.State.Chassis.Transition.Off";
+    auto method = bus.new_method_call(service.c_str(), stateChassisPath,
+                                      propertiesIntf, "Set");
+    method.append(stateChassisIntf, "RequestedPowerTransition", off);
+    try
+    {
+        bus.call_noreply(method);
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        log<level::ERR>("Error powering off the chassis",
+                        entry("ERROR=%s", e.what()));
+        return IPMI_CC_UNSPECIFIED_ERROR;
+    }
 
-    return rc;
+    // Wait a few seconds for the chassis to power off
+    std::this_thread::sleep_for(powerOffWait);
+
+    // Set Factory Reset
+    method = bus.new_method_call(bmcUpdaterServiceName, softwarePath,
+                                 factoryResetIntf, "Reset");
+    try
+    {
+        bus.call_noreply(method);
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        log<level::ERR>("Error setting factory reset",
+                        entry("ERROR=%s", e.what()));
+        return IPMI_CC_UNSPECIFIED_ERROR;
+    }
+
+    // Wait a few seconds for service that sets the reset env variable to
+    // complete before the BMC is rebooted
+    std::this_thread::sleep_for(setFactoryWait);
+
+    // Reboot BMC
+    service = getService(bus, stateBmcPath, stateBmcIntf);
+    if (service.empty())
+    {
+        log<level::ALERT>("Error getting the service name to reboot the BMC. "
+                          "The BMC needs to be manually rebooted to complete "
+                          "the factory reset.");
+        return IPMI_CC_UNSPECIFIED_ERROR;
+    }
+    sdbusplus::message::variant<std::string> reboot =
+        "xyz.openbmc_project.State.BMC.Transition.Reboot";
+    method = bus.new_method_call(service.c_str(), stateBmcPath, propertiesIntf,
+                                 "Set");
+    method.append(stateBmcIntf, "RequestedBMCTransition", reboot);
+    try
+    {
+        bus.call_noreply(method);
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        log<level::ALERT>("Error calling to reboot the BMC. The BMC needs to "
+                          "be manually rebooted to complete the factory reset.",
+                          entry("ERROR=%s", e.what()));
+        return IPMI_CC_UNSPECIFIED_ERROR;
+    }
+
+    return IPMI_CC_OK;
 }
 
 namespace
@@ -339,8 +443,8 @@
     ipmi_register_callback(NETFUN_OEM, IPMI_CMD_PREP_FW_UPDATE, NULL,
                            ipmi_ibm_oem_prep_fw_update, SYSTEM_INTERFACE);
 
-    ipmi_register_callback(NETFUN_IBM_OEM, IPMI_CMD_RESET_BMC_AUTH, NULL,
-                           ipmi_ibm_oem_reset_bmc_auth, SYSTEM_INTERFACE);
+    ipmi_register_callback(NETFUN_IBM_OEM, IPMI_CMD_BMC_FACTORY_RESET, NULL,
+                           ipmi_ibm_oem_bmc_factory_reset, SYSTEM_INTERFACE);
 
     // Create new object on the bus
     auto objPath = std::string{CONTROL_HOST_OBJ_MGR} + '/' + HOST_NAME + '0';
diff --git a/oemhandler.hpp b/oemhandler.hpp
index c6794b8..9c5f563 100644
--- a/oemhandler.hpp
+++ b/oemhandler.hpp
@@ -6,11 +6,22 @@
 #include <map>
 #include <string>
 
+static constexpr auto propertiesIntf = "org.freedesktop.DBus.Properties";
+static constexpr auto bmcUpdaterServiceName =
+    "xyz.openbmc_project.Software.BMC.Updater";
+static constexpr auto softwarePath = "/xyz/openbmc_project/software";
+static constexpr auto factoryResetIntf =
+    "xyz.openbmc_project.Common.FactoryReset";
+static constexpr auto stateChassisPath = "/xyz/openbmc_project/state/chassis0";
+static constexpr auto stateChassisIntf = "xyz.openbmc_project.State.Chassis";
+static constexpr auto stateBmcPath = "/xyz/openbmc_project/state/bmc0";
+static constexpr auto stateBmcIntf = "xyz.openbmc_project.State.BMC";
+
 // IPMI commands for net functions.
 enum ipmi_netfn_oem_cmds
 {
     IPMI_CMD_PREP_FW_UPDATE = 0x10,
-    IPMI_CMD_RESET_BMC_AUTH = 0x11,
+    IPMI_CMD_BMC_FACTORY_RESET = 0x11,
     IPMI_CMD_PESEL = 0xF0,
     IPMI_CMD_OCC_RESET = 0x0E,
 };