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