blob: 238c0e5b8f814bfcdd1bf5056c4c86e86fa969ce [file] [log] [blame]
#include "ipmid.hpp"
#include "host-cmd-manager.hpp"
#include "ipmiwhitelist.hpp"
#include "sensorhandler.hpp"
#include "settings.hpp"
#include <assert.h>
#include <dirent.h>
#include <dlfcn.h>
#include <errno.h>
#include <mapper.h>
#include <sys/time.h>
#include <systemd/sd-bus.h>
#include <unistd.h>
#include <algorithm>
#include <cstring>
#include <host-ipmid/ipmid-host-cmd.hpp>
#include <host-ipmid/oemrouter.hpp>
#include <iostream>
#include <iterator>
#include <map>
#include <memory>
#include <phosphor-logging/log.hpp>
#include <sdbusplus/bus.hpp>
#include <sdbusplus/bus/match.hpp>
#include <sdbusplus/message/types.hpp>
#include <sdbusplus/timer.hpp>
#include <vector>
#include <xyz/openbmc_project/Control/Security/RestrictionMode/server.hpp>
using namespace phosphor::logging;
namespace sdbusRule = sdbusplus::bus::match::rules;
namespace variant_ns = sdbusplus::message::variant_ns;
sd_bus* bus = NULL;
sd_bus_slot* ipmid_slot = NULL;
sd_event* events = nullptr;
// Need this to use new sdbusplus compatible interfaces
sdbusPtr sdbusp;
// Global Host Bound Command manager
using cmdManagerPtr = std::unique_ptr<phosphor::host::command::Manager>;
cmdManagerPtr cmdManager;
// Global timer for network changes
std::unique_ptr<phosphor::Timer> networkTimer = nullptr;
// Command and handler tuple. Used when clients ask the command to be put
// into host message queue
using CommandHandler = phosphor::host::command::CommandHandler;
// Initialise restricted mode to true
bool restricted_mode = true;
FILE *ipmiio, *ipmidbus, *ipmicmddetails;
void print_usage(void)
{
std::fprintf(stderr, "Options: [-d mask]\n");
std::fprintf(stderr, " mask : 0x01 - Print ipmi packets\n");
std::fprintf(stderr, " mask : 0x02 - Print DBUS operations\n");
std::fprintf(stderr, " mask : 0x04 - Print ipmi command details\n");
std::fprintf(stderr, " mask : 0xFF - Print all trace\n");
}
const char* DBUS_INTF = "org.openbmc.HostIpmi";
const char* FILTER =
"type='signal',interface='org.openbmc.HostIpmi',member='ReceivedMessage'";
typedef std::pair<ipmi_netfn_t, ipmi_cmd_t> ipmi_fn_cmd_t;
typedef std::pair<ipmid_callback_t, ipmi_context_t> ipmi_fn_context_t;
// Global data structure that contains the IPMI command handler's registrations.
std::map<ipmi_fn_cmd_t, ipmi_fn_context_t> g_ipmid_router_map;
// 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;
}
namespace internal
{
constexpr auto restrictionModeIntf =
"xyz.openbmc_project.Control.Security.RestrictionMode";
namespace cache
{
std::unique_ptr<settings::Objects> objects = nullptr;
} // namespace cache
} // namespace internal
#ifndef HEXDUMP_COLS
#define HEXDUMP_COLS 16
#endif
void hexdump(FILE* s, void* mem, size_t len)
{
unsigned int i, j;
for (i = 0;
i <
len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0);
i++)
{
/* print offset */
if (i % HEXDUMP_COLS == 0)
{
std::fprintf(s, "0x%06x: ", i);
}
/* print hex data */
if (i < len)
{
std::fprintf(s, "%02x ", 0xFF & ((char*)mem)[i]);
}
else /* end of block, just aligning for ASCII dump */
{
std::fprintf(s, " ");
}
/* print ASCII dump */
if (i % HEXDUMP_COLS == (HEXDUMP_COLS - 1))
{
for (j = i - (HEXDUMP_COLS - 1); j <= i; j++)
{
if (j >= len) /* end of block, not really printing */
{
std::fputc(' ', s);
}
else if (std::isprint(((char*)mem)[j])) /* printable char */
{
std::fputc(0xFF & ((char*)mem)[j], s);
}
else /* other char */
{
std::fputc('.', s);
}
}
std::fputc('\n', s);
}
}
}
// Method that gets called by shared libraries to get their command handlers
// registered
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)
{
// Pack NetFn and Command in one.
auto netfn_and_cmd = std::make_pair(netfn, cmd);
// Pack Function handler and Data in another.
auto handler_and_context = std::make_pair(handler, context);
// Check if the registration has already been made..
auto iter = g_ipmid_router_map.find(netfn_and_cmd);
if (iter != g_ipmid_router_map.end())
{
log<level::ERR>("Duplicate registration", entry("NETFN=0x%X", netfn),
entry("CMD=0x%X", cmd));
}
else
{
// This is a fresh registration.. Add it to the map.
g_ipmid_router_map.emplace(netfn_and_cmd, handler_and_context);
}
return;
}
// Looks at the map and calls corresponding handler functions.
ipmi_ret_t ipmi_netfn_router(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
ipmi_request_t request, ipmi_response_t response,
ipmi_data_len_t data_len)
{
// return from the Command handlers.
ipmi_ret_t rc = IPMI_CC_INVALID;
// If restricted mode is true and command is not whitelisted, don't
// execute the command
if (restricted_mode)
{
if (!std::binary_search(whitelist.cbegin(), whitelist.cend(),
std::make_pair(netfn, cmd)))
{
log<level::ERR>("Net function not whitelisted",
entry("NETFN=0x%X", netfn), entry("CMD=0x%X", cmd));
rc = IPMI_CC_INSUFFICIENT_PRIVILEGE;
std::memcpy(response, &rc, IPMI_CC_LEN);
*data_len = IPMI_CC_LEN;
return rc;
}
}
// Walk the map that has the registered handlers and invoke the approprite
// handlers for matching commands.
auto iter = g_ipmid_router_map.find(std::make_pair(netfn, cmd));
if (iter == g_ipmid_router_map.end())
{
/* By default should only print on failure to find wildcard command. */
#ifdef __IPMI_DEBUG__
log<level::ERR>(
"No registered handlers for NetFn, trying Wilcard implementation",
entry("NET_FUN=0x%X", netfn) entry("CMD=0x%X", IPMI_CMD_WILDCARD));
#endif
// Now that we did not find any specific [NetFn,Cmd], tuple, check for
// NetFn, WildCard command present.
iter =
g_ipmid_router_map.find(std::make_pair(netfn, IPMI_CMD_WILDCARD));
if (iter == g_ipmid_router_map.end())
{
log<level::ERR>("No Registered handlers for NetFn",
entry("NET_FUN=0x%X", netfn),
entry("CMD=0x%X", IPMI_CMD_WILDCARD));
// Respond with a 0xC1
std::memcpy(response, &rc, IPMI_CC_LEN);
*data_len = IPMI_CC_LEN;
return rc;
}
}
#ifdef __IPMI_DEBUG__
// We have either a perfect match -OR- a wild card atleast,
log<level::ERR>("Calling Net function",
entry("NET_FUN=0x%X", netfn) entry("CMD=0x%X", cmd));
#endif
// Extract the map data onto appropriate containers
auto handler_and_context = iter->second;
// Creating a pointer type casted to char* to make sure we advance 1 byte
// when we advance pointer to next's address. advancing void * would not
// make sense.
char* respo = &((char*)response)[IPMI_CC_LEN];
try
{
// Response message from the plugin goes into a byte post the base
// response
rc = (handler_and_context.first)(netfn, cmd, request, respo, data_len,
handler_and_context.second);
}
// IPMI command handlers can throw unhandled exceptions, catch those
// and return sane error code.
catch (const std::exception& e)
{
log<level::ERR>(e.what(), entry("NET_FUN=0x%X", netfn),
entry("CMD=0x%X", cmd));
rc = IPMI_CC_UNSPECIFIED_ERROR;
*data_len = 0;
// fall through
}
// Now copy the return code that we got from handler and pack it in first
// byte.
std::memcpy(response, &rc, IPMI_CC_LEN);
// Data length is now actual data + completion code.
*data_len = *data_len + IPMI_CC_LEN;
return rc;
}
static int send_ipmi_message(sd_bus_message* req, unsigned char seq,
unsigned char netfn, unsigned char lun,
unsigned char cmd, unsigned char cc,
unsigned char* buf, unsigned char len)
{
sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus_message *reply = NULL, *m = NULL;
const char *dest, *path;
int r, pty;
dest = sd_bus_message_get_sender(req);
path = sd_bus_message_get_path(req);
r = sd_bus_message_new_method_call(bus, &m, dest, path, DBUS_INTF,
"sendMessage");
if (r < 0)
{
log<level::ERR>("Failed to add the method object",
entry("ERRNO=0x%X", -r));
return -1;
}
// Responses in IPMI require a bit set. So there ya go...
netfn |= 0x01;
// Add the bytes needed for the methods to be called
r = sd_bus_message_append(m, "yyyyy", seq, netfn, lun, cmd, cc);
if (r < 0)
{
log<level::ERR>("Failed add the netfn and others",
entry("ERRNO=0x%X", -r));
goto final;
}
r = sd_bus_message_append_array(m, 'y', buf, len);
if (r < 0)
{
log<level::ERR>("Failed to add the string of response bytes",
entry("ERRNO=0x%X", -r));
goto final;
}
// Call the IPMI responder on the bus so the message can be sent to the CEC
r = sd_bus_call(bus, m, 0, &error, &reply);
if (r < 0)
{
log<level::ERR>("Failed to call the method", entry("DEST=%s", dest),
entry("PATH=%s", path), entry("ERRNO=0x%X", -r));
goto final;
}
r = sd_bus_message_read(reply, "x", &pty);
if (r < 0)
{
log<level::ERR>("Failed to get a reply from the method",
entry("ERRNO=0x%X", -r));
}
final:
sd_bus_error_free(&error);
m = sd_bus_message_unref(m);
reply = sd_bus_message_unref(reply);
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}
void cache_restricted_mode()
{
restricted_mode = false;
using namespace sdbusplus::xyz::openbmc_project::Control::Security::server;
using namespace internal;
using namespace internal::cache;
sdbusplus::bus::bus dbus(ipmid_get_sd_bus_connection());
const auto& restrictionModeSetting =
objects->map.at(restrictionModeIntf).front();
auto method = dbus.new_method_call(
objects->service(restrictionModeSetting, restrictionModeIntf).c_str(),
restrictionModeSetting.c_str(), "org.freedesktop.DBus.Properties",
"Get");
method.append(restrictionModeIntf, "RestrictionMode");
auto resp = dbus.call(method);
if (resp.is_method_error())
{
log<level::ERR>("Error in RestrictionMode Get");
// Fail-safe to true.
restricted_mode = true;
return;
}
sdbusplus::message::variant<std::string> result;
resp.read(result);
auto restrictionMode = RestrictionMode::convertModesFromString(
variant_ns::get<std::string>(result));
if (RestrictionMode::Modes::Whitelist == restrictionMode)
{
restricted_mode = true;
}
}
static int handle_restricted_mode_change(sd_bus_message* m, void* user_data,
sd_bus_error* ret_error)
{
cache_restricted_mode();
return 0;
}
static int handle_ipmi_command(sd_bus_message* m, void* user_data,
sd_bus_error* ret_error)
{
int r = 0;
unsigned char sequence, netfn, lun, cmd;
const void* request;
size_t sz;
size_t resplen = MAX_IPMI_BUFFER;
unsigned char response[MAX_IPMI_BUFFER];
std::memset(response, 0, MAX_IPMI_BUFFER);
r = sd_bus_message_read(m, "yyyy", &sequence, &netfn, &lun, &cmd);
if (r < 0)
{
log<level::ERR>("Failed to parse signal message",
entry("ERRNO=0x%X", -r));
return -1;
}
r = sd_bus_message_read_array(m, 'y', &request, &sz);
if (r < 0)
{
log<level::ERR>("Failed to parse signal message",
entry("ERRNO=0x%X", -r));
return -1;
}
std::fprintf(ipmiio,
"IPMI Incoming: Seq 0x%02x, NetFn 0x%02x, CMD: 0x%02x \n",
sequence, netfn, cmd);
hexdump(ipmiio, (void*)request, sz);
// Allow the length field to be used for both input and output of the
// ipmi call
resplen = sz;
// Now that we have parsed the entire byte array from the caller
// we can call the ipmi router to do the work...
r = ipmi_netfn_router(netfn, cmd, (void*)request, (void*)response,
&resplen);
if (r != 0)
{
#ifdef __IPMI_DEBUG__
log<level::ERR>("ERROR in handling NetFn", entry("ERRNO=0x%X", -r),
entry("NET_FUN=0x%X", netfn), entry("CMD=0x%X", cmd));
#endif
resplen = 0;
}
else
{
resplen = resplen - 1; // first byte is for return code.
}
std::fprintf(ipmiio, "IPMI Response:\n");
hexdump(ipmiio, (void*)response, resplen);
// Send the response buffer from the ipmi command
r = send_ipmi_message(m, sequence, netfn, lun, cmd, response[0],
((unsigned char*)response) + 1, resplen);
if (r < 0)
{
log<level::ERR>("Failed to send the response message");
return -1;
}
return 0;
}
//----------------------------------------------------------------------
// handler_select
// Select all the files ending with with .so. in the given diretcory
// @d: dirent structure containing the file name
//----------------------------------------------------------------------
int handler_select(const struct dirent* entry)
{
// To hold ".so" from entry->d_name;
char dname_copy[4] = {0};
// We want to avoid checking for everything and isolate to the ones having
// .so.* or .so in them.
// Check for versioned libraries .so.*
if (strstr(entry->d_name, IPMI_PLUGIN_SONAME_EXTN))
{
return 1;
}
// Check for non versioned libraries .so
else if (strstr(entry->d_name, IPMI_PLUGIN_EXTN))
{
// It is possible that .so could be anywhere in the string but unlikely
// But being careful here. Get the base address of the string, move
// until end and come back 3 steps and that gets what we need.
strcpy(dname_copy, (entry->d_name + strlen(entry->d_name) -
strlen(IPMI_PLUGIN_EXTN)));
if (strcmp(dname_copy, IPMI_PLUGIN_EXTN) == 0)
{
return 1;
}
}
return 0;
}
// This will do a dlopen of every .so in ipmi_lib_path and will dlopen
// everything so that they will register a callback handler
void ipmi_register_callback_handlers(const char* ipmi_lib_path)
{
// For walking the ipmi_lib_path
struct dirent** handler_list;
// This is used to check and abort if someone tries to register a bad one.
void* lib_handler = NULL;
if (ipmi_lib_path == NULL)
{
log<level::ERR>("No handlers to be registered for ipmi.. Aborting");
assert(0);
}
else
{
// 1: Open ipmi_lib_path. Its usually "/usr/lib/phosphor-host-ipmid"
// 2: Scan the directory for the files that end with .so
// 3: For each one of them, just do a 'dlopen' so that they register
// the handlers for callback routines.
std::string handler_fqdn = ipmi_lib_path;
// Append a "/" since we need to add the name of the .so. If there is
// already a .so, adding one more is not any harm.
handler_fqdn += "/";
int num_handlers =
scandir(ipmi_lib_path, &handler_list, handler_select, alphasort);
if (num_handlers < 0)
return;
while (num_handlers--)
{
handler_fqdn = ipmi_lib_path;
handler_fqdn += handler_list[num_handlers]->d_name;
#ifdef __IPMI_DEBUG__
log<level::DEBUG>("Registering handler",
entry("HANDLER=%s", handler_fqdn.c_str()));
#endif
lib_handler = dlopen(handler_fqdn.c_str(), RTLD_NOW);
if (lib_handler == NULL)
{
log<level::ERR>("ERROR opening",
entry("HANDLER=%s", handler_fqdn.c_str()),
entry("ERROR=%s", dlerror()));
}
// Wipe the memory allocated for this particular entry.
free(handler_list[num_handlers]);
}
// Done with all registration.
free(handler_list);
}
// TODO : What to be done on the memory that is given by dlopen ?.
return;
}
sd_bus* ipmid_get_sd_bus_connection(void)
{
return bus;
}
sd_event* ipmid_get_sd_event_connection(void)
{
return events;
}
sd_bus_slot* ipmid_get_sd_bus_slot(void)
{
return ipmid_slot;
}
EInterfaceIndex getInterfaceIndex(void)
{
return interfaceKCS;
}
// Calls host command manager to do the right thing for the command
void ipmid_send_cmd_to_host(CommandHandler&& cmd)
{
return cmdManager->execute(std::move(cmd));
}
cmdManagerPtr& ipmid_get_host_cmd_manager()
{
return cmdManager;
}
sdbusPtr& ipmid_get_sdbus_plus_handler()
{
return sdbusp;
}
int main(int argc, char* argv[])
{
int r;
unsigned long tvalue;
int c;
// This file and subsequient switch is for turning on levels
// of trace
ipmicmddetails = ipmiio = ipmidbus = fopen("/dev/null", "w");
while ((c = getopt(argc, argv, "h:d:")) != -1)
switch (c)
{
case 'd':
tvalue = strtoul(optarg, NULL, 16);
if (1 & tvalue)
{
ipmiio = stdout;
}
if (2 & tvalue)
{
ipmidbus = stdout;
}
if (4 & tvalue)
{
ipmicmddetails = stdout;
}
break;
case 'h':
case '?':
print_usage();
return 1;
}
/* Connect to system bus */
r = sd_bus_default_system(&bus);
if (r < 0)
{
log<level::ERR>("Failed to connect to system bus",
entry("ERRNO=0x%X", -r));
goto finish;
}
/* Get an sd event handler */
r = sd_event_default(&events);
if (r < 0)
{
log<level::ERR>("Failure to create sd_event handler",
entry("ERRNO=0x%X", -r));
goto finish;
}
// Now create the Host Bound Command manager. Need sdbusplus
// to use the generated bindings
sdbusp = std::make_unique<sdbusplus::bus::bus>(bus);
sdbusp->request_name("xyz.openbmc_project.Ipmi.Host");
cmdManager =
std::make_unique<phosphor::host::command::Manager>(*sdbusp, events);
// Activate OemRouter.
oem::mutableRouter()->activate();
// Register all the handlers that provider implementation to IPMI commands.
ipmi_register_callback_handlers(HOST_IPMI_LIB_PATH);
// Watch for BT messages
r = sd_bus_add_match(bus, &ipmid_slot, FILTER, handle_ipmi_command, NULL);
if (r < 0)
{
log<level::ERR>("Failed: sd_bus_add_match", entry("FILTER=%s", FILTER),
entry("ERRNO=0x%X", -r));
goto finish;
}
// Attach the bus to sd_event to service user requests
sd_bus_attach_event(bus, events, SD_EVENT_PRIORITY_NORMAL);
{
using namespace internal;
using namespace internal::cache;
sdbusplus::bus::bus dbus{bus};
objects = std::make_unique<settings::Objects>(
dbus, std::vector<settings::Interface>({restrictionModeIntf}));
// Initialize restricted mode
cache_restricted_mode();
// Wait for changes on Restricted mode
sdbusplus::bus::match_t restrictedModeMatch(
dbus,
sdbusRule::propertiesChanged(
objects->map.at(restrictionModeIntf).front(),
restrictionModeIntf),
handle_restricted_mode_change);
for (;;)
{
/* Process requests */
r = sd_event_run(events, (uint64_t)-1);
if (r < 0)
{
log<level::ERR>("Failure in processing request",
entry("ERRNO=0x%X", -r));
goto finish;
}
}
}
finish:
sd_event_unref(events);
sd_bus_detach_event(bus);
sd_bus_slot_unref(ipmid_slot);
sd_bus_unref(bus);
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}