blob: a9aff36d74b97c32acd3413dd0f7e75b45da6ff5 [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 Mauery240b1862018-10-08 12:05:16 -0700138namespace impl
139{
140/* common function to register all standard IPMI handlers */
141bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv,
142 HandlerBase::ptr handler)
143{
144 // check for valid NetFn: even; 00-0Ch, 30-3Eh
145 if (netFn & 1 || (netFn > netFnTransport && netFn < netFnGroup) ||
146 netFn > netFnOemEight)
147 {
148 return false;
149 }
150
151 // create key and value for this handler
152 unsigned int netFnCmd = makeCmdKey(netFn, cmd);
153 HandlerTuple item(prio, priv, handler);
154
155 // consult the handler map and look for a match
156 auto& mapCmd = handlerMap[netFnCmd];
157 if (!std::get<HandlerBase::ptr>(mapCmd) || std::get<int>(mapCmd) <= prio)
158 {
159 mapCmd = item;
160 return true;
161 }
162 return false;
163}
164
Vernon Maueryf984a012018-10-08 12:05:18 -0700165/* common function to register all Group IPMI handlers */
166bool registerGroupHandler(int prio, Group group, Cmd cmd, Privilege priv,
167 HandlerBase::ptr handler)
168{
169 // create key and value for this handler
170 unsigned int netFnCmd = makeCmdKey(group, cmd);
171 HandlerTuple item(prio, priv, handler);
172
173 // consult the handler map and look for a match
174 auto& mapCmd = groupHandlerMap[netFnCmd];
175 if (!std::get<HandlerBase::ptr>(mapCmd) || std::get<int>(mapCmd) <= prio)
176 {
177 mapCmd = item;
178 return true;
179 }
180 return false;
181}
182
183/* common function to register all OEM IPMI handlers */
184bool registerOemHandler(int prio, Iana iana, Cmd cmd, Privilege priv,
185 HandlerBase::ptr handler)
186{
187 // create key and value for this handler
188 unsigned int netFnCmd = makeCmdKey(iana, cmd);
189 HandlerTuple item(prio, priv, handler);
190
191 // consult the handler map and look for a match
192 auto& mapCmd = oemHandlerMap[netFnCmd];
193 if (!std::get<HandlerBase::ptr>(mapCmd) || std::get<int>(mapCmd) <= prio)
194 {
195 mapCmd = item;
196 return true;
197 }
198 return false;
199}
200
Vernon Mauery240b1862018-10-08 12:05:16 -0700201} // namespace impl
202
203message::Response::ptr executeIpmiCommandCommon(
204 std::unordered_map<unsigned int, HandlerTuple>& handlers,
205 unsigned int keyCommon, message::Request::ptr request)
206{
207 Cmd cmd = request->ctx->cmd;
208 unsigned int key = makeCmdKey(keyCommon, cmd);
209 auto cmdIter = handlers.find(key);
210 if (cmdIter != handlers.end())
211 {
212 HandlerTuple& chosen = cmdIter->second;
213 if (request->ctx->priv < std::get<Privilege>(chosen))
214 {
215 return errorResponse(request, ccInsufficientPrivilege);
216 }
217 return std::get<HandlerBase::ptr>(chosen)->call(request);
218 }
219 else
220 {
221 unsigned int wildcard = makeCmdKey(keyCommon, cmdWildcard);
222 cmdIter = handlers.find(wildcard);
223 if (cmdIter != handlers.end())
224 {
225 HandlerTuple& chosen = cmdIter->second;
226 if (request->ctx->priv < std::get<Privilege>(chosen))
227 {
228 return errorResponse(request, ccInsufficientPrivilege);
229 }
230 return std::get<HandlerBase::ptr>(chosen)->call(request);
231 }
232 }
233 return errorResponse(request, ccInvalidCommand);
234}
235
Vernon Maueryf984a012018-10-08 12:05:18 -0700236message::Response::ptr executeIpmiGroupCommand(message::Request::ptr request)
237{
238 // look up the group for this request
239 Group group;
240 if (0 != request->unpack(group))
241 {
242 return errorResponse(request, ccReqDataLenInvalid);
243 }
244 // The handler will need to unpack group as well; we just need it for lookup
245 request->payload.reset();
246 message::Response::ptr response =
247 executeIpmiCommandCommon(groupHandlerMap, group, request);
248 // if the handler should add the group; executeIpmiCommandCommon does not
249 if (response->cc != ccSuccess && response->payload.size() == 0)
250 {
251 response->pack(group);
252 }
253 return response;
254}
255
256message::Response::ptr executeIpmiOemCommand(message::Request::ptr request)
257{
258 // look up the iana for this request
259 Iana iana;
260 if (0 != request->unpack(iana))
261 {
262 return errorResponse(request, ccReqDataLenInvalid);
263 }
264 request->payload.reset();
265 message::Response::ptr response =
266 executeIpmiCommandCommon(oemHandlerMap, iana, request);
267 // if the handler should add the iana; executeIpmiCommandCommon does not
268 if (response->cc != ccSuccess && response->payload.size() == 0)
269 {
270 response->pack(iana);
271 }
272 return response;
273}
274
Vernon Mauery240b1862018-10-08 12:05:16 -0700275message::Response::ptr executeIpmiCommand(message::Request::ptr request)
276{
277 NetFn netFn = request->ctx->netFn;
Vernon Maueryf984a012018-10-08 12:05:18 -0700278 if (netFnGroup == netFn)
279 {
280 return executeIpmiGroupCommand(request);
281 }
282 else if (netFnOem == netFn)
283 {
284 return executeIpmiOemCommand(request);
285 }
Vernon Mauery240b1862018-10-08 12:05:16 -0700286 return executeIpmiCommandCommon(handlerMap, netFn, request);
287}
288
289/* called from sdbus async server context */
290auto executionEntry(boost::asio::yield_context yield, NetFn netFn, uint8_t lun,
291 Cmd cmd, std::vector<uint8_t>& data,
292 std::map<std::string, ipmi::Value>& options)
293{
294 auto ctx = std::make_shared<ipmi::Context>(netFn, cmd, 0, 0,
295 ipmi::Privilege::Admin, &yield);
296 auto request = std::make_shared<ipmi::message::Request>(
297 ctx, std::forward<std::vector<uint8_t>>(data));
298 message::Response::ptr response = executeIpmiCommand(request);
299
300 // Responses in IPMI require a bit set. So there ya go...
301 netFn |= 0x01;
302 return std::make_tuple(netFn, lun, cmd, response->cc,
303 response->payload.raw);
304}
305
306/** @struct IpmiProvider
307 *
308 * RAII wrapper for dlopen so that dlclose gets called on exit
309 */
310struct IpmiProvider
311{
312 public:
313 /** @brief address of the opened library */
314 void* addr;
315 std::string name;
316
317 IpmiProvider() = delete;
318 IpmiProvider(const IpmiProvider&) = delete;
319 IpmiProvider& operator=(const IpmiProvider&) = delete;
320 IpmiProvider(IpmiProvider&&) = delete;
321 IpmiProvider& operator=(IpmiProvider&&) = delete;
322
323 /** @brief dlopen a shared object file by path
324 * @param[in] filename - path of shared object to open
325 */
326 explicit IpmiProvider(const char* fname) : addr(nullptr), name(fname)
327 {
328 log<level::DEBUG>("Open IPMI provider library",
329 entry("PROVIDER=%s", name.c_str()));
330 try
331 {
332 addr = dlopen(name.c_str(), RTLD_NOW);
333 }
334 catch (std::exception& e)
335 {
336 log<level::ERR>("ERROR opening IPMI provider",
337 entry("PROVIDER=%s", name.c_str()),
338 entry("ERROR=%s", e.what()));
339 }
340 catch (...)
341 {
342 std::exception_ptr eptr = std::current_exception();
343 try
344 {
345 std::rethrow_exception(eptr);
346 }
347 catch (std::exception& e)
348 {
349 log<level::ERR>("ERROR opening IPMI provider",
350 entry("PROVIDER=%s", name.c_str()),
351 entry("ERROR=%s", e.what()));
352 }
353 }
354 if (!isOpen())
355 {
356 log<level::ERR>("ERROR opening IPMI provider",
357 entry("PROVIDER=%s", name.c_str()),
358 entry("ERROR=%s", dlerror()));
359 }
360 }
361
362 ~IpmiProvider()
363 {
364 if (isOpen())
365 {
366 dlclose(addr);
367 }
368 }
369 bool isOpen() const
370 {
371 return (nullptr != addr);
372 }
373};
374
375// Plugin libraries need to contain .so either at the end or in the middle
376constexpr const char ipmiPluginExtn[] = ".so";
377
378/* return a list of self-closing library handles */
379std::forward_list<IpmiProvider> loadProviders(const fs::path& ipmiLibsPath)
380{
381 std::vector<fs::path> libs;
382 for (const auto& libPath : fs::directory_iterator(ipmiLibsPath))
383 {
384 fs::path fname = libPath.path();
385 while (fname.has_extension())
386 {
387 fs::path extn = fname.extension();
388 if (extn == ipmiPluginExtn)
389 {
390 libs.push_back(libPath.path());
391 break;
392 }
393 fname.replace_extension();
394 }
395 }
396 std::sort(libs.begin(), libs.end());
397
398 std::forward_list<IpmiProvider> handles;
399 for (auto& lib : libs)
400 {
401#ifdef __IPMI_DEBUG__
402 log<level::DEBUG>("Registering handler",
403 entry("HANDLER=%s", lib.c_str()));
404#endif
405 handles.emplace_front(lib.c_str());
406 }
407 return handles;
408}
409
410} // namespace ipmi
411
412static std::shared_ptr<boost::asio::io_service> io;
413std::shared_ptr<boost::asio::io_service> getIoService()
414{
415 return io;
416}
417
418static std::shared_ptr<sdbusplus::asio::connection> sdbusp;
419std::shared_ptr<sdbusplus::asio::connection> getSdBus()
420{
421 return sdbusp;
422}
423
424#ifdef ALLOW_DEPRECATED_API
425/* legacy registration */
426void ipmi_register_callback(ipmi_netfn_t netFn, ipmi_cmd_t cmd,
427 ipmi_context_t context, ipmid_callback_t handler,
428 ipmi_cmd_privilege_t priv)
429{
430 auto h = ipmi::makeLegacyHandler(handler);
431 // translate priv from deprecated enum to current
432 ipmi::Privilege realPriv;
433 switch (priv)
434 {
435 case PRIVILEGE_CALLBACK:
436 realPriv = ipmi::Privilege::Callback;
437 break;
438 case PRIVILEGE_USER:
439 realPriv = ipmi::Privilege::User;
440 break;
441 case PRIVILEGE_OPERATOR:
442 realPriv = ipmi::Privilege::Operator;
443 break;
444 case PRIVILEGE_ADMIN:
445 realPriv = ipmi::Privilege::Admin;
446 break;
447 case PRIVILEGE_OEM:
448 realPriv = ipmi::Privilege::Oem;
449 break;
450 case SYSTEM_INTERFACE:
451 realPriv = ipmi::Privilege::Admin;
452 break;
453 default:
454 realPriv = ipmi::Privilege::Admin;
455 break;
456 }
457 ipmi::impl::registerHandler(ipmi::prioOpenBmcBase, netFn, cmd, realPriv, h);
458}
459
Vernon Maueryf984a012018-10-08 12:05:18 -0700460namespace oem
461{
462
463class LegacyRouter : public oem::Router
464{
465 public:
466 virtual ~LegacyRouter()
467 {
468 }
469
470 /// Enable message routing to begin.
471 void activate() override
472 {
473 }
474
475 void registerHandler(Number oen, ipmi_cmd_t cmd, Handler handler) override
476 {
477 auto h = ipmi::makeLegacyHandler(std::forward<Handler>(handler));
478 ipmi::impl::registerOemHandler(ipmi::prioOpenBmcBase, oen, cmd,
479 ipmi::Privilege::Admin, h);
480 }
481};
482static LegacyRouter legacyRouter;
483
484Router* mutableRouter()
485{
486 return &legacyRouter;
487}
488
489} // namespace oem
490
Vernon Mauery240b1862018-10-08 12:05:16 -0700491/* legacy alternative to executionEntry */
492void handleLegacyIpmiCommand(sdbusplus::message::message& m)
493{
494 unsigned char seq, netFn, lun, cmd;
495 std::vector<uint8_t> data;
496
497 m.read(seq, netFn, lun, cmd, data);
498
499 auto ctx = std::make_shared<ipmi::Context>(netFn, cmd, 0, 0,
500 ipmi::Privilege::Admin);
501 auto request = std::make_shared<ipmi::message::Request>(
502 ctx, std::forward<std::vector<uint8_t>>(data));
503 ipmi::message::Response::ptr response = ipmi::executeIpmiCommand(request);
504
505 // Responses in IPMI require a bit set. So there ya go...
506 netFn |= 0x01;
507
508 const char *dest, *path;
509 constexpr const char* DBUS_INTF = "org.openbmc.HostIpmi";
510
511 dest = m.get_sender();
512 path = m.get_path();
513 sdbusp->async_method_call([](boost::system::error_code ec) {}, dest, path,
514 DBUS_INTF, "sendMessage", seq, netFn, lun, cmd,
515 response->cc, response->payload.raw);
516}
517
518#endif /* ALLOW_DEPRECATED_API */
519
520// Calls host command manager to do the right thing for the command
521using CommandHandler = phosphor::host::command::CommandHandler;
522std::unique_ptr<phosphor::host::command::Manager> cmdManager;
523void ipmid_send_cmd_to_host(CommandHandler&& cmd)
524{
525 return cmdManager->execute(std::move(cmd));
526}
527
528std::unique_ptr<phosphor::host::command::Manager>& ipmid_get_host_cmd_manager()
529{
530 return cmdManager;
531}
532
533int main(int argc, char* argv[])
534{
535 // Connect to system bus
536 io = std::make_shared<boost::asio::io_service>();
537 if (argc > 1 && std::string(argv[1]) == "-session")
538 {
539 sd_bus_default_user(&bus);
540 }
541 else
542 {
543 sd_bus_default_system(&bus);
544 }
545 sdbusp = std::make_shared<sdbusplus::asio::connection>(*io, bus);
546 sdbusp->request_name("xyz.openbmc_project.Ipmi.Host");
547
548 // TODO: Hack to keep the sdEvents running.... Not sure why the sd_event
549 // queue stops running if we don't have a timer that keeps re-arming
550 phosphor::Timer t2([]() { ; });
551 t2.start(std::chrono::microseconds(500000), true);
552
553 // TODO: Remove all vestiges of sd_event from phosphor-host-ipmid
554 // until that is done, add the sd_event wrapper to the io object
555 sdbusplus::asio::sd_event_wrapper sdEvents(*io);
556
557 cmdManager = std::make_unique<phosphor::host::command::Manager>(*sdbusp);
558
559 // Register all command providers and filters
560 auto handles = ipmi::loadProviders(HOST_IPMI_LIB_PATH);
561
562 // Add bindings for inbound IPMI requests
563 auto server = sdbusplus::asio::object_server(sdbusp);
564 auto iface = server.add_interface("/xyz/openbmc_project/Ipmi",
565 "xyz.openbmc_project.Ipmi.Server");
566 iface->register_method("execute", ipmi::executionEntry);
567 iface->initialize();
568
569#ifdef ALLOW_DEPRECATED_API
570 // listen on deprecated signal interface for kcs/bt commands
571 constexpr const char* FILTER = "type='signal',interface='org.openbmc."
572 "HostIpmi',member='ReceivedMessage'";
573 sdbusplus::bus::match::match oldIpmiInterface(*sdbusp, FILTER,
574 handleLegacyIpmiCommand);
575#endif /* ALLOW_DEPRECATED_API */
576
577 io->run();
578
579 // This avoids a warning about unused variables
580 handles.clear();
581 return 0;
582}