blob: 4986e4984f857c74e474dd64d0b86995e2a1bfa7 [file] [log] [blame]
Vernon Mauery240b1862018-10-08 12:05:16 -07001/**
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>
53namespace std
54{
55// splice experimental::filesystem into std
56namespace filesystem = std::experimental::filesystem;
57} // namespace std
58#else
59#error filesystem not available
60#endif
61
62namespace fs = std::filesystem;
63
64using namespace phosphor::logging;
65
66// Global timer for network changes
67std::unique_ptr<phosphor::Timer> networkTimer = nullptr;
68
69// IPMI Spec, shared Reservation ID.
70static unsigned short selReservationID = 0xFFFF;
71static bool selReservationValid = false;
72
73unsigned 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
85bool checkSELReservation(unsigned short id)
86{
87 return (selReservationValid && selReservationID == id);
88}
89
90void cancelSELReservation(void)
91{
92 selReservationValid = false;
93}
94
95EInterfaceIndex getInterfaceIndex(void)
96{
97 return interfaceKCS;
98}
99
100sd_bus* bus;
101sd_event* events = nullptr;
102sd_event* ipmid_get_sd_event_connection(void)
103{
104 return events;
105}
106sd_bus* ipmid_get_sd_bus_connection(void)
107{
108 return bus;
109}
110
111namespace ipmi
112{
113
114static inline unsigned int makeCmdKey(unsigned int cluster, unsigned int cmd)
115{
116 return (cluster << 8) | cmd;
117}
118
119using HandlerTuple = std::tuple<int, /* prio */
120 Privilege, HandlerBase::ptr /* handler */
121 >;
122
123/* map to handle standard registered commands */
124static std::unordered_map<unsigned int, /* key is NetFn/Cmd */
125 HandlerTuple>
126 handlerMap;
127
Vernon Maueryf984a012018-10-08 12:05:18 -0700128/* special map for decoding Group registered commands (NetFn 2Ch) */
129static 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) */
134static std::unordered_map<unsigned int, /* key is Iana/Cmd (NetFn is 2Eh) */
135 HandlerTuple>
136 oemHandlerMap;
137
Vernon Mauery08a70aa2018-11-07 09:36:22 -0800138using FilterTuple = std::tuple<int, /* prio */
139 FilterBase::ptr /* filter */
140 >;
141
142/* list to hold all registered ipmi command filters */
143static std::forward_list<FilterTuple> filterList;
144
Vernon Mauery240b1862018-10-08 12:05:16 -0700145namespace impl
146{
147/* common function to register all standard IPMI handlers */
148bool 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
Vernon Maueryf984a012018-10-08 12:05:18 -0700172/* common function to register all Group IPMI handlers */
173bool 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 */
191bool 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
Vernon Mauery08a70aa2018-11-07 09:36:22 -0800208/* common function to register all IPMI filter handlers */
209void 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
Vernon Mauery240b1862018-10-08 12:05:16 -0700225} // namespace impl
226
Vernon Mauery08a70aa2018-11-07 09:36:22 -0800227message::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
Vernon Mauery240b1862018-10-08 12:05:16 -0700244message::Response::ptr executeIpmiCommandCommon(
245 std::unordered_map<unsigned int, HandlerTuple>& handlers,
246 unsigned int keyCommon, message::Request::ptr request)
247{
Vernon Mauery08a70aa2018-11-07 09:36:22 -0800248 // 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
Vernon Mauery240b1862018-10-08 12:05:16 -0700256 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
Vernon Maueryf984a012018-10-08 12:05:18 -0700285message::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
305message::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
Vernon Mauery240b1862018-10-08 12:05:16 -0700324message::Response::ptr executeIpmiCommand(message::Request::ptr request)
325{
326 NetFn netFn = request->ctx->netFn;
Vernon Maueryf984a012018-10-08 12:05:18 -0700327 if (netFnGroup == netFn)
328 {
329 return executeIpmiGroupCommand(request);
330 }
331 else if (netFnOem == netFn)
332 {
333 return executeIpmiOemCommand(request);
334 }
Vernon Mauery240b1862018-10-08 12:05:16 -0700335 return executeIpmiCommandCommon(handlerMap, netFn, request);
336}
337
338/* called from sdbus async server context */
339auto 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 */
359struct 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
425constexpr const char ipmiPluginExtn[] = ".so";
426
427/* return a list of self-closing library handles */
428std::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
461static std::shared_ptr<boost::asio::io_service> io;
462std::shared_ptr<boost::asio::io_service> getIoService()
463{
464 return io;
465}
466
467static std::shared_ptr<sdbusplus::asio::connection> sdbusp;
468std::shared_ptr<sdbusplus::asio::connection> getSdBus()
469{
470 return sdbusp;
471}
472
473#ifdef ALLOW_DEPRECATED_API
474/* legacy registration */
475void 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
Vernon Maueryf984a012018-10-08 12:05:18 -0700509namespace oem
510{
511
512class 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};
531static LegacyRouter legacyRouter;
532
533Router* mutableRouter()
534{
535 return &legacyRouter;
536}
537
538} // namespace oem
539
Vernon Mauery240b1862018-10-08 12:05:16 -0700540/* legacy alternative to executionEntry */
541void 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
570using CommandHandler = phosphor::host::command::CommandHandler;
571std::unique_ptr<phosphor::host::command::Manager> cmdManager;
572void ipmid_send_cmd_to_host(CommandHandler&& cmd)
573{
574 return cmdManager->execute(std::move(cmd));
575}
576
577std::unique_ptr<phosphor::host::command::Manager>& ipmid_get_host_cmd_manager()
578{
579 return cmdManager;
580}
581
582int 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}