blob: fb57666a61583c79700b667ab35bad116444eade [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
128namespace impl
129{
130/* common function to register all standard IPMI handlers */
131bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv,
132 HandlerBase::ptr handler)
133{
134 // check for valid NetFn: even; 00-0Ch, 30-3Eh
135 if (netFn & 1 || (netFn > netFnTransport && netFn < netFnGroup) ||
136 netFn > netFnOemEight)
137 {
138 return false;
139 }
140
141 // create key and value for this handler
142 unsigned int netFnCmd = makeCmdKey(netFn, cmd);
143 HandlerTuple item(prio, priv, handler);
144
145 // consult the handler map and look for a match
146 auto& mapCmd = handlerMap[netFnCmd];
147 if (!std::get<HandlerBase::ptr>(mapCmd) || std::get<int>(mapCmd) <= prio)
148 {
149 mapCmd = item;
150 return true;
151 }
152 return false;
153}
154
155} // namespace impl
156
157message::Response::ptr executeIpmiCommandCommon(
158 std::unordered_map<unsigned int, HandlerTuple>& handlers,
159 unsigned int keyCommon, message::Request::ptr request)
160{
161 Cmd cmd = request->ctx->cmd;
162 unsigned int key = makeCmdKey(keyCommon, cmd);
163 auto cmdIter = handlers.find(key);
164 if (cmdIter != handlers.end())
165 {
166 HandlerTuple& chosen = cmdIter->second;
167 if (request->ctx->priv < std::get<Privilege>(chosen))
168 {
169 return errorResponse(request, ccInsufficientPrivilege);
170 }
171 return std::get<HandlerBase::ptr>(chosen)->call(request);
172 }
173 else
174 {
175 unsigned int wildcard = makeCmdKey(keyCommon, cmdWildcard);
176 cmdIter = handlers.find(wildcard);
177 if (cmdIter != handlers.end())
178 {
179 HandlerTuple& chosen = cmdIter->second;
180 if (request->ctx->priv < std::get<Privilege>(chosen))
181 {
182 return errorResponse(request, ccInsufficientPrivilege);
183 }
184 return std::get<HandlerBase::ptr>(chosen)->call(request);
185 }
186 }
187 return errorResponse(request, ccInvalidCommand);
188}
189
190message::Response::ptr executeIpmiCommand(message::Request::ptr request)
191{
192 NetFn netFn = request->ctx->netFn;
193 // TODO: handler OEM and group OEM commands here
194 return executeIpmiCommandCommon(handlerMap, netFn, request);
195}
196
197/* called from sdbus async server context */
198auto executionEntry(boost::asio::yield_context yield, NetFn netFn, uint8_t lun,
199 Cmd cmd, std::vector<uint8_t>& data,
200 std::map<std::string, ipmi::Value>& options)
201{
202 auto ctx = std::make_shared<ipmi::Context>(netFn, cmd, 0, 0,
203 ipmi::Privilege::Admin, &yield);
204 auto request = std::make_shared<ipmi::message::Request>(
205 ctx, std::forward<std::vector<uint8_t>>(data));
206 message::Response::ptr response = executeIpmiCommand(request);
207
208 // Responses in IPMI require a bit set. So there ya go...
209 netFn |= 0x01;
210 return std::make_tuple(netFn, lun, cmd, response->cc,
211 response->payload.raw);
212}
213
214/** @struct IpmiProvider
215 *
216 * RAII wrapper for dlopen so that dlclose gets called on exit
217 */
218struct IpmiProvider
219{
220 public:
221 /** @brief address of the opened library */
222 void* addr;
223 std::string name;
224
225 IpmiProvider() = delete;
226 IpmiProvider(const IpmiProvider&) = delete;
227 IpmiProvider& operator=(const IpmiProvider&) = delete;
228 IpmiProvider(IpmiProvider&&) = delete;
229 IpmiProvider& operator=(IpmiProvider&&) = delete;
230
231 /** @brief dlopen a shared object file by path
232 * @param[in] filename - path of shared object to open
233 */
234 explicit IpmiProvider(const char* fname) : addr(nullptr), name(fname)
235 {
236 log<level::DEBUG>("Open IPMI provider library",
237 entry("PROVIDER=%s", name.c_str()));
238 try
239 {
240 addr = dlopen(name.c_str(), RTLD_NOW);
241 }
242 catch (std::exception& e)
243 {
244 log<level::ERR>("ERROR opening IPMI provider",
245 entry("PROVIDER=%s", name.c_str()),
246 entry("ERROR=%s", e.what()));
247 }
248 catch (...)
249 {
250 std::exception_ptr eptr = std::current_exception();
251 try
252 {
253 std::rethrow_exception(eptr);
254 }
255 catch (std::exception& e)
256 {
257 log<level::ERR>("ERROR opening IPMI provider",
258 entry("PROVIDER=%s", name.c_str()),
259 entry("ERROR=%s", e.what()));
260 }
261 }
262 if (!isOpen())
263 {
264 log<level::ERR>("ERROR opening IPMI provider",
265 entry("PROVIDER=%s", name.c_str()),
266 entry("ERROR=%s", dlerror()));
267 }
268 }
269
270 ~IpmiProvider()
271 {
272 if (isOpen())
273 {
274 dlclose(addr);
275 }
276 }
277 bool isOpen() const
278 {
279 return (nullptr != addr);
280 }
281};
282
283// Plugin libraries need to contain .so either at the end or in the middle
284constexpr const char ipmiPluginExtn[] = ".so";
285
286/* return a list of self-closing library handles */
287std::forward_list<IpmiProvider> loadProviders(const fs::path& ipmiLibsPath)
288{
289 std::vector<fs::path> libs;
290 for (const auto& libPath : fs::directory_iterator(ipmiLibsPath))
291 {
292 fs::path fname = libPath.path();
293 while (fname.has_extension())
294 {
295 fs::path extn = fname.extension();
296 if (extn == ipmiPluginExtn)
297 {
298 libs.push_back(libPath.path());
299 break;
300 }
301 fname.replace_extension();
302 }
303 }
304 std::sort(libs.begin(), libs.end());
305
306 std::forward_list<IpmiProvider> handles;
307 for (auto& lib : libs)
308 {
309#ifdef __IPMI_DEBUG__
310 log<level::DEBUG>("Registering handler",
311 entry("HANDLER=%s", lib.c_str()));
312#endif
313 handles.emplace_front(lib.c_str());
314 }
315 return handles;
316}
317
318} // namespace ipmi
319
320static std::shared_ptr<boost::asio::io_service> io;
321std::shared_ptr<boost::asio::io_service> getIoService()
322{
323 return io;
324}
325
326static std::shared_ptr<sdbusplus::asio::connection> sdbusp;
327std::shared_ptr<sdbusplus::asio::connection> getSdBus()
328{
329 return sdbusp;
330}
331
332#ifdef ALLOW_DEPRECATED_API
333/* legacy registration */
334void ipmi_register_callback(ipmi_netfn_t netFn, ipmi_cmd_t cmd,
335 ipmi_context_t context, ipmid_callback_t handler,
336 ipmi_cmd_privilege_t priv)
337{
338 auto h = ipmi::makeLegacyHandler(handler);
339 // translate priv from deprecated enum to current
340 ipmi::Privilege realPriv;
341 switch (priv)
342 {
343 case PRIVILEGE_CALLBACK:
344 realPriv = ipmi::Privilege::Callback;
345 break;
346 case PRIVILEGE_USER:
347 realPriv = ipmi::Privilege::User;
348 break;
349 case PRIVILEGE_OPERATOR:
350 realPriv = ipmi::Privilege::Operator;
351 break;
352 case PRIVILEGE_ADMIN:
353 realPriv = ipmi::Privilege::Admin;
354 break;
355 case PRIVILEGE_OEM:
356 realPriv = ipmi::Privilege::Oem;
357 break;
358 case SYSTEM_INTERFACE:
359 realPriv = ipmi::Privilege::Admin;
360 break;
361 default:
362 realPriv = ipmi::Privilege::Admin;
363 break;
364 }
365 ipmi::impl::registerHandler(ipmi::prioOpenBmcBase, netFn, cmd, realPriv, h);
366}
367
368/* legacy alternative to executionEntry */
369void handleLegacyIpmiCommand(sdbusplus::message::message& m)
370{
371 unsigned char seq, netFn, lun, cmd;
372 std::vector<uint8_t> data;
373
374 m.read(seq, netFn, lun, cmd, data);
375
376 auto ctx = std::make_shared<ipmi::Context>(netFn, cmd, 0, 0,
377 ipmi::Privilege::Admin);
378 auto request = std::make_shared<ipmi::message::Request>(
379 ctx, std::forward<std::vector<uint8_t>>(data));
380 ipmi::message::Response::ptr response = ipmi::executeIpmiCommand(request);
381
382 // Responses in IPMI require a bit set. So there ya go...
383 netFn |= 0x01;
384
385 const char *dest, *path;
386 constexpr const char* DBUS_INTF = "org.openbmc.HostIpmi";
387
388 dest = m.get_sender();
389 path = m.get_path();
390 sdbusp->async_method_call([](boost::system::error_code ec) {}, dest, path,
391 DBUS_INTF, "sendMessage", seq, netFn, lun, cmd,
392 response->cc, response->payload.raw);
393}
394
395#endif /* ALLOW_DEPRECATED_API */
396
397// Calls host command manager to do the right thing for the command
398using CommandHandler = phosphor::host::command::CommandHandler;
399std::unique_ptr<phosphor::host::command::Manager> cmdManager;
400void ipmid_send_cmd_to_host(CommandHandler&& cmd)
401{
402 return cmdManager->execute(std::move(cmd));
403}
404
405std::unique_ptr<phosphor::host::command::Manager>& ipmid_get_host_cmd_manager()
406{
407 return cmdManager;
408}
409
410int main(int argc, char* argv[])
411{
412 // Connect to system bus
413 io = std::make_shared<boost::asio::io_service>();
414 if (argc > 1 && std::string(argv[1]) == "-session")
415 {
416 sd_bus_default_user(&bus);
417 }
418 else
419 {
420 sd_bus_default_system(&bus);
421 }
422 sdbusp = std::make_shared<sdbusplus::asio::connection>(*io, bus);
423 sdbusp->request_name("xyz.openbmc_project.Ipmi.Host");
424
425 // TODO: Hack to keep the sdEvents running.... Not sure why the sd_event
426 // queue stops running if we don't have a timer that keeps re-arming
427 phosphor::Timer t2([]() { ; });
428 t2.start(std::chrono::microseconds(500000), true);
429
430 // TODO: Remove all vestiges of sd_event from phosphor-host-ipmid
431 // until that is done, add the sd_event wrapper to the io object
432 sdbusplus::asio::sd_event_wrapper sdEvents(*io);
433
434 cmdManager = std::make_unique<phosphor::host::command::Manager>(*sdbusp);
435
436 // Register all command providers and filters
437 auto handles = ipmi::loadProviders(HOST_IPMI_LIB_PATH);
438
439 // Add bindings for inbound IPMI requests
440 auto server = sdbusplus::asio::object_server(sdbusp);
441 auto iface = server.add_interface("/xyz/openbmc_project/Ipmi",
442 "xyz.openbmc_project.Ipmi.Server");
443 iface->register_method("execute", ipmi::executionEntry);
444 iface->initialize();
445
446#ifdef ALLOW_DEPRECATED_API
447 // listen on deprecated signal interface for kcs/bt commands
448 constexpr const char* FILTER = "type='signal',interface='org.openbmc."
449 "HostIpmi',member='ReceivedMessage'";
450 sdbusplus::bus::match::match oldIpmiInterface(*sdbusp, FILTER,
451 handleLegacyIpmiCommand);
452#endif /* ALLOW_DEPRECATED_API */
453
454 io->run();
455
456 // This avoids a warning about unused variables
457 handles.clear();
458 return 0;
459}