xref: /openbmc/phosphor-host-ipmid/ipmid-new.cpp (revision 9f3073a69d750e557a859e9fa6c9e3b3eb7946bc)
1  /**
2   * Copyright © 2018 Intel Corporation
3   *
4   * Licensed under the Apache License, Version 2.0 (the "License");
5   * you may not use this file except in compliance with the License.
6   * You may obtain a copy of the License at
7   *
8   *     http://www.apache.org/licenses/LICENSE-2.0
9   *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  #include "config.h"
17  
18  #include "settings.hpp"
19  
20  #include <dlfcn.h>
21  
22  #include <boost/algorithm/string.hpp>
23  #include <boost/asio/io_context.hpp>
24  #include <boost/asio/spawn.hpp>
25  #include <host-cmd-manager.hpp>
26  #include <ipmid-host/cmd.hpp>
27  #include <ipmid/api.hpp>
28  #include <ipmid/handler.hpp>
29  #include <ipmid/message.hpp>
30  #include <ipmid/oemrouter.hpp>
31  #include <ipmid/types.hpp>
32  #include <phosphor-logging/lg2.hpp>
33  #include <sdbusplus/asio/connection.hpp>
34  #include <sdbusplus/asio/object_server.hpp>
35  #include <sdbusplus/asio/sd_event.hpp>
36  #include <sdbusplus/bus.hpp>
37  #include <sdbusplus/bus/match.hpp>
38  #include <sdbusplus/timer.hpp>
39  
40  #include <algorithm>
41  #include <any>
42  #include <exception>
43  #include <filesystem>
44  #include <forward_list>
45  #include <map>
46  #include <memory>
47  #include <optional>
48  #include <tuple>
49  #include <unordered_map>
50  #include <utility>
51  #include <vector>
52  
53  namespace fs = std::filesystem;
54  
55  using namespace phosphor::logging;
56  
57  // IPMI Spec, shared Reservation ID.
58  static unsigned short selReservationID = 0xFFFF;
59  static bool selReservationValid = false;
60  
reserveSel(void)61  unsigned short reserveSel(void)
62  {
63      // IPMI spec, Reservation ID, the value simply increases against each
64      // execution of the Reserve SEL command.
65      if (++selReservationID == 0)
66      {
67          selReservationID = 1;
68      }
69      selReservationValid = true;
70      return selReservationID;
71  }
72  
checkSELReservation(unsigned short id)73  bool checkSELReservation(unsigned short id)
74  {
75      return (selReservationValid && selReservationID == id);
76  }
77  
cancelSELReservation(void)78  void cancelSELReservation(void)
79  {
80      selReservationValid = false;
81  }
82  
getInterfaceIndex(void)83  EInterfaceIndex getInterfaceIndex(void)
84  {
85      return interfaceKCS;
86  }
87  
88  sd_bus* bus;
89  sd_event* events = nullptr;
ipmid_get_sd_event_connection(void)90  sd_event* ipmid_get_sd_event_connection(void)
91  {
92      return events;
93  }
ipmid_get_sd_bus_connection(void)94  sd_bus* ipmid_get_sd_bus_connection(void)
95  {
96      return bus;
97  }
98  
99  namespace ipmi
100  {
101  
makeCmdKey(unsigned int cluster,unsigned int cmd)102  static inline unsigned int makeCmdKey(unsigned int cluster, unsigned int cmd)
103  {
104      return (cluster << 8) | cmd;
105  }
106  
107  using HandlerTuple = std::tuple<int,                        /* prio */
108                                  Privilege, HandlerBase::ptr /* handler */
109                                  >;
110  
111  /* map to handle standard registered commands */
112  static std::unordered_map<unsigned int, /* key is NetFn/Cmd */
113                            HandlerTuple>
114      handlerMap;
115  
116  /* special map for decoding Group registered commands (NetFn 2Ch) */
117  static std::unordered_map<unsigned int, /* key is Group/Cmd (NetFn is 2Ch) */
118                            HandlerTuple>
119      groupHandlerMap;
120  
121  /* special map for decoding OEM registered commands (NetFn 2Eh) */
122  static std::unordered_map<unsigned int, /* key is Iana/Cmd (NetFn is 2Eh) */
123                            HandlerTuple>
124      oemHandlerMap;
125  
126  using FilterTuple = std::tuple<int,            /* prio */
127                                 FilterBase::ptr /* filter */
128                                 >;
129  
130  /* list to hold all registered ipmi command filters */
131  static std::forward_list<FilterTuple> filterList;
132  
133  namespace impl
134  {
135  /* common function to register all standard IPMI handlers */
registerHandler(int prio,NetFn netFn,Cmd cmd,Privilege priv,HandlerBase::ptr handler)136  bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv,
137                       HandlerBase::ptr handler)
138  {
139      // check for valid NetFn: even; 00-0Ch, 30-3Eh
140      if (netFn & 1 || (netFn > netFnTransport && netFn < netFnGroup) ||
141          netFn > netFnOemEight)
142      {
143          return false;
144      }
145  
146      // create key and value for this handler
147      unsigned int netFnCmd = makeCmdKey(netFn, cmd);
148      HandlerTuple item(prio, priv, handler);
149  
150      // consult the handler map and look for a match
151      auto& mapCmd = handlerMap[netFnCmd];
152      if (!std::get<HandlerBase::ptr>(mapCmd) || std::get<int>(mapCmd) <= prio)
153      {
154          mapCmd = item;
155          return true;
156      }
157      return false;
158  }
159  
160  /* common function to register all Group IPMI handlers */
registerGroupHandler(int prio,Group group,Cmd cmd,Privilege priv,HandlerBase::ptr handler)161  bool registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv,
162                            HandlerBase::ptr handler)
163  {
164      // create key and value for this handler
165      unsigned int netFnCmd = makeCmdKey(group, cmd);
166      HandlerTuple item(prio, priv, handler);
167  
168      // consult the handler map and look for a match
169      auto& mapCmd = groupHandlerMap[netFnCmd];
170      if (!std::get<HandlerBase::ptr>(mapCmd) || std::get<int>(mapCmd) <= prio)
171      {
172          mapCmd = item;
173          return true;
174      }
175      return false;
176  }
177  
178  /* common function to register all OEM IPMI handlers */
registerOemHandler(int prio,Iana iana,Cmd cmd,Privilege priv,HandlerBase::ptr handler)179  bool registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv,
180                          HandlerBase::ptr handler)
181  {
182      // create key and value for this handler
183      unsigned int netFnCmd = makeCmdKey(iana, cmd);
184      HandlerTuple item(prio, priv, handler);
185  
186      // consult the handler map and look for a match
187      auto& mapCmd = oemHandlerMap[netFnCmd];
188      if (!std::get<HandlerBase::ptr>(mapCmd) || std::get<int>(mapCmd) <= prio)
189      {
190          mapCmd = item;
191          lg2::debug("registered OEM Handler: NetFn/Cmd={NETFNCMD}", "IANA",
192                     lg2::hex, iana, "CMD", lg2::hex, cmd, "NETFNCMD", lg2::hex,
193                     netFnCmd);
194          return true;
195      }
196  
197      lg2::warning("could not register OEM Handler: NetFn/Cmd={NETFNCMD}", "IANA",
198                   lg2::hex, iana, "CMD", lg2::hex, cmd, "NETFNCMD", lg2::hex,
199                   netFnCmd);
200      return false;
201  }
202  
203  /* common function to register all IPMI filter handlers */
registerFilter(int prio,FilterBase::ptr filter)204  void registerFilter(int prio, FilterBase::ptr filter)
205  {
206      // check for initial placement
207      if (filterList.empty() || std::get<int>(filterList.front()) < prio)
208      {
209          filterList.emplace_front(std::make_tuple(prio, filter));
210          return;
211      }
212      // walk the list and put it in the right place
213      auto j = filterList.begin();
214      for (auto i = j; i != filterList.end() && std::get<int>(*i) > prio; i++)
215      {
216          j = i;
217      }
218      filterList.emplace_after(j, std::make_tuple(prio, filter));
219  }
220  
221  } // namespace impl
222  
filterIpmiCommand(message::Request::ptr request)223  message::Response::ptr filterIpmiCommand(message::Request::ptr request)
224  {
225      // pass the command through the filter mechanism
226      // This can be the firmware firewall or any OEM mechanism like
227      // whitelist filtering based on operational mode
228      for (auto& item : filterList)
229      {
230          FilterBase::ptr filter = std::get<FilterBase::ptr>(item);
231          ipmi::Cc cc = filter->call(request);
232          if (ipmi::ccSuccess != cc)
233          {
234              return errorResponse(request, cc);
235          }
236      }
237      return message::Response::ptr();
238  }
239  
executeIpmiCommandCommon(std::unordered_map<unsigned int,HandlerTuple> & handlers,unsigned int keyCommon,message::Request::ptr request)240  message::Response::ptr executeIpmiCommandCommon(
241      std::unordered_map<unsigned int, HandlerTuple>& handlers,
242      unsigned int keyCommon, message::Request::ptr request)
243  {
244      // filter the command first; a non-null message::Response::ptr
245      // means that the message has been rejected for some reason
246      message::Response::ptr filterResponse = filterIpmiCommand(request);
247  
248      Cmd cmd = request->ctx->cmd;
249      unsigned int key = makeCmdKey(keyCommon, cmd);
250      auto cmdIter = handlers.find(key);
251      if (cmdIter != handlers.end())
252      {
253          // only return the filter response if the command is found
254          if (filterResponse)
255          {
256              lg2::debug("request for NetFn/Cmd {NETFN}/{CMD} has been filtered",
257                         "NETFN", lg2::hex, keyCommon, "CMD", lg2::hex, cmd);
258              return filterResponse;
259          }
260          HandlerTuple& chosen = cmdIter->second;
261          if (request->ctx->priv < std::get<Privilege>(chosen))
262          {
263              return errorResponse(request, ccInsufficientPrivilege);
264          }
265          return std::get<HandlerBase::ptr>(chosen)->call(request);
266      }
267      else
268      {
269          unsigned int wildcard = makeCmdKey(keyCommon, cmdWildcard);
270          cmdIter = handlers.find(wildcard);
271          if (cmdIter != handlers.end())
272          {
273              // only return the filter response if the command is found
274              if (filterResponse)
275              {
276                  lg2::debug(
277                      "request for NetFn/Cmd {NETFN}/{CMD} has been filtered",
278                      "NETFN", lg2::hex, keyCommon, "CMD", lg2::hex, cmd);
279                  return filterResponse;
280              }
281              HandlerTuple& chosen = cmdIter->second;
282              if (request->ctx->priv < std::get<Privilege>(chosen))
283              {
284                  return errorResponse(request, ccInsufficientPrivilege);
285              }
286              return std::get<HandlerBase::ptr>(chosen)->call(request);
287          }
288      }
289      return errorResponse(request, ccInvalidCommand);
290  }
291  
executeIpmiGroupCommand(message::Request::ptr request)292  message::Response::ptr executeIpmiGroupCommand(message::Request::ptr request)
293  {
294      // look up the group for this request
295      uint8_t bytes;
296      if (0 != request->payload.unpack(bytes))
297      {
298          return errorResponse(request, ccReqDataLenInvalid);
299      }
300      auto group = static_cast<Group>(bytes);
301      message::Response::ptr response =
302          executeIpmiCommandCommon(groupHandlerMap, group, request);
303      ipmi::message::Payload prefix;
304      prefix.pack(bytes);
305      response->prepend(prefix);
306      return response;
307  }
308  
executeIpmiOemCommand(message::Request::ptr request)309  message::Response::ptr executeIpmiOemCommand(message::Request::ptr request)
310  {
311      // look up the iana for this request
312      uint24_t bytes;
313      if (0 != request->payload.unpack(bytes))
314      {
315          return errorResponse(request, ccReqDataLenInvalid);
316      }
317      auto iana = static_cast<Iana>(bytes);
318  
319      lg2::debug("unpack IANA {IANA}", "IANA", lg2::hex, iana);
320  
321      message::Response::ptr response =
322          executeIpmiCommandCommon(oemHandlerMap, iana, request);
323      ipmi::message::Payload prefix;
324      prefix.pack(bytes);
325      response->prepend(prefix);
326      return response;
327  }
328  
executeIpmiCommand(message::Request::ptr request)329  message::Response::ptr executeIpmiCommand(message::Request::ptr request)
330  {
331      NetFn netFn = request->ctx->netFn;
332      if (netFnGroup == netFn)
333      {
334          return executeIpmiGroupCommand(request);
335      }
336      else if (netFnOem == netFn)
337      {
338          return executeIpmiOemCommand(request);
339      }
340      return executeIpmiCommandCommon(handlerMap, netFn, request);
341  }
342  
343  namespace utils
344  {
345  template <typename AssocContainer, typename UnaryPredicate>
assoc_erase_if(AssocContainer & c,UnaryPredicate p)346  void assoc_erase_if(AssocContainer& c, UnaryPredicate p)
347  {
348      typename AssocContainer::iterator next = c.begin();
349      typename AssocContainer::iterator last = c.end();
350      while ((next = std::find_if(next, last, p)) != last)
351      {
352          c.erase(next++);
353      }
354  }
355  } // namespace utils
356  
357  namespace
358  {
359  std::unordered_map<std::string, uint8_t> uniqueNameToChannelNumber;
360  
361  // sdbusplus::bus::match::rules::arg0namespace() wants the prefix
362  // to match without any trailing '.'
363  constexpr const char ipmiDbusChannelMatch[] =
364      "xyz.openbmc_project.Ipmi.Channel";
updateOwners(sdbusplus::asio::connection & conn,const std::string & name)365  void updateOwners(sdbusplus::asio::connection& conn, const std::string& name)
366  {
367      conn.async_method_call(
368          [name](const boost::system::error_code ec,
369                 const std::string& nameOwner) {
370              if (ec)
371              {
372                  lg2::error("Error getting dbus owner for {INTERFACE}",
373                             "INTERFACE", name);
374                  return;
375              }
376              // start after ipmiDbusChannelPrefix (after the '.')
377              std::string chName =
378                  name.substr(std::strlen(ipmiDbusChannelMatch) + 1);
379              try
380              {
381                  uint8_t channel = getChannelByName(chName);
382                  uniqueNameToChannelNumber[nameOwner] = channel;
383                  lg2::info(
384                      "New interface mapping: {INTERFACE} -> channel {CHANNEL}",
385                      "INTERFACE", name, "CHANNEL", channel);
386              }
387              catch (const std::exception& e)
388              {
389                  lg2::info("Failed interface mapping, no such name: {INTERFACE}",
390                            "INTERFACE", name);
391              }
392          },
393          "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetNameOwner",
394          name);
395  }
396  
doListNames(sdbusplus::asio::connection & conn)397  void doListNames(sdbusplus::asio::connection& conn)
398  {
399      conn.async_method_call(
400          [&conn](const boost::system::error_code ec,
401                  std::vector<std::string> busNames) {
402              if (ec)
403              {
404                  lg2::error("Error getting dbus names: {ERROR}", "ERROR",
405                             ec.message());
406                  std::exit(EXIT_FAILURE);
407                  return;
408              }
409              // Try to make startup consistent
410              std::sort(busNames.begin(), busNames.end());
411  
412              const std::string channelPrefix =
413                  std::string(ipmiDbusChannelMatch) + ".";
414              for (const std::string& busName : busNames)
415              {
416                  if (busName.find(channelPrefix) == 0)
417                  {
418                      updateOwners(conn, busName);
419                  }
420              }
421          },
422          "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus",
423          "ListNames");
424  }
425  
nameChangeHandler(sdbusplus::message_t & message)426  void nameChangeHandler(sdbusplus::message_t& message)
427  {
428      std::string name;
429      std::string oldOwner;
430      std::string newOwner;
431  
432      message.read(name, oldOwner, newOwner);
433  
434      if (!oldOwner.empty())
435      {
436          if (boost::starts_with(oldOwner, ":"))
437          {
438              // Connection removed
439              auto it = uniqueNameToChannelNumber.find(oldOwner);
440              if (it != uniqueNameToChannelNumber.end())
441              {
442                  uniqueNameToChannelNumber.erase(it);
443              }
444          }
445      }
446      if (!newOwner.empty())
447      {
448          // start after ipmiDbusChannelMatch (and after the '.')
449          std::string chName = name.substr(std::strlen(ipmiDbusChannelMatch) + 1);
450          try
451          {
452              uint8_t channel = getChannelByName(chName);
453              uniqueNameToChannelNumber[newOwner] = channel;
454              lg2::info("New interface mapping: {INTERFACE} -> channel {CHANNEL}",
455                        "INTERFACE", name, "CHANNEL", channel);
456          }
457          catch (const std::exception& e)
458          {
459              lg2::info("Failed interface mapping, no such name: {INTERFACE}",
460                        "INTERFACE", name);
461          }
462      }
463  };
464  
465  } // anonymous namespace
466  
467  static constexpr const char intraBmcName[] = "INTRABMC";
channelFromMessage(sdbusplus::message_t & msg)468  uint8_t channelFromMessage(sdbusplus::message_t& msg)
469  {
470      // channel name for ipmitool to resolve to
471      std::string sender = msg.get_sender();
472      auto chIter = uniqueNameToChannelNumber.find(sender);
473      if (chIter != uniqueNameToChannelNumber.end())
474      {
475          return chIter->second;
476      }
477      // FIXME: currently internal connections are ephemeral and hard to pin down
478      try
479      {
480          return getChannelByName(intraBmcName);
481      }
482      catch (const std::exception& e)
483      {
484          return invalidChannel;
485      }
486  } // namespace ipmi
487  
488  /* called from sdbus async server context */
executionEntry(boost::asio::yield_context yield,sdbusplus::message_t & m,NetFn netFn,uint8_t lun,Cmd cmd,ipmi::SecureBuffer & data,std::map<std::string,ipmi::Value> & options)489  auto executionEntry(boost::asio::yield_context yield, sdbusplus::message_t& m,
490                      NetFn netFn, uint8_t lun, Cmd cmd, ipmi::SecureBuffer& data,
491                      std::map<std::string, ipmi::Value>& options)
492  {
493      const auto dbusResponse =
494          [netFn, lun, cmd](Cc cc, const ipmi::SecureBuffer& data = {}) {
495              constexpr uint8_t netFnResponse = 0x01;
496              uint8_t retNetFn = netFn | netFnResponse;
497              return std::make_tuple(retNetFn, lun, cmd, cc, data);
498          };
499      std::string sender = m.get_sender();
500      Privilege privilege = Privilege::None;
501      int rqSA = 0;
502      int hostIdx = 0;
503      uint8_t userId = 0; // undefined user
504      uint32_t sessionId = 0;
505  
506      // figure out what channel the request came in on
507      uint8_t channel = channelFromMessage(m);
508      if (channel == invalidChannel)
509      {
510          // unknown sender channel; refuse to service the request
511          lg2::error("ERROR determining source IPMI channel from "
512                     "{SENDER} NetFn/Cmd {NETFN}/{CMD}",
513                     "SENDER", sender, "NETFN", lg2::hex, netFn, "CMD", lg2::hex,
514                     cmd);
515          return dbusResponse(ipmi::ccDestinationUnavailable);
516      }
517  
518      // session-based channels are required to provide userId, privilege and
519      // sessionId
520      if (getChannelSessionSupport(channel) != EChannelSessSupported::none)
521      {
522          try
523          {
524              Value requestPriv = options.at("privilege");
525              Value requestUserId = options.at("userId");
526              Value requestSessionId = options.at("currentSessionId");
527              privilege = static_cast<Privilege>(std::get<int>(requestPriv));
528              userId = static_cast<uint8_t>(std::get<int>(requestUserId));
529              sessionId =
530                  static_cast<uint32_t>(std::get<uint32_t>(requestSessionId));
531          }
532          catch (const std::exception& e)
533          {
534              lg2::error("ERROR determining IPMI session credentials on "
535                         "channel {CHANNEL} for userid {USERID}",
536                         "CHANNEL", channel, "USERID", userId, "NETFN", lg2::hex,
537                         netFn, "CMD", lg2::hex, cmd);
538              return dbusResponse(ipmi::ccUnspecifiedError);
539          }
540      }
541      else
542      {
543          // get max privilege for session-less channels
544          // For now, there is not a way to configure this, default to Admin
545          privilege = Privilege::Admin;
546  
547          // ipmb should supply rqSA
548          ChannelInfo chInfo;
549          getChannelInfo(channel, chInfo);
550          if (static_cast<EChannelMediumType>(chInfo.mediumType) ==
551              EChannelMediumType::ipmb)
552          {
553              const auto iter = options.find("rqSA");
554              if (iter != options.end())
555              {
556                  if (std::holds_alternative<int>(iter->second))
557                  {
558                      rqSA = std::get<int>(iter->second);
559                  }
560              }
561              const auto iteration = options.find("hostId");
562              if (iteration != options.end())
563              {
564                  if (std::holds_alternative<int>(iteration->second))
565                  {
566                      hostIdx = std::get<int>(iteration->second);
567                  }
568              }
569          }
570      }
571      // check to see if the requested priv/username is valid
572      lg2::debug("Set up ipmi context: Ch:NetFn/Cmd={CHANNEL}:{NETFN}/{CMD}",
573                 "SENDER", sender, "NETFN", lg2::hex, netFn, "LUN", lg2::hex, lun,
574                 "CMD", lg2::hex, cmd, "CHANNEL", channel, "USERID", userId,
575                 "SESSIONID", lg2::hex, sessionId, "PRIVILEGE",
576                 static_cast<uint8_t>(privilege), "RQSA", lg2::hex, rqSA);
577  
578      auto ctx = std::make_shared<ipmi::Context>(
579          getSdBus(), netFn, lun, cmd, channel, userId, sessionId, privilege,
580          rqSA, hostIdx, yield);
581      auto request = std::make_shared<ipmi::message::Request>(
582          ctx, std::forward<ipmi::SecureBuffer>(data));
583      message::Response::ptr response = executeIpmiCommand(request);
584  
585      return dbusResponse(response->cc, response->payload.raw);
586  }
587  
588  /** @struct IpmiProvider
589   *
590   *  RAII wrapper for dlopen so that dlclose gets called on exit
591   */
592  struct IpmiProvider
593  {
594    public:
595      /** @brief address of the opened library */
596      void* addr;
597      std::string name;
598  
599      IpmiProvider() = delete;
600      IpmiProvider(const IpmiProvider&) = delete;
601      IpmiProvider& operator=(const IpmiProvider&) = delete;
602      IpmiProvider(IpmiProvider&&) = delete;
603      IpmiProvider& operator=(IpmiProvider&&) = delete;
604  
605      /** @brief dlopen a shared object file by path
606       *  @param[in]  filename - path of shared object to open
607       */
IpmiProvideripmi::IpmiProvider608      explicit IpmiProvider(const char* fname) : addr(nullptr), name(fname)
609      {
610          lg2::debug("Open IPMI provider library: {PROVIDER}", "PROVIDER", name);
611          try
612          {
613              addr = dlopen(name.c_str(), RTLD_NOW);
614          }
615          catch (const std::exception& e)
616          {
617              lg2::error("ERROR opening IPMI provider {PROVIDER}: {ERROR}",
618                         "PROVIDER", name, "ERROR", e);
619          }
620          catch (...)
621          {
622              const char* what = currentExceptionType();
623              lg2::error("ERROR opening IPMI provider {PROVIDER}: {ERROR}",
624                         "PROVIDER", name, "ERROR", what);
625          }
626          if (!isOpen())
627          {
628              lg2::error("ERROR opening IPMI provider {PROVIDER}: {ERROR}",
629                         "PROVIDER", name, "ERROR", dlerror());
630          }
631      }
632  
~IpmiProvideripmi::IpmiProvider633      ~IpmiProvider()
634      {
635          if (isOpen())
636          {
637              dlclose(addr);
638          }
639      }
isOpenipmi::IpmiProvider640      bool isOpen() const
641      {
642          return (nullptr != addr);
643      }
644  };
645  
646  // Plugin libraries need to contain .so either at the end or in the middle
647  constexpr const char ipmiPluginExtn[] = ".so";
648  
649  /* return a list of self-closing library handles */
loadProviders(const fs::path & ipmiLibsPath)650  std::forward_list<IpmiProvider> loadProviders(const fs::path& ipmiLibsPath)
651  {
652      std::vector<fs::path> libs;
653      for (const auto& libPath : fs::directory_iterator(ipmiLibsPath))
654      {
655          std::error_code ec;
656          fs::path fname = libPath.path();
657          if (fs::is_symlink(fname, ec) || ec)
658          {
659              // it's a symlink or some other error; skip it
660              continue;
661          }
662          while (fname.has_extension())
663          {
664              fs::path extn = fname.extension();
665              if (extn == ipmiPluginExtn)
666              {
667                  libs.push_back(libPath.path());
668                  break;
669              }
670              fname.replace_extension();
671          }
672      }
673      std::sort(libs.begin(), libs.end());
674  
675      std::forward_list<IpmiProvider> handles;
676      for (auto& lib : libs)
677      {
678  #ifdef __IPMI_DEBUG__
679          lg2::debug("Registering handler {HANDLER}", "HANDLER", lib);
680  #endif
681          handles.emplace_front(lib.c_str());
682      }
683      return handles;
684  }
685  
686  } // namespace ipmi
687  
688  #ifdef ALLOW_DEPRECATED_API
689  /* legacy registration */
ipmi_register_callback(ipmi_netfn_t netFn,ipmi_cmd_t cmd,ipmi_context_t context,ipmid_callback_t handler,ipmi_cmd_privilege_t priv)690  void ipmi_register_callback(ipmi_netfn_t netFn, ipmi_cmd_t cmd,
691                              ipmi_context_t context, ipmid_callback_t handler,
692                              ipmi_cmd_privilege_t priv)
693  {
694      auto h = ipmi::makeLegacyHandler(handler, context);
695      // translate priv from deprecated enum to current
696      ipmi::Privilege realPriv;
697      switch (priv)
698      {
699          case PRIVILEGE_CALLBACK:
700              realPriv = ipmi::Privilege::Callback;
701              break;
702          case PRIVILEGE_USER:
703              realPriv = ipmi::Privilege::User;
704              break;
705          case PRIVILEGE_OPERATOR:
706              realPriv = ipmi::Privilege::Operator;
707              break;
708          case PRIVILEGE_ADMIN:
709              realPriv = ipmi::Privilege::Admin;
710              break;
711          case PRIVILEGE_OEM:
712              realPriv = ipmi::Privilege::Oem;
713              break;
714          case SYSTEM_INTERFACE:
715              realPriv = ipmi::Privilege::Admin;
716              break;
717          default:
718              realPriv = ipmi::Privilege::Admin;
719              break;
720      }
721      // The original ipmi_register_callback allowed for group OEM handlers
722      // to be registered via this same interface. It just so happened that
723      // all the handlers were part of the DCMI group, so default to that.
724      if (netFn == NETFUN_GRPEXT)
725      {
726          ipmi::impl::registerGroupHandler(ipmi::prioOpenBmcBase, ipmi::groupDCMI,
727                                           cmd, realPriv, h);
728      }
729      else
730      {
731          ipmi::impl::registerHandler(ipmi::prioOpenBmcBase, netFn, cmd, realPriv,
732                                      h);
733      }
734  }
735  
736  namespace oem
737  {
738  
739  class LegacyRouter : public oem::Router
740  {
741    public:
~LegacyRouter()742      virtual ~LegacyRouter() {}
743  
744      /// Enable message routing to begin.
activate()745      void activate() override {}
746  
registerHandler(Number oen,ipmi_cmd_t cmd,Handler handler)747      void registerHandler(Number oen, ipmi_cmd_t cmd, Handler handler) override
748      {
749          auto h = ipmi::makeLegacyHandler(std::forward<Handler>(handler));
750          ipmi::impl::registerOemHandler(ipmi::prioOpenBmcBase, oen, cmd,
751                                         ipmi::Privilege::Admin, h);
752      }
753  };
754  static LegacyRouter legacyRouter;
755  
mutableRouter()756  Router* mutableRouter()
757  {
758      return &legacyRouter;
759  }
760  
761  } // namespace oem
762  
763  /* legacy alternative to executionEntry */
handleLegacyIpmiCommand(sdbusplus::message_t & m)764  void handleLegacyIpmiCommand(sdbusplus::message_t& m)
765  {
766      // make a copy so the next two moves don't wreak havoc on the stack
767      sdbusplus::message_t b{m};
768      (void)boost::asio::spawn(
769          *getIoContext(),
770          [b = std::move(b)](boost::asio::yield_context yield) {
771              sdbusplus::message_t m{std::move(b)};
772              unsigned char seq = 0, netFn = 0, lun = 0, cmd = 0;
773              ipmi::SecureBuffer data;
774  
775              m.read(seq, netFn, lun, cmd, data);
776              std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus();
777              auto ctx = std::make_shared<ipmi::Context>(
778                  bus, netFn, lun, cmd, 0, 0, 0, ipmi::Privilege::Admin, 0, 0,
779                  yield);
780              auto request = std::make_shared<ipmi::message::Request>(
781                  ctx, std::forward<ipmi::SecureBuffer>(data));
782              ipmi::message::Response::ptr response =
783                  ipmi::executeIpmiCommand(request);
784  
785              // Responses in IPMI require a bit set.  So there ya go...
786              netFn |= 0x01;
787  
788              const char *dest, *path;
789              constexpr const char* DBUS_INTF = "org.openbmc.HostIpmi";
790  
791              dest = m.get_sender();
792              path = m.get_path();
793              boost::system::error_code ec;
794              bus->yield_method_call(yield, ec, dest, path, DBUS_INTF,
795                                     "sendMessage", seq, netFn, lun, cmd,
796                                     response->cc, response->payload.raw);
797              if (ec)
798              {
799                  lg2::error(
800                      "Failed to send response to requestor ({NETFN}/{CMD}): {ERROR}",
801                      "ERROR", ec.message(), "SENDER", dest, "NETFN", lg2::hex,
802                      netFn, "CMD", lg2::hex, cmd);
803              }
804          },
805          {});
806  }
807  
808  #endif /* ALLOW_DEPRECATED_API */
809  
810  // Calls host command manager to do the right thing for the command
811  using CommandHandler = phosphor::host::command::CommandHandler;
812  std::unique_ptr<phosphor::host::command::Manager> cmdManager;
ipmid_send_cmd_to_host(CommandHandler && cmd)813  void ipmid_send_cmd_to_host(CommandHandler&& cmd)
814  {
815      cmdManager->execute(std::forward<CommandHandler>(cmd));
816  }
817  
ipmid_get_host_cmd_manager()818  std::unique_ptr<phosphor::host::command::Manager>& ipmid_get_host_cmd_manager()
819  {
820      return cmdManager;
821  }
822  
823  // These are symbols that are present in libipmid, but not expected
824  // to be used except here (or maybe a unit test), so declare them here
825  extern void setIoContext(std::shared_ptr<boost::asio::io_context>& newIo);
826  extern void setSdBus(std::shared_ptr<sdbusplus::asio::connection>& newBus);
827  
main(int argc,char * argv[])828  int main(int argc, char* argv[])
829  {
830      // Connect to system bus
831      auto io = std::make_shared<boost::asio::io_context>();
832      setIoContext(io);
833      if (argc > 1 && std::string(argv[1]) == "-session")
834      {
835          sd_bus_default_user(&bus);
836      }
837      else
838      {
839          sd_bus_default_system(&bus);
840      }
841      auto sdbusp = std::make_shared<sdbusplus::asio::connection>(*io, bus);
842      setSdBus(sdbusp);
843  
844      // TODO: Hack to keep the sdEvents running.... Not sure why the sd_event
845      //       queue stops running if we don't have a timer that keeps re-arming
846      sdbusplus::Timer t2([]() { ; });
847      t2.start(std::chrono::microseconds(500000), true);
848  
849      // TODO: Remove all vestiges of sd_event from phosphor-host-ipmid
850      //       until that is done, add the sd_event wrapper to the io object
851      sdbusplus::asio::sd_event_wrapper sdEvents(*io);
852  
853      cmdManager = std::make_unique<phosphor::host::command::Manager>(*sdbusp);
854  
855      // Register all command providers and filters
856      std::forward_list<ipmi::IpmiProvider> providers =
857          ipmi::loadProviders(HOST_IPMI_LIB_PATH);
858  
859  #ifdef ALLOW_DEPRECATED_API
860      // listen on deprecated signal interface for kcs/bt commands
861      constexpr const char* FILTER = "type='signal',interface='org.openbmc."
862                                     "HostIpmi',member='ReceivedMessage'";
863      sdbusplus::bus::match_t oldIpmiInterface(*sdbusp, FILTER,
864                                               handleLegacyIpmiCommand);
865  #endif /* ALLOW_DEPRECATED_API */
866  
867      // set up bus name watching to match channels with bus names
868      sdbusplus::bus::match_t nameOwnerChanged(
869          *sdbusp,
870          sdbusplus::bus::match::rules::nameOwnerChanged() +
871              sdbusplus::bus::match::rules::arg0namespace(
872                  ipmi::ipmiDbusChannelMatch),
873          ipmi::nameChangeHandler);
874      ipmi::doListNames(*sdbusp);
875  
876      int exitCode = 0;
877      // set up boost::asio signal handling
878      std::function<SignalResponse(int)> stopAsioRunLoop = [&io, &exitCode](
879                                                               int signalNumber) {
880          lg2::info("Received signal {SIGNAL}; quitting", "SIGNAL", signalNumber);
881          io->stop();
882          exitCode = signalNumber;
883          return SignalResponse::breakExecution;
884      };
885      registerSignalHandler(ipmi::prioOpenBmcBase, SIGINT, stopAsioRunLoop);
886      registerSignalHandler(ipmi::prioOpenBmcBase, SIGTERM, stopAsioRunLoop);
887  
888      sdbusp->request_name("xyz.openbmc_project.Ipmi.Host");
889      // Add bindings for inbound IPMI requests
890      auto server = sdbusplus::asio::object_server(sdbusp);
891      auto iface = server.add_interface("/xyz/openbmc_project/Ipmi",
892                                        "xyz.openbmc_project.Ipmi.Server");
893      iface->register_method("execute", ipmi::executionEntry);
894      iface->initialize();
895  
896      io->run();
897  
898      // destroy all the IPMI handlers so the providers can unload safely
899      ipmi::handlerMap.clear();
900      ipmi::groupHandlerMap.clear();
901      ipmi::oemHandlerMap.clear();
902      ipmi::filterList.clear();
903      // unload the provider libraries
904      providers.clear();
905  
906      std::exit(exitCode);
907  }
908