ipmid: Add whitelist filtering using the new filter registration mechanism

Implement the whitelist filtering as an optional provider using the
new filter registration mechanism.

Change-Id: I0d738e58508d31e6b1867e13b3b7ed048303d5d8
Signed-off-by: Vernon Mauery <vernon.mauery@linux.intel.com>
diff --git a/Makefile.am b/Makefile.am
index ce96af7..b62741a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -15,8 +15,6 @@
 	host-cmd-manager.cpp \
 	utils.cpp
 
-nodist_ipmid_SOURCES = ipmiwhitelist.cpp
-
 libipmi20_BUILT_LIST = \
 	sensor-gen.cpp \
 	inventory-sensor-gen.cpp \
@@ -149,6 +147,19 @@
 	-version-info 0:0:0 -shared
 libsysintfcmds_la_CXXFLAGS = $(COMMON_CXX)
 
+libwhitelistdir = ${libdir}/ipmid-providers
+libwhitelist_LTLIBRARIES = libwhitelist.la
+libwhitelist_la_SOURCES = \
+	whitelist-filter.cpp
+libwhitelist_la_LDFLAGS = \
+	$(SYSTEMD_LIBS) \
+	$(libmapper_LIBS) \
+	$(PHOSPHOR_LOGGING_LIBS) \
+	$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+	-version-info 0:0:0 -shared
+libwhitelist_la_CXXFLAGS = $(COMMON_CXX)
+nodist_libwhitelist_la_SOURCES = ipmiwhitelist.cpp
+
 nobase_include_HEADERS = \
 	user_channel/channel_layer.hpp \
 	user_channel/user_layer.hpp
