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