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