diff --git a/whitelist-filter.cpp b/whitelist-filter.cpp
new file mode 100644
index 0000000..6fe0f3e
--- /dev/null
+++ b/whitelist-filter.cpp
@@ -0,0 +1,168 @@
+#include <algorithm>
+#include <array>
+#include <ipmid/api.hpp>
+#include <ipmid/registration.hpp>
+#include <ipmiwhitelist.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/log.hpp>
+#include <settings.hpp>
+#include <xyz/openbmc_project/Control/Security/RestrictionMode/server.hpp>
+
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+namespace ipmi
+{
+
+// put the filter provider in an unnamed namespace
+namespace
+{
+
+/** @class WhitelistFilter
+ *
+ * Class that implements an IPMI message filter based
+ * on incoming interface and a restriction mode setting
+ */
+class WhitelistFilter
+{
+  public:
+    WhitelistFilter();
+    ~WhitelistFilter() = default;
+    WhitelistFilter(WhitelistFilter const&) = delete;
+    WhitelistFilter(WhitelistFilter&&) = delete;
+    WhitelistFilter& operator=(WhitelistFilter const&) = delete;
+    WhitelistFilter& operator=(WhitelistFilter&&) = delete;
+
+  private:
+    void postInit();
+    void cacheRestrictedMode();
+    void handleRestrictedModeChange(sdbusplus::message::message& m);
+    ipmi::Cc filterMessage(ipmi::message::Request::ptr request);
+
+    bool restrictedMode = true;
+    std::shared_ptr<sdbusplus::asio::connection> bus;
+    std::unique_ptr<settings::Objects> objects;
+    std::unique_ptr<sdbusplus::bus::match::match> modeChangeMatch;
+
+    static constexpr const char restrictionModeIntf[] =
+        "xyz.openbmc_project.Control.Security.RestrictionMode";
+};
+
+WhitelistFilter::WhitelistFilter()
+{
+    bus = getSdBus();
+
+    log<level::INFO>("Loading whitelist filter");
+
+    ipmi::registerFilter(ipmi::prioOpenBmcBase,
+                         [this](ipmi::message::Request::ptr request) {
+                             return filterMessage(request);
+                         });
+
+    // wait until io->run is going to fetch RestrictionMode
+    post_work([this]() { postInit(); });
+}
+
+void WhitelistFilter::cacheRestrictedMode()
+{
+    using namespace sdbusplus::xyz::openbmc_project::Control::Security::server;
+    std::string restrictionModeSetting;
+    std::string restrictionModeService;
+    try
+    {
+        restrictionModeSetting = objects->map.at(restrictionModeIntf).at(0);
+        restrictionModeService =
+            objects->service(restrictionModeSetting, restrictionModeIntf);
+    }
+    catch (const std::out_of_range& e)
+    {
+        log<level::ERR>(
+            "Could not look up restriction mode interface from cache");
+        return;
+    }
+    bus->async_method_call(
+        [this](boost::system::error_code ec, std::string mode) {
+            if (ec)
+            {
+                log<level::ERR>("Error in RestrictionMode Get");
+                // Fail-safe to true.
+                restrictedMode = true;
+                return;
+            }
+            auto restrictionMode =
+                RestrictionMode::convertModesFromString(mode);
+            restrictedMode =
+                (restrictionMode == RestrictionMode::Modes::Whitelist);
+            log<level::INFO>((restrictedMode ? "Set restrictedMode = true"
+                                             : "Set restrictedMode = false"));
+        },
+        restrictionModeService.c_str(), restrictionModeSetting.c_str(),
+        "org.freedesktop.DBus.Properties", "Get", "RestrictionMode");
+}
+
+void WhitelistFilter::handleRestrictedModeChange(sdbusplus::message::message& m)
+{
+    using namespace sdbusplus::xyz::openbmc_project::Control::Security::server;
+    std::string mode;
+    m.read(mode);
+    RestrictionMode::Modes restrictionMode =
+        RestrictionMode::convertModesFromString(mode);
+    restrictedMode = (restrictionMode == RestrictionMode::Modes::Whitelist);
+    log<level::INFO>((restrictedMode ? "Updated restrictedMode = true"
+                                     : "Updated restrictedMode = false"));
+}
+
+void WhitelistFilter::postInit()
+{
+    objects = std::make_unique<settings::Objects>(
+        *bus, std::vector<settings::Interface>({restrictionModeIntf}));
+    if (!objects)
+    {
+        log<level::ERR>(
+            "Failed to create settings object; defaulting to restricted mode");
+        return;
+    }
+
+    // Initialize restricted mode
+    cacheRestrictedMode();
+    // Wait for changes on Restricted mode
+    std::string filterStr;
+    try
+    {
+        filterStr = sdbusplus::bus::match::rules::propertiesChanged(
+            objects->map.at(restrictionModeIntf).at(0), restrictionModeIntf);
+    }
+    catch (const std::out_of_range& e)
+    {
+        log<level::ERR>("Failed to determine restriction mode filter string");
+        return;
+    }
+    modeChangeMatch = std::make_unique<sdbusplus::bus::match::match>(
+        *bus, filterStr, [this](sdbusplus::message::message& m) {
+            handleRestrictedModeChange(m);
+        });
+}
+
+ipmi::Cc WhitelistFilter::filterMessage(ipmi::message::Request::ptr request)
+{
+    if (request->ctx->channel == ipmi::channelSystemIface && restrictedMode)
+    {
+        if (!std::binary_search(
+                whitelist.cbegin(), whitelist.cend(),
+                std::make_pair(request->ctx->netFn, request->ctx->cmd)))
+        {
+            log<level::ERR>("Net function not whitelisted",
+                            entry("NETFN=0x%X", int(request->ctx->netFn)),
+                            entry("CMD=0x%X", int(request->ctx->cmd)));
+            return ipmi::ccInsufficientPrivilege;
+        }
+    }
+    return ipmi::ccSuccess;
+}
+
+// instantiate the WhitelistFilter when this shared object is loaded
+WhitelistFilter whitelistFilter;
+
+} // namespace
+
+} // namespace ipmi