diff options
Diffstat (limited to 'ipmid-new.cpp')
-rw-r--r-- | ipmid-new.cpp | 370 |
1 files changed, 308 insertions, 62 deletions
diff --git a/ipmid-new.cpp b/ipmid-new.cpp index 2a2d4a6..abd0a10 100644 --- a/ipmid-new.cpp +++ b/ipmid-new.cpp @@ -21,6 +21,7 @@ #include <algorithm> #include <any> +#include <boost/algorithm/string.hpp> #include <dcmihandler.hpp> #include <exception> #include <filesystem> @@ -232,17 +233,18 @@ message::Response::ptr executeIpmiCommandCommon( { // filter the command first; a non-null message::Response::ptr // means that the message has been rejected for some reason - message::Response::ptr response = filterIpmiCommand(request); - if (response) - { - return response; - } + message::Response::ptr filterResponse = filterIpmiCommand(request); Cmd cmd = request->ctx->cmd; unsigned int key = makeCmdKey(keyCommon, cmd); auto cmdIter = handlers.find(key); if (cmdIter != handlers.end()) { + // only return the filter response if the command is found + if (filterResponse) + { + return filterResponse; + } HandlerTuple& chosen = cmdIter->second; if (request->ctx->priv < std::get<Privilege>(chosen)) { @@ -256,6 +258,11 @@ message::Response::ptr executeIpmiCommandCommon( cmdIter = handlers.find(wildcard); if (cmdIter != handlers.end()) { + // only return the filter response if the command is found + if (filterResponse) + { + return filterResponse; + } HandlerTuple& chosen = cmdIter->second; if (request->ctx->priv < std::get<Privilege>(chosen)) { @@ -270,39 +277,34 @@ message::Response::ptr executeIpmiCommandCommon( message::Response::ptr executeIpmiGroupCommand(message::Request::ptr request) { // look up the group for this request - Group group; - if (0 != request->payload.unpack(group)) + uint8_t bytes; + if (0 != request->payload.unpack(bytes)) { return errorResponse(request, ccReqDataLenInvalid); } - // The handler will need to unpack group as well; we just need it for lookup - request->payload.reset(); + auto group = static_cast<Group>(bytes); message::Response::ptr response = executeIpmiCommandCommon(groupHandlerMap, group, request); - // if the handler should add the group; executeIpmiCommandCommon does not - if (response->cc != ccSuccess && response->payload.size() == 0) - { - response->pack(group); - } + ipmi::message::Payload prefix; + prefix.pack(bytes); + response->prepend(prefix); return response; } message::Response::ptr executeIpmiOemCommand(message::Request::ptr request) { // look up the iana for this request - Iana iana; - if (0 != request->payload.unpack(iana)) + uint24_t bytes; + if (0 != request->payload.unpack(bytes)) { return errorResponse(request, ccReqDataLenInvalid); } - request->payload.reset(); + auto iana = static_cast<Iana>(bytes); message::Response::ptr response = executeIpmiCommandCommon(oemHandlerMap, iana, request); - // if the handler should add the iana; executeIpmiCommandCommon does not - if (response->cc != ccSuccess && response->payload.size() == 0) - { - response->pack(iana); - } + ipmi::message::Payload prefix; + prefix.pack(bytes); + response->prepend(prefix); return response; } @@ -320,21 +322,240 @@ message::Response::ptr executeIpmiCommand(message::Request::ptr request) return executeIpmiCommandCommon(handlerMap, netFn, request); } +namespace utils +{ +template <typename AssocContainer, typename UnaryPredicate> +void assoc_erase_if(AssocContainer& c, UnaryPredicate p) +{ + typename AssocContainer::iterator next = c.begin(); + typename AssocContainer::iterator last = c.end(); + while ((next = std::find_if(next, last, p)) != last) + { + c.erase(next++); + } +} +} // namespace utils + +namespace +{ +std::unordered_map<std::string, uint8_t> uniqueNameToChannelNumber; + +// sdbusplus::bus::match::rules::arg0namespace() wants the prefix +// to match without any trailing '.' +constexpr const char ipmiDbusChannelMatch[] = + "xyz.openbmc_project.Ipmi.Channel"; +void updateOwners(sdbusplus::asio::connection& conn, const std::string& name) +{ + conn.async_method_call( + [name](const boost::system::error_code ec, + const std::string& nameOwner) { + if (ec) + { + log<level::ERR>("Error getting dbus owner", + entry("INTERFACE=%s", name.c_str())); + return; + } + // start after ipmiDbusChannelPrefix (after the '.') + std::string chName = + name.substr(std::strlen(ipmiDbusChannelMatch) + 1); + try + { + uint8_t channel = getChannelByName(chName); + uniqueNameToChannelNumber[nameOwner] = channel; + log<level::INFO>("New interface mapping", + entry("INTERFACE=%s", name.c_str()), + entry("CHANNEL=%u", channel)); + } + catch (const std::exception& e) + { + log<level::INFO>("Failed interface mapping, no such name", + entry("INTERFACE=%s", name.c_str())); + } + }, + "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetNameOwner", + name); +} + +void doListNames(boost::asio::io_service& io, sdbusplus::asio::connection& conn) +{ + conn.async_method_call( + [&io, &conn](const boost::system::error_code ec, + std::vector<std::string> busNames) { + if (ec) + { + log<level::ERR>("Error getting dbus names"); + std::exit(EXIT_FAILURE); + return; + } + // Try to make startup consistent + std::sort(busNames.begin(), busNames.end()); + + const std::string channelPrefix = + std::string(ipmiDbusChannelMatch) + "."; + for (const std::string& busName : busNames) + { + if (busName.find(channelPrefix) == 0) + { + updateOwners(conn, busName); + } + } + }, + "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", + "ListNames"); +} + +void nameChangeHandler(sdbusplus::message::message& message) +{ + std::string name; + std::string oldOwner; + std::string newOwner; + + message.read(name, oldOwner, newOwner); + + if (!oldOwner.empty()) + { + if (boost::starts_with(oldOwner, ":")) + { + // Connection removed + auto it = uniqueNameToChannelNumber.find(oldOwner); + if (it != uniqueNameToChannelNumber.end()) + { + uniqueNameToChannelNumber.erase(it); + } + } + } + if (!newOwner.empty()) + { + // start after ipmiDbusChannelMatch (and after the '.') + std::string chName = name.substr(std::strlen(ipmiDbusChannelMatch) + 1); + try + { + uint8_t channel = getChannelByName(chName); + uniqueNameToChannelNumber[newOwner] = channel; + log<level::INFO>("New interface mapping", + entry("INTERFACE=%s", name.c_str()), + entry("CHANNEL=%u", channel)); + } + catch (const std::exception& e) + { + log<level::INFO>("Failed interface mapping, no such name", + entry("INTERFACE=%s", name.c_str())); + } + } +}; + +} // anonymous namespace + +static constexpr const char intraBmcName[] = "INTRABMC"; +uint8_t channelFromMessage(sdbusplus::message::message& msg) +{ + // channel name for ipmitool to resolve to + std::string sender = msg.get_sender(); + auto chIter = uniqueNameToChannelNumber.find(sender); + if (chIter != uniqueNameToChannelNumber.end()) + { + return chIter->second; + } + // FIXME: currently internal connections are ephemeral and hard to pin down + try + { + return getChannelByName(intraBmcName); + } + catch (const std::exception& e) + { + return invalidChannel; + } +} // namespace ipmi + /* called from sdbus async server context */ -auto executionEntry(boost::asio::yield_context yield, NetFn netFn, uint8_t lun, +auto executionEntry(boost::asio::yield_context yield, + sdbusplus::message::message& m, 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); + const auto dbusResponse = + [netFn, lun, cmd](Cc cc, const std::vector<uint8_t>& data = {}) { + constexpr uint8_t netFnResponse = 0x01; + uint8_t retNetFn = netFn | netFnResponse; + return std::make_tuple(retNetFn, lun, cmd, cc, data); + }; + std::string sender = m.get_sender(); + Privilege privilege = Privilege::None; + int rqSA = 0; + uint8_t userId = 0; // undefined user + uint32_t sessionId = 0; + + // figure out what channel the request came in on + uint8_t channel = channelFromMessage(m); + if (channel == invalidChannel) + { + // unknown sender channel; refuse to service the request + log<level::ERR>("ERROR determining source IPMI channel", + entry("SENDER=%s", sender.c_str()), + entry("NETFN=0x%X", netFn), entry("CMD=0x%X", cmd)); + return dbusResponse(ipmi::ccDestinationUnavailable); + } + + // session-based channels are required to provide userId, privilege and + // sessionId + if (getChannelSessionSupport(channel) != EChannelSessSupported::none) + { + try + { + Value requestPriv = options.at("privilege"); + Value requestUserId = options.at("userId"); + Value requestSessionId = options.at("currentSessionId"); + privilege = static_cast<Privilege>(std::get<int>(requestPriv)); + userId = static_cast<uint8_t>(std::get<int>(requestUserId)); + sessionId = + static_cast<uint32_t>(std::get<uint32_t>(requestSessionId)); + } + catch (const std::exception& e) + { + log<level::ERR>("ERROR determining IPMI session credentials", + entry("CHANNEL=%u", channel), + entry("NETFN=0x%X", netFn), entry("CMD=0x%X", cmd)); + return dbusResponse(ipmi::ccUnspecifiedError); + } + } + else + { + // get max privilege for session-less channels + // For now, there is not a way to configure this, default to Admin + privilege = Privilege::Admin; + + // ipmb should supply rqSA + ChannelInfo chInfo; + getChannelInfo(channel, chInfo); + if (static_cast<EChannelMediumType>(chInfo.mediumType) == + EChannelMediumType::ipmb) + { + const auto iter = options.find("rqSA"); + if (iter != options.end()) + { + if (std::holds_alternative<int>(iter->second)) + { + rqSA = std::get<int>(iter->second); + } + } + } + } + // check to see if the requested priv/username is valid + log<level::DEBUG>("Set up ipmi context", entry("SENDER=%s", sender.c_str()), + entry("NETFN=0x%X", netFn), entry("CMD=0x%X", cmd), + entry("CHANNEL=%u", channel), entry("USERID=%u", userId), + entry("SESSIONID=0x%X", sessionId), + entry("PRIVILEGE=%u", static_cast<uint8_t>(privilege)), + entry("RQSA=%x", rqSA)); + + auto ctx = + std::make_shared<ipmi::Context>(getSdBus(), netFn, cmd, channel, userId, + sessionId, privilege, rqSA, 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); + return dbusResponse(response->cc, response->payload.raw); } /** @struct IpmiProvider @@ -531,29 +752,43 @@ Router* mutableRouter() /* 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(); - getSdBus()->async_method_call([](boost::system::error_code ec) {}, dest, - path, DBUS_INTF, "sendMessage", seq, netFn, - lun, cmd, response->cc, - response->payload.raw); + // make a copy so the next two moves don't wreak havoc on the stack + sdbusplus::message::message b{m}; + boost::asio::spawn(*getIoContext(), [b = std::move(b)]( + boost::asio::yield_context yield) { + sdbusplus::message::message m{std::move(b)}; + unsigned char seq, netFn, lun, cmd; + std::vector<uint8_t> data; + + m.read(seq, netFn, lun, cmd, data); + std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus(); + auto ctx = std::make_shared<ipmi::Context>( + bus, netFn, cmd, 0, 0, 0, ipmi::Privilege::Admin, 0, yield); + 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(); + boost::system::error_code ec; + bus->yield_method_call(yield, ec, dest, path, DBUS_INTF, "sendMessage", + seq, netFn, lun, cmd, response->cc, + response->payload.raw); + if (ec) + { + log<level::ERR>("Failed to send response to requestor", + entry("ERROR=%s", ec.message().c_str()), + entry("SENDER=%s", dest), + entry("NETFN=0x%X", netFn), entry("CMD=0x%X", cmd)); + } + }); } #endif /* ALLOW_DEPRECATED_API */ @@ -591,7 +826,6 @@ int main(int argc, char* argv[]) } auto sdbusp = std::make_shared<sdbusplus::asio::connection>(*io, bus); setSdBus(sdbusp); - 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 @@ -608,13 +842,6 @@ int main(int argc, char* argv[]) std::forward_list<ipmi::IpmiProvider> providers = 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." @@ -623,17 +850,36 @@ int main(int argc, char* argv[]) handleLegacyIpmiCommand); #endif /* ALLOW_DEPRECATED_API */ + // set up bus name watching to match channels with bus names + sdbusplus::bus::match::match nameOwnerChanged( + *sdbusp, + sdbusplus::bus::match::rules::nameOwnerChanged() + + sdbusplus::bus::match::rules::arg0namespace( + ipmi::ipmiDbusChannelMatch), + ipmi::nameChangeHandler); + ipmi::doListNames(*io, *sdbusp); + + int exitCode = 0; // set up boost::asio signal handling std::function<SignalResponse(int)> stopAsioRunLoop = - [&io](int signalNumber) { + [&io, &exitCode](int signalNumber) { log<level::INFO>("Received signal; quitting", entry("SIGNAL=%d", signalNumber)); io->stop(); + exitCode = signalNumber; return SignalResponse::breakExecution; }; registerSignalHandler(ipmi::prioOpenBmcBase, SIGINT, stopAsioRunLoop); registerSignalHandler(ipmi::prioOpenBmcBase, SIGTERM, stopAsioRunLoop); + sdbusp->request_name("xyz.openbmc_project.Ipmi.Host"); + // 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(); + io->run(); // destroy all the IPMI handlers so the providers can unload safely @@ -644,5 +890,5 @@ int main(int argc, char* argv[]) // unload the provider libraries providers.clear(); - return 0; + std::exit(exitCode); } |