summaryrefslogtreecommitdiffstats
path: root/ipmid-new.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ipmid-new.cpp')
-rw-r--r--ipmid-new.cpp370
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);
}
OpenPOWER on IntegriCloud