diff options
Diffstat (limited to 'ipmid-new.cpp')
-rw-r--r-- | ipmid-new.cpp | 459 |
1 files changed, 459 insertions, 0 deletions
diff --git a/ipmid-new.cpp b/ipmid-new.cpp new file mode 100644 index 0000000..fb57666 --- /dev/null +++ b/ipmid-new.cpp @@ -0,0 +1,459 @@ +/** + * Copyright © 2018 Intel Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" + +#include "settings.hpp" + +#include <dlfcn.h> + +#include <algorithm> +#include <any> +#include <exception> +#include <forward_list> +#include <host-cmd-manager.hpp> +#include <ipmid-host/cmd.hpp> +#include <ipmid/api.hpp> +#include <ipmid/handler.hpp> +#include <ipmid/message.hpp> +#include <ipmid/oemrouter.hpp> +#include <ipmid/registration.hpp> +#include <map> +#include <memory> +#include <optional> +#include <phosphor-logging/log.hpp> +#include <sdbusplus/asio/connection.hpp> +#include <sdbusplus/asio/object_server.hpp> +#include <sdbusplus/asio/sd_event.hpp> +#include <sdbusplus/bus.hpp> +#include <sdbusplus/bus/match.hpp> +#include <sdbusplus/timer.hpp> +#include <tuple> +#include <types.hpp> +#include <unordered_map> +#include <utility> +#include <vector> + +#if __has_include(<filesystem>) +#include <filesystem> +#elif __has_include(<experimental/filesystem>) +#include <experimental/filesystem> +namespace std +{ +// splice experimental::filesystem into std +namespace filesystem = std::experimental::filesystem; +} // namespace std +#else +#error filesystem not available +#endif + +namespace fs = std::filesystem; + +using namespace phosphor::logging; + +// Global timer for network changes +std::unique_ptr<phosphor::Timer> networkTimer = nullptr; + +// IPMI Spec, shared Reservation ID. +static unsigned short selReservationID = 0xFFFF; +static bool selReservationValid = false; + +unsigned short reserveSel(void) +{ + // IPMI spec, Reservation ID, the value simply increases against each + // execution of the Reserve SEL command. + if (++selReservationID == 0) + { + selReservationID = 1; + } + selReservationValid = true; + return selReservationID; +} + +bool checkSELReservation(unsigned short id) +{ + return (selReservationValid && selReservationID == id); +} + +void cancelSELReservation(void) +{ + selReservationValid = false; +} + +EInterfaceIndex getInterfaceIndex(void) +{ + return interfaceKCS; +} + +sd_bus* bus; +sd_event* events = nullptr; +sd_event* ipmid_get_sd_event_connection(void) +{ + return events; +} +sd_bus* ipmid_get_sd_bus_connection(void) +{ + return bus; +} + +namespace ipmi +{ + +static inline unsigned int makeCmdKey(unsigned int cluster, unsigned int cmd) +{ + return (cluster << 8) | cmd; +} + +using HandlerTuple = std::tuple<int, /* prio */ + Privilege, HandlerBase::ptr /* handler */ + >; + +/* map to handle standard registered commands */ +static std::unordered_map<unsigned int, /* key is NetFn/Cmd */ + HandlerTuple> + handlerMap; + +namespace impl +{ +/* common function to register all standard IPMI handlers */ +bool registerHandler(int prio, NetFn netFn, Cmd cmd, Privilege priv, + HandlerBase::ptr handler) +{ + // check for valid NetFn: even; 00-0Ch, 30-3Eh + if (netFn & 1 || (netFn > netFnTransport && netFn < netFnGroup) || + netFn > netFnOemEight) + { + return false; + } + + // create key and value for this handler + unsigned int netFnCmd = makeCmdKey(netFn, cmd); + HandlerTuple item(prio, priv, handler); + + // consult the handler map and look for a match + auto& mapCmd = handlerMap[netFnCmd]; + if (!std::get<HandlerBase::ptr>(mapCmd) || std::get<int>(mapCmd) <= prio) + { + mapCmd = item; + return true; + } + return false; +} + +} // namespace impl + +message::Response::ptr executeIpmiCommandCommon( + std::unordered_map<unsigned int, HandlerTuple>& handlers, + unsigned int keyCommon, message::Request::ptr request) +{ + Cmd cmd = request->ctx->cmd; + unsigned int key = makeCmdKey(keyCommon, cmd); + auto cmdIter = handlers.find(key); + if (cmdIter != handlers.end()) + { + HandlerTuple& chosen = cmdIter->second; + if (request->ctx->priv < std::get<Privilege>(chosen)) + { + return errorResponse(request, ccInsufficientPrivilege); + } + return std::get<HandlerBase::ptr>(chosen)->call(request); + } + else + { + unsigned int wildcard = makeCmdKey(keyCommon, cmdWildcard); + cmdIter = handlers.find(wildcard); + if (cmdIter != handlers.end()) + { + HandlerTuple& chosen = cmdIter->second; + if (request->ctx->priv < std::get<Privilege>(chosen)) + { + return errorResponse(request, ccInsufficientPrivilege); + } + return std::get<HandlerBase::ptr>(chosen)->call(request); + } + } + return errorResponse(request, ccInvalidCommand); +} + +message::Response::ptr executeIpmiCommand(message::Request::ptr request) +{ + NetFn netFn = request->ctx->netFn; + // TODO: handler OEM and group OEM commands here + return executeIpmiCommandCommon(handlerMap, netFn, request); +} + +/* called from sdbus async server context */ +auto executionEntry(boost::asio::yield_context yield, NetFn netFn, uint8_t lun, + Cmd cmd, std::vector<uint8_t>& data, + std::map<std::string, ipmi::Value>& options) +{ + auto ctx = std::make_shared<ipmi::Context>(netFn, cmd, 0, 0, + ipmi::Privilege::Admin, &yield); + auto request = std::make_shared<ipmi::message::Request>( + ctx, std::forward<std::vector<uint8_t>>(data)); + message::Response::ptr response = executeIpmiCommand(request); + + // Responses in IPMI require a bit set. So there ya go... + netFn |= 0x01; + return std::make_tuple(netFn, lun, cmd, response->cc, + response->payload.raw); +} + +/** @struct IpmiProvider + * + * RAII wrapper for dlopen so that dlclose gets called on exit + */ +struct IpmiProvider +{ + public: + /** @brief address of the opened library */ + void* addr; + std::string name; + + IpmiProvider() = delete; + IpmiProvider(const IpmiProvider&) = delete; + IpmiProvider& operator=(const IpmiProvider&) = delete; + IpmiProvider(IpmiProvider&&) = delete; + IpmiProvider& operator=(IpmiProvider&&) = delete; + + /** @brief dlopen a shared object file by path + * @param[in] filename - path of shared object to open + */ + explicit IpmiProvider(const char* fname) : addr(nullptr), name(fname) + { + log<level::DEBUG>("Open IPMI provider library", + entry("PROVIDER=%s", name.c_str())); + try + { + addr = dlopen(name.c_str(), RTLD_NOW); + } + catch (std::exception& e) + { + log<level::ERR>("ERROR opening IPMI provider", + entry("PROVIDER=%s", name.c_str()), + entry("ERROR=%s", e.what())); + } + catch (...) + { + std::exception_ptr eptr = std::current_exception(); + try + { + std::rethrow_exception(eptr); + } + catch (std::exception& e) + { + log<level::ERR>("ERROR opening IPMI provider", + entry("PROVIDER=%s", name.c_str()), + entry("ERROR=%s", e.what())); + } + } + if (!isOpen()) + { + log<level::ERR>("ERROR opening IPMI provider", + entry("PROVIDER=%s", name.c_str()), + entry("ERROR=%s", dlerror())); + } + } + + ~IpmiProvider() + { + if (isOpen()) + { + dlclose(addr); + } + } + bool isOpen() const + { + return (nullptr != addr); + } +}; + +// Plugin libraries need to contain .so either at the end or in the middle +constexpr const char ipmiPluginExtn[] = ".so"; + +/* return a list of self-closing library handles */ +std::forward_list<IpmiProvider> loadProviders(const fs::path& ipmiLibsPath) +{ + std::vector<fs::path> libs; + for (const auto& libPath : fs::directory_iterator(ipmiLibsPath)) + { + fs::path fname = libPath.path(); + while (fname.has_extension()) + { + fs::path extn = fname.extension(); + if (extn == ipmiPluginExtn) + { + libs.push_back(libPath.path()); + break; + } + fname.replace_extension(); + } + } + std::sort(libs.begin(), libs.end()); + + std::forward_list<IpmiProvider> handles; + for (auto& lib : libs) + { +#ifdef __IPMI_DEBUG__ + log<level::DEBUG>("Registering handler", + entry("HANDLER=%s", lib.c_str())); +#endif + handles.emplace_front(lib.c_str()); + } + return handles; +} + +} // namespace ipmi + +static std::shared_ptr<boost::asio::io_service> io; +std::shared_ptr<boost::asio::io_service> getIoService() +{ + return io; +} + +static std::shared_ptr<sdbusplus::asio::connection> sdbusp; +std::shared_ptr<sdbusplus::asio::connection> getSdBus() +{ + return sdbusp; +} + +#ifdef ALLOW_DEPRECATED_API +/* legacy registration */ +void ipmi_register_callback(ipmi_netfn_t netFn, ipmi_cmd_t cmd, + ipmi_context_t context, ipmid_callback_t handler, + ipmi_cmd_privilege_t priv) +{ + auto h = ipmi::makeLegacyHandler(handler); + // translate priv from deprecated enum to current + ipmi::Privilege realPriv; + switch (priv) + { + case PRIVILEGE_CALLBACK: + realPriv = ipmi::Privilege::Callback; + break; + case PRIVILEGE_USER: + realPriv = ipmi::Privilege::User; + break; + case PRIVILEGE_OPERATOR: + realPriv = ipmi::Privilege::Operator; + break; + case PRIVILEGE_ADMIN: + realPriv = ipmi::Privilege::Admin; + break; + case PRIVILEGE_OEM: + realPriv = ipmi::Privilege::Oem; + break; + case SYSTEM_INTERFACE: + realPriv = ipmi::Privilege::Admin; + break; + default: + realPriv = ipmi::Privilege::Admin; + break; + } + ipmi::impl::registerHandler(ipmi::prioOpenBmcBase, netFn, cmd, realPriv, h); +} + +/* legacy alternative to executionEntry */ +void handleLegacyIpmiCommand(sdbusplus::message::message& m) +{ + unsigned char seq, netFn, lun, cmd; + std::vector<uint8_t> data; + + m.read(seq, netFn, lun, cmd, data); + + auto ctx = std::make_shared<ipmi::Context>(netFn, cmd, 0, 0, + ipmi::Privilege::Admin); + auto request = std::make_shared<ipmi::message::Request>( + ctx, std::forward<std::vector<uint8_t>>(data)); + ipmi::message::Response::ptr response = ipmi::executeIpmiCommand(request); + + // Responses in IPMI require a bit set. So there ya go... + netFn |= 0x01; + + const char *dest, *path; + constexpr const char* DBUS_INTF = "org.openbmc.HostIpmi"; + + dest = m.get_sender(); + path = m.get_path(); + sdbusp->async_method_call([](boost::system::error_code ec) {}, dest, path, + DBUS_INTF, "sendMessage", seq, netFn, lun, cmd, + response->cc, response->payload.raw); +} + +#endif /* ALLOW_DEPRECATED_API */ + +// Calls host command manager to do the right thing for the command +using CommandHandler = phosphor::host::command::CommandHandler; +std::unique_ptr<phosphor::host::command::Manager> cmdManager; +void ipmid_send_cmd_to_host(CommandHandler&& cmd) +{ + return cmdManager->execute(std::move(cmd)); +} + +std::unique_ptr<phosphor::host::command::Manager>& ipmid_get_host_cmd_manager() +{ + return cmdManager; +} + +int main(int argc, char* argv[]) +{ + // Connect to system bus + io = std::make_shared<boost::asio::io_service>(); + if (argc > 1 && std::string(argv[1]) == "-session") + { + sd_bus_default_user(&bus); + } + else + { + sd_bus_default_system(&bus); + } + sdbusp = std::make_shared<sdbusplus::asio::connection>(*io, bus); + sdbusp->request_name("xyz.openbmc_project.Ipmi.Host"); + + // TODO: Hack to keep the sdEvents running.... Not sure why the sd_event + // queue stops running if we don't have a timer that keeps re-arming + phosphor::Timer t2([]() { ; }); + t2.start(std::chrono::microseconds(500000), true); + + // TODO: Remove all vestiges of sd_event from phosphor-host-ipmid + // until that is done, add the sd_event wrapper to the io object + sdbusplus::asio::sd_event_wrapper sdEvents(*io); + + cmdManager = std::make_unique<phosphor::host::command::Manager>(*sdbusp); + + // Register all command providers and filters + auto handles = ipmi::loadProviders(HOST_IPMI_LIB_PATH); + + // Add bindings for inbound IPMI requests + auto server = sdbusplus::asio::object_server(sdbusp); + auto iface = server.add_interface("/xyz/openbmc_project/Ipmi", + "xyz.openbmc_project.Ipmi.Server"); + iface->register_method("execute", ipmi::executionEntry); + iface->initialize(); + +#ifdef ALLOW_DEPRECATED_API + // listen on deprecated signal interface for kcs/bt commands + constexpr const char* FILTER = "type='signal',interface='org.openbmc." + "HostIpmi',member='ReceivedMessage'"; + sdbusplus::bus::match::match oldIpmiInterface(*sdbusp, FILTER, + handleLegacyIpmiCommand); +#endif /* ALLOW_DEPRECATED_API */ + + io->run(); + + // This avoids a warning about unused variables + handles.clear(); + return 0; +} |