ipmid: Add command filter mechanism

Every ipmi command will pass through a filter prior to execution by the
registered handler. The filter consists of all registered filter
handlers that all must either return an IPMI error code or allow the
command to be executed. If any of the filter handlers return an error
code, the remaining handlers will not get a chance to run.

Each handler, executed in registered priority order, can be passed the
full message, or just the context (metadata describing the command,
netfn, cmd, etc.)

Change-Id: I3c48f19ebae0d24344b15fbcd2b940a32f8511d7
Signed-off-by: Vernon Mauery <vernon.mauery@linux.intel.com>
diff --git a/ipmid-new.cpp b/ipmid-new.cpp
index a9aff36..4986e49 100644
--- a/ipmid-new.cpp
+++ b/ipmid-new.cpp
@@ -135,6 +135,13 @@
                           HandlerTuple>
     oemHandlerMap;
 
+using FilterTuple = std::tuple<int,            /* prio */
+                               FilterBase::ptr /* filter */
+                               >;
+
+/* list to hold all registered ipmi command filters */
+static std::forward_list<FilterTuple> filterList;
+
 namespace impl
 {
 /* common function to register all standard IPMI handlers */
@@ -198,12 +205,54 @@
     return false;
 }
 
+/* common function to register all IPMI filter handlers */
+void registerFilter(int prio, FilterBase::ptr filter)
+{
+    // check for initial placement
+    if (filterList.empty() || std::get<int>(filterList.front()) < prio)
+    {
+        filterList.emplace_front(std::make_tuple(prio, filter));
+    }
+    // walk the list and put it in the right place
+    auto j = filterList.begin();
+    for (auto i = j; i != filterList.end() && std::get<int>(*i) > prio; i++)
+    {
+        j = i;
+    }
+    filterList.emplace_after(j, std::make_tuple(prio, filter));
+}
+
 } // namespace impl
 
+message::Response::ptr filterIpmiCommand(message::Request::ptr request)
+{
+    // pass the command through the filter mechanism
+    // This can be the firmware firewall or any OEM mechanism like
+    // whitelist filtering based on operational mode
+    for (auto& item : filterList)
+    {
+        FilterBase::ptr filter = std::get<FilterBase::ptr>(item);
+        ipmi::Cc cc = filter->call(request);
+        if (ipmi::ccSuccess != cc)
+        {
+            return errorResponse(request, cc);
+        }
+    }
+    return message::Response::ptr();
+}
+
 message::Response::ptr executeIpmiCommandCommon(
     std::unordered_map<unsigned int, HandlerTuple>& handlers,
     unsigned int keyCommon, message::Request::ptr request)
 {
+    // filter the command first; a non-null message::Response::ptr
+    // means that the message has been rejected for some reason
+    message::Response::ptr response = filterIpmiCommand(request);
+    if (response)
+    {
+        return response;
+    }
+
     Cmd cmd = request->ctx->cmd;
     unsigned int key = makeCmdKey(keyCommon, cmd);
     auto cmdIter = handlers.find(key);