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 <exception>
25 #include <forward_list>
26 #include <host-cmd-manager.hpp>
27 #include <ipmid-host/cmd.hpp>
28 #include <ipmid/api.hpp>
29 #include <ipmid/handler.hpp>
30 #include <ipmid/message.hpp>
31 #include <ipmid/oemrouter.hpp>
32 #include <ipmid/registration.hpp>
33 #include <map>
34 #include <memory>
35 #include <optional>
36 #include <phosphor-logging/log.hpp>
37 #include <sdbusplus/asio/connection.hpp>
38 #include <sdbusplus/asio/object_server.hpp>
39 #include <sdbusplus/asio/sd_event.hpp>
40 #include <sdbusplus/bus.hpp>
41 #include <sdbusplus/bus/match.hpp>
42 #include <sdbusplus/timer.hpp>
43 #include <tuple>
44 #include <types.hpp>
45 #include <unordered_map>
46 #include <utility>
47 #include <vector>
48 
49 #if __has_include(<filesystem>)
50 #include <filesystem>
51 #elif __has_include(<experimental/filesystem>)
52 #include <experimental/filesystem>
53 namespace std
54 {
55 // splice experimental::filesystem into std
56 namespace filesystem = std::experimental::filesystem;
57 } // namespace std
58 #else
59 #error filesystem not available
60 #endif
61 
62 namespace fs = std::filesystem;
63 
64 using namespace phosphor::logging;
65 
66 // Global timer for network changes
67 std::unique_ptr<phosphor::Timer> networkTimer = nullptr;
68 
69 // IPMI Spec, shared Reservation ID.
70 static unsigned short selReservationID = 0xFFFF;
71 static bool selReservationValid = false;
72 
73 unsigned short reserveSel(void)
74 {
75     // IPMI spec, Reservation ID, the value simply increases against each
76     // execution of the Reserve SEL command.
77     if (++selReservationID == 0)
78     {
79         selReservationID = 1;
80     }
81     selReservationValid = true;
82     return selReservationID;
83 }
84 
85 bool checkSELReservation(unsigned short id)
86 {
87     return (selReservationValid && selReservationID == id);
88 }
89 
90 void cancelSELReservation(void)
91 {
92     selReservationValid = false;
93 }
94 
95 EInterfaceIndex getInterfaceIndex(void)
96 {
97     return interfaceKCS;
98 }
99 
100 sd_bus* bus;
101 sd_event* events = nullptr;
102 sd_event* ipmid_get_sd_event_connection(void)
103 {
104     return events;
105 }
106 sd_bus* ipmid_get_sd_bus_connection(void)
107 {
108     return bus;
109 }
110 
111 namespace ipmi
112 {
113 
114 static inline unsigned int makeCmdKey(unsigned int cluster, unsigned int cmd)
115 {
116     return (cluster << 8) | cmd;
117 }
118 
119 using HandlerTuple = std::tuple<int,                        /* prio */
120                                 Privilege, HandlerBase::ptr /* handler */
121                                 >;
122 
123 /* map to handle standard registered commands */
124 static std::unordered_map<unsigned int, /* key is NetFn/Cmd */
125                           HandlerTuple>
126     handlerMap;
127 
128 /* special map for decoding Group registered commands (NetFn 2Ch) */
129 static std::unordered_map<unsigned int, /* key is Group/Cmd (NetFn is 2Ch) */
130                           HandlerTuple>
131     groupHandlerMap;
132 
133 /* special map for decoding OEM registered commands (NetFn 2Eh) */
134 static std::unordered_map<unsigned int, /* key is Iana/Cmd (NetFn is 2Eh) */
135                           HandlerTuple>
136     oemHandlerMap;
137 
138 using FilterTuple = std::tuple<int,            /* prio */
139                                FilterBase::ptr /* filter */
140                                >;
141 
142 /* list to hold all registered ipmi command filters */
143 static std::forward_list<FilterTuple> filterList;
144 
145 namespace impl
146 {
147 /* common function to register all standard IPMI handlers */
148 bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv,
149                      HandlerBase::ptr handler)
150 {
151     // check for valid NetFn: even; 00-0Ch, 30-3Eh
152     if (netFn & 1 || (netFn > netFnTransport && netFn < netFnGroup) ||
153         netFn > netFnOemEight)
154     {
155         return false;
156     }
157 
158     // create key and value for this handler
159     unsigned int netFnCmd = makeCmdKey(netFn, cmd);
160     HandlerTuple item(prio, priv, handler);
161 
162     // consult the handler map and look for a match
163     auto& mapCmd = handlerMap[netFnCmd];
164     if (!std::get<HandlerBase::ptr>(mapCmd) || std::get<int>(mapCmd) <= prio)
165     {
166         mapCmd = item;
167         return true;
168     }
169     return false;
170 }
171 
172 /* common function to register all Group IPMI handlers */
173 bool registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv,
174                           HandlerBase::ptr handler)
175 {
176     // create key and value for this handler
177     unsigned int netFnCmd = makeCmdKey(group, cmd);
178     HandlerTuple item(prio, priv, handler);
179 
180     // consult the handler map and look for a match
181     auto& mapCmd = groupHandlerMap[netFnCmd];
182     if (!std::get<HandlerBase::ptr>(mapCmd) || std::get<int>(mapCmd) <= prio)
183     {
184         mapCmd = item;
185         return true;
186     }
187     return false;
188 }
189 
190 /* common function to register all OEM IPMI handlers */
191 bool registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv,
192                         HandlerBase::ptr handler)
193 {
194     // create key and value for this handler
195     unsigned int netFnCmd = makeCmdKey(iana, cmd);
196     HandlerTuple item(prio, priv, handler);
197 
198     // consult the handler map and look for a match
199     auto& mapCmd = oemHandlerMap[netFnCmd];
200     if (!std::get<HandlerBase::ptr>(mapCmd) || std::get<int>(mapCmd) <= prio)
201     {
202         mapCmd = item;
203         return true;
204     }
205     return false;
206 }
207 
208 /* common function to register all IPMI filter handlers */
209 void registerFilter(int prio, FilterBase::ptr filter)
210 {
211     // check for initial placement
212     if (filterList.empty() || std::get<int>(filterList.front()) < prio)
213     {
214         filterList.emplace_front(std::make_tuple(prio, filter));
215     }
216     // walk the list and put it in the right place
217     auto j = filterList.begin();
218     for (auto i = j; i != filterList.end() && std::get<int>(*i) > prio; i++)
219     {
220         j = i;
221     }
222     filterList.emplace_after(j, std::make_tuple(prio, filter));
223 }
224 
225 } // namespace impl
226 
227 message::Response::ptr filterIpmiCommand(message::Request::ptr request)
228 {
229     // pass the command through the filter mechanism
230     // This can be the firmware firewall or any OEM mechanism like
231     // whitelist filtering based on operational mode
232     for (auto& item : filterList)
233     {
234         FilterBase::ptr filter = std::get<FilterBase::ptr>(item);
235         ipmi::Cc cc = filter->call(request);
236         if (ipmi::ccSuccess != cc)
237         {
238             return errorResponse(request, cc);
239         }
240     }
241     return message::Response::ptr();
242 }
243 
244 message::Response::ptr executeIpmiCommandCommon(
245     std::unordered_map<unsigned int, HandlerTuple>& handlers,
246     unsigned int keyCommon, message::Request::ptr request)
247 {
248     // filter the command first; a non-null message::Response::ptr
249     // means that the message has been rejected for some reason
250     message::Response::ptr response = filterIpmiCommand(request);
251     if (response)
252     {
253         return response;
254     }
255 
256     Cmd cmd = request->ctx->cmd;
257     unsigned int key = makeCmdKey(keyCommon, cmd);
258     auto cmdIter = handlers.find(key);
259     if (cmdIter != handlers.end())
260     {
261         HandlerTuple& chosen = cmdIter->second;
262         if (request->ctx->priv < std::get<Privilege>(chosen))
263         {
264             return errorResponse(request, ccInsufficientPrivilege);
265         }
266         return std::get<HandlerBase::ptr>(chosen)->call(request);
267     }
268     else
269     {
270         unsigned int wildcard = makeCmdKey(keyCommon, cmdWildcard);
271         cmdIter = handlers.find(wildcard);
272         if (cmdIter != handlers.end())
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 
285 message::Response::ptr executeIpmiGroupCommand(message::Request::ptr request)
286 {
287     // look up the group for this request
288     Group group;
289     if (0 != request->unpack(group))
290     {
291         return errorResponse(request, ccReqDataLenInvalid);
292     }
293     // The handler will need to unpack group as well; we just need it for lookup
294     request->payload.reset();
295     message::Response::ptr response =
296         executeIpmiCommandCommon(groupHandlerMap, group, request);
297     // if the handler should add the group; executeIpmiCommandCommon does not
298     if (response->cc != ccSuccess && response->payload.size() == 0)
299     {
300         response->pack(group);
301     }
302     return response;
303 }
304 
305 message::Response::ptr executeIpmiOemCommand(message::Request::ptr request)
306 {
307     // look up the iana for this request
308     Iana iana;
309     if (0 != request->unpack(iana))
310     {
311         return errorResponse(request, ccReqDataLenInvalid);
312     }
313     request->payload.reset();
314     message::Response::ptr response =
315         executeIpmiCommandCommon(oemHandlerMap, iana, request);
316     // if the handler should add the iana; executeIpmiCommandCommon does not
317     if (response->cc != ccSuccess && response->payload.size() == 0)
318     {
319         response->pack(iana);
320     }
321     return response;
322 }
323 
324 message::Response::ptr executeIpmiCommand(message::Request::ptr request)
325 {
326     NetFn netFn = request->ctx->netFn;
327     if (netFnGroup == netFn)
328     {
329         return executeIpmiGroupCommand(request);
330     }
331     else if (netFnOem == netFn)
332     {
333         return executeIpmiOemCommand(request);
334     }
335     return executeIpmiCommandCommon(handlerMap, netFn, request);
336 }
337 
338 /* called from sdbus async server context */
339 auto executionEntry(boost::asio::yield_context yield, NetFn netFn, uint8_t lun,
340                     Cmd cmd, std::vector<uint8_t>& data,
341                     std::map<std::string, ipmi::Value>& options)
342 {
343     auto ctx = std::make_shared<ipmi::Context>(netFn, cmd, 0, 0,
344                                                ipmi::Privilege::Admin, &yield);
345     auto request = std::make_shared<ipmi::message::Request>(
346         ctx, std::forward<std::vector<uint8_t>>(data));
347     message::Response::ptr response = executeIpmiCommand(request);
348 
349     // Responses in IPMI require a bit set.  So there ya go...
350     netFn |= 0x01;
351     return std::make_tuple(netFn, lun, cmd, response->cc,
352                            response->payload.raw);
353 }
354 
355 /** @struct IpmiProvider
356  *
357  *  RAII wrapper for dlopen so that dlclose gets called on exit
358  */
359 struct IpmiProvider
360 {
361   public:
362     /** @brief address of the opened library */
363     void* addr;
364     std::string name;
365 
366     IpmiProvider() = delete;
367     IpmiProvider(const IpmiProvider&) = delete;
368     IpmiProvider& operator=(const IpmiProvider&) = delete;
369     IpmiProvider(IpmiProvider&&) = delete;
370     IpmiProvider& operator=(IpmiProvider&&) = delete;
371 
372     /** @brief dlopen a shared object file by path
373      *  @param[in]  filename - path of shared object to open
374      */
375     explicit IpmiProvider(const char* fname) : addr(nullptr), name(fname)
376     {
377         log<level::DEBUG>("Open IPMI provider library",
378                           entry("PROVIDER=%s", name.c_str()));
379         try
380         {
381             addr = dlopen(name.c_str(), RTLD_NOW);
382         }
383         catch (std::exception& e)
384         {
385             log<level::ERR>("ERROR opening IPMI provider",
386                             entry("PROVIDER=%s", name.c_str()),
387                             entry("ERROR=%s", e.what()));
388         }
389         catch (...)
390         {
391             std::exception_ptr eptr = std::current_exception();
392             try
393             {
394                 std::rethrow_exception(eptr);
395             }
396             catch (std::exception& e)
397             {
398                 log<level::ERR>("ERROR opening IPMI provider",
399                                 entry("PROVIDER=%s", name.c_str()),
400                                 entry("ERROR=%s", e.what()));
401             }
402         }
403         if (!isOpen())
404         {
405             log<level::ERR>("ERROR opening IPMI provider",
406                             entry("PROVIDER=%s", name.c_str()),
407                             entry("ERROR=%s", dlerror()));
408         }
409     }
410 
411     ~IpmiProvider()
412     {
413         if (isOpen())
414         {
415             dlclose(addr);
416         }
417     }
418     bool isOpen() const
419     {
420         return (nullptr != addr);
421     }
422 };
423 
424 // Plugin libraries need to contain .so either at the end or in the middle
425 constexpr const char ipmiPluginExtn[] = ".so";
426 
427 /* return a list of self-closing library handles */
428 std::forward_list<IpmiProvider> loadProviders(const fs::path& ipmiLibsPath)
429 {
430     std::vector<fs::path> libs;
431     for (const auto& libPath : fs::directory_iterator(ipmiLibsPath))
432     {
433         fs::path fname = libPath.path();
434         while (fname.has_extension())
435         {
436             fs::path extn = fname.extension();
437             if (extn == ipmiPluginExtn)
438             {
439                 libs.push_back(libPath.path());
440                 break;
441             }
442             fname.replace_extension();
443         }
444     }
445     std::sort(libs.begin(), libs.end());
446 
447     std::forward_list<IpmiProvider> handles;
448     for (auto& lib : libs)
449     {
450 #ifdef __IPMI_DEBUG__
451         log<level::DEBUG>("Registering handler",
452                           entry("HANDLER=%s", lib.c_str()));
453 #endif
454         handles.emplace_front(lib.c_str());
455     }
456     return handles;
457 }
458 
459 } // namespace ipmi
460 
461 static std::shared_ptr<boost::asio::io_service> io;
462 std::shared_ptr<boost::asio::io_service> getIoService()
463 {
464     return io;
465 }
466 
467 static std::shared_ptr<sdbusplus::asio::connection> sdbusp;
468 std::shared_ptr<sdbusplus::asio::connection> getSdBus()
469 {
470     return sdbusp;
471 }
472 
473 #ifdef ALLOW_DEPRECATED_API
474 /* legacy registration */
475 void ipmi_register_callback(ipmi_netfn_t netFn, ipmi_cmd_t cmd,
476                             ipmi_context_t context, ipmid_callback_t handler,
477                             ipmi_cmd_privilege_t priv)
478 {
479     auto h = ipmi::makeLegacyHandler(handler);
480     // translate priv from deprecated enum to current
481     ipmi::Privilege realPriv;
482     switch (priv)
483     {
484         case PRIVILEGE_CALLBACK:
485             realPriv = ipmi::Privilege::Callback;
486             break;
487         case PRIVILEGE_USER:
488             realPriv = ipmi::Privilege::User;
489             break;
490         case PRIVILEGE_OPERATOR:
491             realPriv = ipmi::Privilege::Operator;
492             break;
493         case PRIVILEGE_ADMIN:
494             realPriv = ipmi::Privilege::Admin;
495             break;
496         case PRIVILEGE_OEM:
497             realPriv = ipmi::Privilege::Oem;
498             break;
499         case SYSTEM_INTERFACE:
500             realPriv = ipmi::Privilege::Admin;
501             break;
502         default:
503             realPriv = ipmi::Privilege::Admin;
504             break;
505     }
506     ipmi::impl::registerHandler(ipmi::prioOpenBmcBase, netFn, cmd, realPriv, h);
507 }
508 
509 namespace oem
510 {
511 
512 class LegacyRouter : public oem::Router
513 {
514   public:
515     virtual ~LegacyRouter()
516     {
517     }
518 
519     /// Enable message routing to begin.
520     void activate() override
521     {
522     }
523 
524     void registerHandler(Number oen, ipmi_cmd_t cmd, Handler handler) override
525     {
526         auto h = ipmi::makeLegacyHandler(std::forward<Handler>(handler));
527         ipmi::impl::registerOemHandler(ipmi::prioOpenBmcBase, oen, cmd,
528                                        ipmi::Privilege::Admin, h);
529     }
530 };
531 static LegacyRouter legacyRouter;
532 
533 Router* mutableRouter()
534 {
535     return &legacyRouter;
536 }
537 
538 } // namespace oem
539 
540 /* legacy alternative to executionEntry */
541 void handleLegacyIpmiCommand(sdbusplus::message::message& m)
542 {
543     unsigned char seq, netFn, lun, cmd;
544     std::vector<uint8_t> data;
545 
546     m.read(seq, netFn, lun, cmd, data);
547 
548     auto ctx = std::make_shared<ipmi::Context>(netFn, cmd, 0, 0,
549                                                ipmi::Privilege::Admin);
550     auto request = std::make_shared<ipmi::message::Request>(
551         ctx, std::forward<std::vector<uint8_t>>(data));
552     ipmi::message::Response::ptr response = ipmi::executeIpmiCommand(request);
553 
554     // Responses in IPMI require a bit set.  So there ya go...
555     netFn |= 0x01;
556 
557     const char *dest, *path;
558     constexpr const char* DBUS_INTF = "org.openbmc.HostIpmi";
559 
560     dest = m.get_sender();
561     path = m.get_path();
562     sdbusp->async_method_call([](boost::system::error_code ec) {}, dest, path,
563                               DBUS_INTF, "sendMessage", seq, netFn, lun, cmd,
564                               response->cc, response->payload.raw);
565 }
566 
567 #endif /* ALLOW_DEPRECATED_API */
568 
569 // Calls host command manager to do the right thing for the command
570 using CommandHandler = phosphor::host::command::CommandHandler;
571 std::unique_ptr<phosphor::host::command::Manager> cmdManager;
572 void ipmid_send_cmd_to_host(CommandHandler&& cmd)
573 {
574     return cmdManager->execute(std::move(cmd));
575 }
576 
577 std::unique_ptr<phosphor::host::command::Manager>& ipmid_get_host_cmd_manager()
578 {
579     return cmdManager;
580 }
581 
582 int main(int argc, char* argv[])
583 {
584     // Connect to system bus
585     io = std::make_shared<boost::asio::io_service>();
586     if (argc > 1 && std::string(argv[1]) == "-session")
587     {
588         sd_bus_default_user(&bus);
589     }
590     else
591     {
592         sd_bus_default_system(&bus);
593     }
594     sdbusp = std::make_shared<sdbusplus::asio::connection>(*io, bus);
595     sdbusp->request_name("xyz.openbmc_project.Ipmi.Host");
596 
597     // TODO: Hack to keep the sdEvents running.... Not sure why the sd_event
598     //       queue stops running if we don't have a timer that keeps re-arming
599     phosphor::Timer t2([]() { ; });
600     t2.start(std::chrono::microseconds(500000), true);
601 
602     // TODO: Remove all vestiges of sd_event from phosphor-host-ipmid
603     //       until that is done, add the sd_event wrapper to the io object
604     sdbusplus::asio::sd_event_wrapper sdEvents(*io);
605 
606     cmdManager = std::make_unique<phosphor::host::command::Manager>(*sdbusp);
607 
608     // Register all command providers and filters
609     auto handles = ipmi::loadProviders(HOST_IPMI_LIB_PATH);
610 
611     // Add bindings for inbound IPMI requests
612     auto server = sdbusplus::asio::object_server(sdbusp);
613     auto iface = server.add_interface("/xyz/openbmc_project/Ipmi",
614                                       "xyz.openbmc_project.Ipmi.Server");
615     iface->register_method("execute", ipmi::executionEntry);
616     iface->initialize();
617 
618 #ifdef ALLOW_DEPRECATED_API
619     // listen on deprecated signal interface for kcs/bt commands
620     constexpr const char* FILTER = "type='signal',interface='org.openbmc."
621                                    "HostIpmi',member='ReceivedMessage'";
622     sdbusplus::bus::match::match oldIpmiInterface(*sdbusp, FILTER,
623                                                   handleLegacyIpmiCommand);
624 #endif /* ALLOW_DEPRECATED_API */
625 
626     io->run();
627 
628     // This avoids a warning about unused variables
629     handles.clear();
630     return 0;
631 }
632