diff options
Diffstat (limited to 'apphandler.cpp')
-rw-r--r-- | apphandler.cpp | 1362 |
1 files changed, 1000 insertions, 362 deletions
diff --git a/apphandler.cpp b/apphandler.cpp index 8be092c..2430e8e 100644 --- a/apphandler.cpp +++ b/apphandler.cpp @@ -1,6 +1,12 @@ #include <arpa/inet.h> +#include <fcntl.h> #include <limits.h> +#include <linux/i2c-dev.h> +#include <linux/i2c.h> #include <mapper.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> #include <systemd/sd-bus.h> #include <unistd.h> @@ -14,6 +20,8 @@ #include <filesystem> #include <fstream> #include <ipmid/api.hpp> +#include <ipmid/sessiondef.hpp> +#include <ipmid/sessionhelper.hpp> #include <ipmid/types.hpp> #include <ipmid/utils.hpp> #include <memory> @@ -23,7 +31,6 @@ #include <sdbusplus/message/types.hpp> #include <string> #include <sys_info_param.hpp> -#include <transporthandler.hpp> #include <tuple> #include <vector> #include <xyz/openbmc_project/Common/error.hpp> @@ -36,10 +43,6 @@ extern sd_bus* bus; constexpr auto bmc_state_interface = "xyz.openbmc_project.State.BMC"; constexpr auto bmc_state_property = "CurrentBMCState"; -constexpr auto bmc_interface = "xyz.openbmc_project.Inventory.Item.Bmc"; -constexpr auto bmc_guid_interface = "xyz.openbmc_project.Common.UUID"; -constexpr auto bmc_guid_property = "UUID"; -constexpr auto bmc_guid_len = 16; static constexpr auto redundancyIntf = "xyz.openbmc_project.Software.RedundancyPriority"; @@ -57,7 +60,35 @@ using Activation = sdbusplus::xyz::openbmc_project::Software::server::Activation; using BMC = sdbusplus::xyz::openbmc_project::State::server::BMC; namespace fs = std::filesystem; -namespace variant_ns = sdbusplus::message::variant_ns; + +#ifdef ENABLE_I2C_WHITELIST_CHECK +typedef struct +{ + uint8_t busId; + uint8_t slaveAddr; + uint8_t slaveAddrMask; + std::vector<uint8_t> data; + std::vector<uint8_t> dataMask; +} i2cMasterWRWhitelist; + +static std::vector<i2cMasterWRWhitelist>& getWRWhitelist() +{ + static std::vector<i2cMasterWRWhitelist> wrWhitelist; + return wrWhitelist; +} + +static constexpr const char* i2cMasterWRWhitelistFile = + "/usr/share/ipmi-providers/master_write_read_white_list.json"; + +static constexpr const char* filtersStr = "filters"; +static constexpr const char* busIdStr = "busId"; +static constexpr const char* slaveAddrStr = "slaveAddr"; +static constexpr const char* slaveAddrMaskStr = "slaveAddrMask"; +static constexpr const char* cmdStr = "command"; +static constexpr const char* cmdMaskStr = "commandMask"; +static constexpr int base_16 = 16; +#endif // ENABLE_I2C_WHITELIST_CHECK +static constexpr uint8_t maxIPMIWriteReadSize = 144; /** * @brief Returns the Version info from primary s/w object @@ -70,16 +101,14 @@ namespace variant_ns = sdbusplus::message::variant_ns; * @return On success returns the Version info from primary s/w object. * */ -std::string getActiveSoftwareVersionInfo() +std::string getActiveSoftwareVersionInfo(ipmi::Context::ptr ctx) { - auto busp = getSdBus(); - std::string revision{}; ipmi::ObjectTree objectTree; try { objectTree = - ipmi::getAllDbusObjects(*busp, softwareRoot, redundancyIntf); + ipmi::getAllDbusObjects(*ctx->bus, softwareRoot, redundancyIntf); } catch (sdbusplus::exception::SdBusError& e) { @@ -93,9 +122,9 @@ std::string getActiveSoftwareVersionInfo() for (auto& softObject : objectTree) { auto service = - ipmi::getService(*busp, redundancyIntf, softObject.first); + ipmi::getService(*ctx->bus, redundancyIntf, softObject.first); auto objValueTree = - ipmi::getManagedObjects(*busp, service, softwareRoot); + ipmi::getManagedObjects(*ctx->bus, service, softwareRoot); auto minPriority = 0xFF; for (const auto& objIter : objValueTree) @@ -106,14 +135,14 @@ std::string getActiveSoftwareVersionInfo() auto& redundancyPriorityProps = intfMap.at(redundancyIntf); auto& versionProps = intfMap.at(versionIntf); auto& activationProps = intfMap.at(activationIntf); - auto priority = variant_ns::get<uint8_t>( - redundancyPriorityProps.at("Priority")); + auto priority = + std::get<uint8_t>(redundancyPriorityProps.at("Priority")); auto purpose = - variant_ns::get<std::string>(versionProps.at("Purpose")); - auto activation = variant_ns::get<std::string>( - activationProps.at("Activation")); + std::get<std::string>(versionProps.at("Purpose")); + auto activation = + std::get<std::string>(activationProps.at("Activation")); auto version = - variant_ns::get<std::string>(versionProps.at("Version")); + std::get<std::string>(versionProps.at("Version")); if ((Version::convertVersionPurposeFromString(purpose) == Version::VersionPurpose::BMC) && (Activation::convertActivationsFromString(activation) == @@ -154,9 +183,23 @@ bool getCurrentBmcState() ipmi::getDbusProperty(bus, bmcObject.second, bmcObject.first, bmc_state_interface, bmc_state_property); - return variant_ns::holds_alternative<std::string>(variant) && - BMC::convertBMCStateFromString( - variant_ns::get<std::string>(variant)) == BMC::BMCState::Ready; + return std::holds_alternative<std::string>(variant) && + BMC::convertBMCStateFromString(std::get<std::string>(variant)) == + BMC::BMCState::Ready; +} + +bool getCurrentBmcStateWithFallback(const bool fallbackAvailability) +{ + try + { + return getCurrentBmcState(); + } + catch (...) + { + // Nothing provided the BMC interface, therefore return whatever was + // configured as the default. + return fallbackAvailability; + } } namespace acpi_state @@ -198,12 +241,6 @@ enum class PowerState : uint8_t static constexpr uint8_t stateChanged = 0x80; -struct ACPIState -{ - uint8_t sysACPIState; - uint8_t devACPIState; -} __attribute__((packed)); - std::map<ACPIPowerState::ACPI, PowerState> dbusToIPMI = { {ACPIPowerState::ACPI::S0_G0_D0, PowerState::s0G0D0}, {ACPIPowerState::ACPI::S1_D1, PowerState::s1D1}, @@ -259,41 +296,32 @@ bool isValidACPIState(acpi_state::PowerStateType type, uint8_t state) } } // namespace acpi_state -ipmi_ret_t ipmi_app_set_acpi_power_state(ipmi_netfn_t netfn, ipmi_cmd_t cmd, - ipmi_request_t request, - ipmi_response_t response, - ipmi_data_len_t data_len, - ipmi_context_t context) +/** @brief implements Set ACPI Power State command + * @param sysAcpiState - ACPI system power state to set + * @param devAcpiState - ACPI device power state to set + * + * @return IPMI completion code on success + **/ +ipmi::RspType<> ipmiSetAcpiPowerState(uint8_t sysAcpiState, + uint8_t devAcpiState) { auto s = static_cast<uint8_t>(acpi_state::PowerState::unknown); - ipmi_ret_t rc = IPMI_CC_OK; sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; auto value = acpi_state::ACPIPowerState::ACPI::Unknown; - auto* req = reinterpret_cast<acpi_state::ACPIState*>(request); - - if (*data_len != sizeof(acpi_state::ACPIState)) - { - log<level::ERR>("set_acpi invalid len"); - *data_len = 0; - return IPMI_CC_REQ_DATA_LEN_INVALID; - } - - *data_len = 0; - - if (req->sysACPIState & acpi_state::stateChanged) + if (sysAcpiState & acpi_state::stateChanged) { // set system power state - s = req->sysACPIState & ~acpi_state::stateChanged; + s = sysAcpiState & ~acpi_state::stateChanged; if (!acpi_state::isValidACPIState( acpi_state::PowerStateType::sysPowerState, s)) { log<level::ERR>("set_acpi_power sys invalid input", entry("S=%x", s)); - return IPMI_CC_PARM_OUT_OF_RANGE; + return ipmi::responseParmOutOfRange(); } // valid input @@ -324,7 +352,7 @@ ipmi_ret_t ipmi_app_set_acpi_power_state(ipmi_netfn_t netfn, ipmi_cmd_t cmd, { log<level::ERR>("Failed in set ACPI system property", entry("EXCEPTION=%s", e.what())); - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } } } @@ -333,16 +361,16 @@ ipmi_ret_t ipmi_app_set_acpi_power_state(ipmi_netfn_t netfn, ipmi_cmd_t cmd, log<level::DEBUG>("Do not change system power state"); } - if (req->devACPIState & acpi_state::stateChanged) + if (devAcpiState & acpi_state::stateChanged) { // set device power state - s = req->devACPIState & ~acpi_state::stateChanged; + s = devAcpiState & ~acpi_state::stateChanged; if (!acpi_state::isValidACPIState( acpi_state::PowerStateType::devPowerState, s)) { log<level::ERR>("set_acpi_power dev invalid input", entry("S=%x", s)); - return IPMI_CC_PARM_OUT_OF_RANGE; + return ipmi::responseParmOutOfRange(); } // valid input @@ -373,7 +401,7 @@ ipmi_ret_t ipmi_app_set_acpi_power_state(ipmi_netfn_t netfn, ipmi_cmd_t cmd, { log<level::ERR>("Failed in set ACPI device property", entry("EXCEPTION=%s", e.what())); - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } } } @@ -381,24 +409,26 @@ ipmi_ret_t ipmi_app_set_acpi_power_state(ipmi_netfn_t netfn, ipmi_cmd_t cmd, { log<level::DEBUG>("Do not change device power state"); } - - return rc; + return ipmi::responseSuccess(); } -ipmi_ret_t ipmi_app_get_acpi_power_state(ipmi_netfn_t netfn, ipmi_cmd_t cmd, - ipmi_request_t request, - ipmi_response_t response, - ipmi_data_len_t data_len, - ipmi_context_t context) +/** + * @brief implements the get ACPI power state command + * + * @return IPMI completion code plus response data on success. + * - ACPI system power state + * - ACPI device power state + **/ +ipmi::RspType<uint8_t, // acpiSystemPowerState + uint8_t // acpiDevicePowerState + > + ipmiGetAcpiPowerState() { - ipmi_ret_t rc = IPMI_CC_OK; - - auto* res = reinterpret_cast<acpi_state::ACPIState*>(response); + uint8_t sysAcpiState; + uint8_t devAcpiState; sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; - *data_len = 0; - try { auto acpiObject = ipmi::getDbusObject(bus, acpi_state::acpiInterface); @@ -407,26 +437,22 @@ ipmi_ret_t ipmi_app_get_acpi_power_state(ipmi_netfn_t netfn, ipmi_cmd_t cmd, bus, acpiObject.second, acpiObject.first, acpi_state::acpiInterface, acpi_state::sysACPIProp); auto sysACPI = acpi_state::ACPIPowerState::convertACPIFromString( - variant_ns::get<std::string>(sysACPIVal)); - res->sysACPIState = - static_cast<uint8_t>(acpi_state::dbusToIPMI.at(sysACPI)); + std::get<std::string>(sysACPIVal)); + sysAcpiState = static_cast<uint8_t>(acpi_state::dbusToIPMI.at(sysACPI)); auto devACPIVal = ipmi::getDbusProperty( bus, acpiObject.second, acpiObject.first, acpi_state::acpiInterface, acpi_state::devACPIProp); auto devACPI = acpi_state::ACPIPowerState::convertACPIFromString( - variant_ns::get<std::string>(devACPIVal)); - res->devACPIState = - static_cast<uint8_t>(acpi_state::dbusToIPMI.at(devACPI)); - - *data_len = sizeof(acpi_state::ACPIState); + std::get<std::string>(devACPIVal)); + devAcpiState = static_cast<uint8_t>(acpi_state::dbusToIPMI.at(devACPI)); } catch (const InternalFailure& e) { - log<level::ERR>("Failed in get ACPI property"); - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::responseUnspecifiedError(); } - return rc; + + return ipmi::responseSuccess(sysAcpiState, devAcpiState); } typedef struct @@ -524,16 +550,31 @@ int convertVersion(std::string s, Revision& rev) return 0; } -auto ipmiAppGetDeviceId() -> ipmi::RspType<uint8_t, // Device ID - uint8_t, // Device Revision - uint8_t, // Firmware Revision Major - uint8_t, // Firmware Revision minor - uint8_t, // IPMI version - uint8_t, // Additional device support - uint24_t, // MFG ID - uint16_t, // Product ID - uint32_t // AUX info - > +/* @brief: Implement the Get Device ID IPMI command per the IPMI spec + * @param[in] ctx - shared_ptr to an IPMI context struct + * + * @returns IPMI completion code plus response data + * - Device ID (manufacturer defined) + * - Device revision[4 bits]; reserved[3 bits]; SDR support[1 bit] + * - FW revision major[7 bits] (binary encoded); available[1 bit] + * - FW Revision minor (BCD encoded) + * - IPMI version (0x02 for IPMI 2.0) + * - device support (bitfield of supported options) + * - MFG IANA ID (3 bytes) + * - product ID (2 bytes) + * - AUX info (4 bytes) + */ +ipmi::RspType<uint8_t, // Device ID + uint8_t, // Device Revision + uint8_t, // Firmware Revision Major + uint8_t, // Firmware Revision minor + uint8_t, // IPMI version + uint8_t, // Additional device support + uint24_t, // MFG ID + uint16_t, // Product ID + uint32_t // AUX info + > + ipmiAppGetDeviceId(ipmi::Context::ptr ctx) { int r = -1; Revision rev = {0}; @@ -549,6 +590,7 @@ auto ipmiAppGetDeviceId() -> ipmi::RspType<uint8_t, // Device ID uint32_t aux; } devId; static bool dev_id_initialized = false; + static bool defaultActivationSetting = true; const char* filename = "/usr/share/ipmi-providers/dev_id.json"; constexpr auto ipmiDevIdStateShift = 7; constexpr auto ipmiDevIdFw1Mask = ~(1 << ipmiDevIdStateShift); @@ -557,7 +599,7 @@ auto ipmiAppGetDeviceId() -> ipmi::RspType<uint8_t, // Device ID { try { - auto version = getActiveSoftwareVersionInfo(); + auto version = getActiveSoftwareVersionInfo(ctx); r = convertVersion(version, rev); } catch (const std::exception& e) @@ -596,6 +638,9 @@ auto ipmiAppGetDeviceId() -> ipmi::RspType<uint8_t, // Device ID devId.prodId = data.value("prod_id", 0); devId.aux = data.value("aux", 0); + // Set the availablitity of the BMC. + defaultActivationSetting = data.value("availability", true); + // Don't read the file every time if successful dev_id_initialized = true; } @@ -614,7 +659,7 @@ auto ipmiAppGetDeviceId() -> ipmi::RspType<uint8_t, // Device ID // Set availability to the actual current BMC state devId.fw[0] &= ipmiDevIdFw1Mask; - if (!getCurrentBmcState()) + if (!getCurrentBmcStateWithFallback(defaultActivationSetting)) { devId.fw[0] |= (1 << ipmiDevIdStateShift); } @@ -651,120 +696,75 @@ auto ipmiAppGetSelfTestResults() -> ipmi::RspType<uint8_t, uint8_t> return ipmi::responseSuccess(notImplemented, zero); } -ipmi_ret_t ipmi_app_get_device_guid(ipmi_netfn_t netfn, ipmi_cmd_t cmd, - ipmi_request_t request, - ipmi_response_t response, - ipmi_data_len_t data_len, - ipmi_context_t context) +static constexpr size_t uuidBinaryLength = 16; +static std::array<uint8_t, uuidBinaryLength> rfc4122ToIpmi(std::string rfc4122) { - const char* objname = "/org/openbmc/control/chassis0"; - const char* iface = "org.freedesktop.DBus.Properties"; - const char* chassis_iface = "org.openbmc.control.Chassis"; - sd_bus_message* reply = NULL; - sd_bus_error error = SD_BUS_ERROR_NULL; - int r = 0; - char* uuid = NULL; - char* busname = NULL; - + using Argument = xyz::openbmc_project::Common::InvalidArgument; // UUID is in RFC4122 format. Ex: 61a39523-78f2-11e5-9862-e6402cfc3223 // Per IPMI Spec 2.0 need to convert to 16 hex bytes and reverse the byte // order // Ex: 0x2332fc2c40e66298e511f2782395a361 - - const int resp_size = 16; // Response is 16 hex bytes per IPMI Spec - uint8_t resp_uuid[resp_size]; // Array to hold the formatted response - // Point resp end of array to save in reverse order - int resp_loc = resp_size - 1; - int i = 0; - char* tokptr = NULL; - char* id_octet = NULL; - size_t total_uuid_size = 0; - // 1 byte of resp is built from 2 chars of uuid. - constexpr size_t max_uuid_size = 2 * resp_size; - - // Status code. - ipmi_ret_t rc = IPMI_CC_OK; - *data_len = 0; - - // Call Get properties method with the interface and property name - r = mapper_get_service(bus, objname, &busname); - if (r < 0) - { - log<level::ERR>("Failed to get bus name", entry("BUS=%s", objname), - entry("ERRNO=0x%X", -r)); - goto finish; - } - r = sd_bus_call_method(bus, busname, objname, iface, "Get", &error, &reply, - "ss", chassis_iface, "uuid"); - if (r < 0) - { - log<level::ERR>("Failed to call Get Method", entry("ERRNO=0x%X", -r)); - rc = IPMI_CC_UNSPECIFIED_ERROR; - goto finish; - } - - r = sd_bus_message_read(reply, "v", "s", &uuid); - if (r < 0 || uuid == NULL) + constexpr size_t uuidHexLength = (2 * uuidBinaryLength); + constexpr size_t uuidRfc4122Length = (uuidHexLength + 4); + std::array<uint8_t, uuidBinaryLength> uuid; + if (rfc4122.size() == uuidRfc4122Length) { - log<level::ERR>("Failed to get a response", entry("ERRNO=0x%X", -r)); - rc = IPMI_CC_RESPONSE_ERROR; - goto finish; + rfc4122.erase(std::remove(rfc4122.begin(), rfc4122.end(), '-'), + rfc4122.end()); } - - // Traverse the UUID - // Get the UUID octects separated by dash - id_octet = strtok_r(uuid, "-", &tokptr); - - if (id_octet == NULL) + if (rfc4122.size() != uuidHexLength) { - // Error - log<level::ERR>("Unexpected UUID format", entry("UUID=%s", uuid)); - rc = IPMI_CC_RESPONSE_ERROR; - goto finish; + elog<InvalidArgument>(Argument::ARGUMENT_NAME("rfc4122"), + Argument::ARGUMENT_VALUE(rfc4122.c_str())); } - - while (id_octet != NULL) + for (size_t ind = 0; ind < uuidHexLength; ind += 2) { - // Calculate the octet string size since it varies - // Divide it by 2 for the array size since 1 byte is built from 2 chars - int tmp_size = strlen(id_octet) / 2; - - // Check if total UUID size has been exceeded - if ((total_uuid_size += strlen(id_octet)) > max_uuid_size) + char v[3]; + v[0] = rfc4122[ind]; + v[1] = rfc4122[ind + 1]; + v[2] = 0; + size_t err; + long b; + try { - // Error - UUID too long to store - log<level::ERR>("UUID too long", entry("UUID=%s", uuid)); - rc = IPMI_CC_RESPONSE_ERROR; - goto finish; + b = std::stoul(v, &err, 16); } - - for (i = 0; i < tmp_size; i++) + catch (std::exception& e) + { + elog<InvalidArgument>(Argument::ARGUMENT_NAME("rfc4122"), + Argument::ARGUMENT_VALUE(rfc4122.c_str())); + } + // check that exactly two ascii bytes were converted + if (err != 2) { - // Holder of the 2 chars that will become a byte - char tmp_array[3] = {0}; - strncpy(tmp_array, id_octet, 2); // 2 chars at a time - - int resp_byte = strtoul(tmp_array, NULL, 16); // Convert to hex byte - // Copy end to first - std::memcpy((void*)&resp_uuid[resp_loc], &resp_byte, 1); - resp_loc--; - id_octet += 2; // Finished with the 2 chars, advance + elog<InvalidArgument>(Argument::ARGUMENT_NAME("rfc4122"), + Argument::ARGUMENT_VALUE(rfc4122.c_str())); } - id_octet = strtok_r(NULL, "-", &tokptr); // Get next octet + uuid[uuidBinaryLength - (ind / 2) - 1] = static_cast<uint8_t>(b); } + return uuid; +} + +auto ipmiAppGetDeviceGuid() + -> ipmi::RspType<std::array<uint8_t, uuidBinaryLength>> +{ + // return a fixed GUID based on /etc/machine-id + // This should match the /redfish/v1/Managers/bmc's UUID data - // Data length - *data_len = resp_size; + // machine specific application ID (for BMC ID) + // generated by systemd-id128 -p new as per man page + static constexpr sd_id128_t bmcUuidAppId = SD_ID128_MAKE( + e0, e1, 73, 76, 64, 61, 47, da, a5, 0c, d0, cc, 64, 12, 45, 78); - // Pack the actual response - std::memcpy(response, &resp_uuid, *data_len); + sd_id128_t bmcUuid; + // create the UUID from /etc/machine-id via the systemd API + sd_id128_get_machine_app_specific(bmcUuidAppId, &bmcUuid); -finish: - sd_bus_error_free(&error); - reply = sd_bus_message_unref(reply); - free(busname); + char bmcUuidCstr[SD_ID128_STRING_MAX]; + std::string systemUuid = sd_id128_to_string(bmcUuid, bmcUuidCstr); - return rc; + std::array<uint8_t, uuidBinaryLength> uuid = rfc4122ToIpmi(systemUuid); + return ipmi::responseSuccess(uuid); } auto ipmiAppGetBtCapabilities() @@ -782,210 +782,524 @@ auto ipmiAppGetBtCapabilities() outputBufferSize, transactionTime, nrRetries); } -ipmi_ret_t ipmi_app_get_sys_guid(ipmi_netfn_t netfn, ipmi_cmd_t cmd, - ipmi_request_t request, - ipmi_response_t response, - ipmi_data_len_t data_len, - ipmi_context_t context) - +auto ipmiAppGetSystemGuid() -> ipmi::RspType<std::array<uint8_t, 16>> { - ipmi_ret_t rc = IPMI_CC_OK; - sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; + static constexpr auto bmcInterface = + "xyz.openbmc_project.Inventory.Item.Bmc"; + static constexpr auto uuidInterface = "xyz.openbmc_project.Common.UUID"; + static constexpr auto uuidProperty = "UUID"; + ipmi::Value propValue; try { // Get the Inventory object implementing BMC interface - ipmi::DbusObjectInfo bmcObject = - ipmi::getDbusObject(bus, bmc_interface); + auto busPtr = getSdBus(); + auto objectInfo = ipmi::getDbusObject(*busPtr, bmcInterface); // Read UUID property value from bmcObject // UUID is in RFC4122 format Ex: 61a39523-78f2-11e5-9862-e6402cfc3223 - auto variant = - ipmi::getDbusProperty(bus, bmcObject.second, bmcObject.first, - bmc_guid_interface, bmc_guid_property); - std::string guidProp = variant_ns::get<std::string>(variant); - - // Erase "-" characters from the property value - guidProp.erase(std::remove(guidProp.begin(), guidProp.end(), '-'), - guidProp.end()); + propValue = + ipmi::getDbusProperty(*busPtr, objectInfo.second, objectInfo.first, + uuidInterface, uuidProperty); + } + catch (const InternalFailure& e) + { + log<level::ERR>("Failed in reading BMC UUID property", + entry("INTERFACE=%s", uuidInterface), + entry("PROPERTY=%s", uuidProperty)); + return ipmi::responseUnspecifiedError(); + } + std::array<uint8_t, 16> uuid; + std::string rfc4122Uuid = std::get<std::string>(propValue); + try + { + // convert to IPMI format + uuid = rfc4122ToIpmi(rfc4122Uuid); + } + catch (const InvalidArgument& e) + { + log<level::ERR>("Failed in parsing BMC UUID property", + entry("INTERFACE=%s", uuidInterface), + entry("PROPERTY=%s", uuidProperty), + entry("VALUE=%s", rfc4122Uuid.c_str())); + return ipmi::responseUnspecifiedError(); + } + return ipmi::responseSuccess(uuid); +} - auto guidPropLen = guidProp.length(); - // Validate UUID data - // Divide by 2 as 1 byte is built from 2 chars - if ((guidPropLen <= 0) || ((guidPropLen / 2) != bmc_guid_len)) +/** + * @brief set the session state as teardown + * + * This function is to set the session state to tear down in progress if the + * state is active. + * + * @param[in] busp - Dbus obj + * @param[in] service - service name + * @param[in] obj - object path + * + * @return success completion code if it sets the session state to + * tearDownInProgress else return the corresponding error completion code. + **/ +uint8_t setSessionState(std::shared_ptr<sdbusplus::asio::connection>& busp, + const std::string& service, const std::string& obj) +{ + try + { + uint8_t sessionState = std::get<uint8_t>(ipmi::getDbusProperty( + *busp, service, obj, session::sessionIntf, "State")); + if (sessionState == static_cast<uint8_t>(session::State::active)) { - log<level::ERR>("Invalid UUID property value", - entry("UUID_LENGTH=%d", guidPropLen)); - return IPMI_CC_RESPONSE_ERROR; + ipmi::setDbusProperty( + *busp, service, obj, session::sessionIntf, "State", + static_cast<uint8_t>(session::State::tearDownInProgress)); + return ipmi::ccSuccess; } + } + catch (std::exception& e) + { + log<level::ERR>("Failed in getting session state property", + entry("service=%s", service.c_str()), + entry("object path=%s", obj.c_str()), + entry("interface=%s", session::sessionIntf)); + return ipmi::ccUnspecifiedError; + } - // Convert data in RFC4122(MSB) format to LSB format - // Get 2 characters at a time as 1 byte is built from 2 chars and - // convert to hex byte - // TODO: Data printed for GUID command is not as per the - // GUID format defined in IPMI specification 2.0 section 20.8 - // Ticket raised: https://sourceforge.net/p/ipmitool/bugs/501/ - uint8_t respGuid[bmc_guid_len]; - for (size_t i = 0, respLoc = (bmc_guid_len - 1); - i < guidPropLen && respLoc >= 0; i += 2, respLoc--) - { - auto value = static_cast<uint8_t>( - std::stoi(guidProp.substr(i, 2).c_str(), NULL, 16)); - respGuid[respLoc] = value; - } + return ipmi::ccInvalidFieldRequest; +} - *data_len = bmc_guid_len; - std::memcpy(response, &respGuid, bmc_guid_len); +ipmi::RspType<> ipmiAppCloseSession(uint32_t reqSessionId, + std::optional<uint8_t> requestSessionHandle) +{ + auto busp = getSdBus(); + uint8_t reqSessionHandle = + requestSessionHandle.value_or(session::defaultSessionHandle); + + if (reqSessionId == session::sessionZero && + reqSessionHandle == session::defaultSessionHandle) + { + return ipmi::response(session::ccInvalidSessionId); } - catch (const InternalFailure& e) + + if (reqSessionId == session::sessionZero && + reqSessionHandle == session::invalidSessionHandle) { - log<level::ERR>("Failed in reading BMC UUID property", - entry("INTERFACE=%s", bmc_interface), - entry("PROPERTY_INTERFACE=%s", bmc_guid_interface), - entry("PROPERTY=%s", bmc_guid_property)); - return IPMI_CC_UNSPECIFIED_ERROR; + return ipmi::response(session::ccInvalidSessionHandle); } - return rc; -} -static std::unique_ptr<SysInfoParamStore> sysInfoParamStore; + if (reqSessionId != session::sessionZero && + reqSessionHandle != session::defaultSessionHandle) + { + return ipmi::response(ipmi::ccInvalidFieldRequest); + } -static std::string sysInfoReadSystemName() -{ - // Use the BMC hostname as the "System Name." - char hostname[HOST_NAME_MAX + 1] = {}; - if (gethostname(hostname, HOST_NAME_MAX) != 0) + try { - perror("System info parameter: system name"); + ipmi::ObjectTree objectTree = ipmi::getAllDbusObjects( + *busp, session::sessionManagerRootPath, session::sessionIntf); + + for (auto& objectTreeItr : objectTree) + { + const std::string obj = objectTreeItr.first; + + if (isSessionObjectMatched(obj, reqSessionId, reqSessionHandle)) + { + auto& serviceMap = objectTreeItr.second; + + // Session id and session handle are unique for each session. + // Session id and handler are retrived from the object path and + // object path will be unique for each session. Checking if + // multiple objects exist with same object path under multiple + // services. + if (serviceMap.size() != 1) + { + return ipmi::responseUnspecifiedError(); + } + + auto itr = serviceMap.begin(); + const std::string service = itr->first; + return ipmi::response(setSessionState(busp, service, obj)); + } + } } - return hostname; + catch (sdbusplus::exception::SdBusError& e) + { + log<level::ERR>("Failed to fetch object from dbus", + entry("INTERFACE=%s", session::sessionIntf), + entry("ERRMSG=%s", e.what())); + return ipmi::responseUnspecifiedError(); + } + + return ipmi::responseInvalidFieldRequest(); } -struct IpmiSysInfoResp +uint8_t getTotalSessionCount() { - uint8_t paramRevision; - uint8_t setSelector; - union + uint8_t count = 0, ch = 1; + + while (ch < ipmi::maxIpmiChannels && + count < session::maxNetworkInstanceSupported) { - struct + ipmi::ChannelInfo chInfo; + ipmi::getChannelInfo(ch, chInfo); + if (static_cast<ipmi::EChannelMediumType>(chInfo.mediumType) == + ipmi::EChannelMediumType::lan8032) { - uint8_t encoding; - uint8_t stringLen; - uint8_t stringData0[14]; - } __attribute__((packed)); - uint8_t stringDataN[16]; - uint8_t byteData; - }; -} __attribute__((packed)); + count++; + } + ch++; + } + return count * session::maxSessionCountPerChannel; +} /** - * Split a string into (up to) 16-byte chunks as expected in response for get - * system info parameter. + * @brief get session info request data. * - * @param[in] fullString: Input string to be split - * @param[in] chunkIndex: Index of the chunk to be written out - * @param[in,out] chunk: Output data buffer; must have 14 byte capacity if - * chunk_index = 0 and 16-byte capacity otherwise - * @return the number of bytes written into the output buffer, or -EINVAL for - * invalid arguments. - */ -static int splitStringParam(const std::string& fullString, int chunkIndex, - uint8_t* chunk) + * This function validates the request data and retrive request session id, + * session handle. + * + * @param[in] ctx - context of current session. + * @param[in] sessionIndex - request session index + * @param[in] payload - input payload + * @param[in] reqSessionId - unpacked session Id will be asigned + * @param[in] reqSessionHandle - unpacked session handle will be asigned + * + * @return success completion code if request data is valid + * else return the correcponding error completion code. + **/ +uint8_t getSessionInfoRequestData(const ipmi::Context::ptr ctx, + const uint8_t sessionIndex, + ipmi::message::Payload& payload, + uint32_t& reqSessionId, + uint8_t& reqSessionHandle) { - constexpr int maxChunk = 255; - constexpr int smallChunk = 14; - constexpr int chunkSize = 16; - if (chunkIndex > maxChunk || chunk == nullptr) + if ((sessionIndex > session::maxSessionCountPerChannel) && + (sessionIndex < session::searchSessionByHandle)) { - return -EINVAL; + return ipmi::ccInvalidFieldRequest; } + + switch (sessionIndex) + { + case session::searchCurrentSession: + + ipmi::ChannelInfo chInfo; + ipmi::getChannelInfo(ctx->channel, chInfo); + + if (static_cast<ipmi::EChannelMediumType>(chInfo.mediumType) != + ipmi::EChannelMediumType::lan8032) + { + return ipmi::ccInvalidFieldRequest; + } + + if (!payload.fullyUnpacked()) + { + return ipmi::ccReqDataLenInvalid; + } + // Check if current sessionId is 0, sessionId 0 is reserved. + if (ctx->sessionId == session::sessionZero) + { + return session::ccInvalidSessionId; + } + reqSessionId = ctx->sessionId; + break; + + case session::searchSessionByHandle: + + if ((payload.unpack(reqSessionHandle)) || + (!payload.fullyUnpacked())) + { + return ipmi::ccReqDataLenInvalid; + } + + if ((reqSessionHandle == session::sessionZero) || + ((reqSessionHandle & session::multiIntfaceSessionHandleMask) > + session::maxSessionCountPerChannel)) + { + return session::ccInvalidSessionHandle; + } + break; + + case session::searchSessionById: + + if ((payload.unpack(reqSessionId)) || (!payload.fullyUnpacked())) + { + return ipmi::ccReqDataLenInvalid; + } + + if (reqSessionId == session::sessionZero) + { + return session::ccInvalidSessionId; + } + break; + + default: + if (!payload.fullyUnpacked()) + { + return ipmi::ccReqDataLenInvalid; + } + break; + } + return ipmi::ccSuccess; +} + +uint8_t getSessionState(std::shared_ptr<sdbusplus::asio::connection>& busp, + const std::string& service, const std::string& objPath, + uint8_t& sessionState) +{ + try + { + sessionState = std::get<uint8_t>(ipmi::getDbusProperty( + *busp, service, objPath, session::sessionIntf, "State")); + } + catch (sdbusplus::exception::SdBusError& e) + { + log<level::ERR>("Failed to fetch state property ", + entry("SERVICE=%s", service.c_str()), + entry("OBJECTPATH=%s", objPath.c_str()), + entry("INTERFACE=%s", session::sessionIntf), + entry("ERRMSG=%s", e.what())); + return ipmi::ccUnspecifiedError; + } + + return ipmi::ccSuccess; +} + +static constexpr uint8_t macAddrLen = 6; +struct GetSessionInfoRes +{ + uint8_t sessionHandle; + uint8_t totalSessionCount; + uint8_t activeSessionCount; + uint8_t userID; + uint8_t privLevel; + uint8_t channelNumber; + uint32_t remoteIpAddr; + std::array<uint8_t, macAddrLen> macAddr = {0}; + uint16_t remotePort; +}; + +uint8_t + fillGetSessionInfoRes(std::shared_ptr<sdbusplus::asio::connection>& busp, + const std::string& service, + const std::string& objPath, + struct GetSessionInfoRes& resp, uint8_t& sessionState) +{ try { - std::string output; - if (chunkIndex == 0) + ipmi::PropertyMap sessionProps = ipmi::getAllDbusProperties( + *busp, service, objPath, session::sessionIntf); + + sessionState = std::get<uint8_t>(sessionProps.at("State")); + if (sessionState == static_cast<uint8_t>(session::State::active)) { - // Output must have 14 byte capacity. - output = fullString.substr(0, smallChunk); + resp.sessionHandle = + std::get<uint8_t>(sessionProps["SessionHandle"]); + resp.userID = std::get<uint8_t>(sessionProps["UserID"]); + resp.privLevel = + std::get<uint8_t>(sessionProps["CurrentPrivilege"]); + resp.channelNumber = std::get<uint8_t>(sessionProps["ChannelNum"]); + resp.remoteIpAddr = + std::get<uint32_t>(sessionProps["RemoteIPAddr"]); + resp.remotePort = std::get<uint16_t>(sessionProps["RemotePort"]); } - else + } + catch (sdbusplus::exception::SdBusError& e) + { + log<level::ERR>("Failed to fetch state property ", + entry("SERVICE=%s", service.c_str()), + entry("OBJECTPATH=%s", objPath.c_str()), + entry("INTERFACE=%s", session::sessionIntf), + entry("ERRMSG=%s", e.what())); + return ipmi::ccUnspecifiedError; + } + + return ipmi::ccSuccess; +} + +ipmi::RspType< + uint8_t, // session handle, + uint8_t, // total session count + uint8_t, // active session count + std::optional<std::tuple<uint8_t, // user ID + uint8_t, // privilege level + uint8_t, // channel number + uint32_t, // remote ip address, + std::array<uint8_t, macAddrLen>, // mac address + uint16_t // remote port + >>> + ipmiAppGetSessionInfo(ipmi::Context::ptr ctx, uint8_t sessionIndex, + ipmi::message::Payload& payload) +{ + uint32_t reqSessionId = 0; + uint8_t reqSessionHandle = session::defaultSessionHandle; + // initializing state to 0xff as 0 represents state as inactive. + uint8_t state = 0xFF; + + uint8_t completionCode = getSessionInfoRequestData( + ctx, sessionIndex, payload, reqSessionId, reqSessionHandle); + + if (completionCode) + { + return ipmi::response(completionCode); + } + struct GetSessionInfoRes res = {0}; + res.totalSessionCount = getTotalSessionCount(); + res.activeSessionCount = 0; + auto busp = getSdBus(); + + try + { + uint8_t index = 0; + ipmi::ObjectTree objectTree = ipmi::getAllDbusObjects( + *busp, session::sessionManagerRootPath, session::sessionIntf); + + for (auto& objectTreeItr : objectTree) { - // Output must have 16 byte capacity. - output = fullString.substr((chunkIndex * chunkSize) - 2, chunkSize); + uint32_t sessionId = 0; + uint8_t sessionHandle = session::defaultSessionHandle; + std::string objectPath = objectTreeItr.first; + + if (!parseCloseSessionInputPayload(objectPath, sessionId, + sessionHandle)) + { + continue; + } + index++; + auto& serviceMap = objectTreeItr.second; + auto itr = serviceMap.begin(); + + if (serviceMap.size() != 1) + { + return ipmi::responseUnspecifiedError(); + } + + std::string service = itr->first; + uint8_t sessionState = 0; + completionCode = + getSessionState(busp, service, objectPath, sessionState); + if (completionCode) + { + return ipmi::response(completionCode); + } + + if (sessionState == static_cast<uint8_t>(session::State::active)) + { + res.activeSessionCount++; + } + + if (index != sessionIndex && reqSessionId != sessionId && + reqSessionHandle != sessionHandle) + { + continue; + } + + completionCode = + fillGetSessionInfoRes(busp, service, objectPath, res, state); + + if (completionCode) + { + return ipmi::response(completionCode); + } } + } + + catch (sdbusplus::exception::SdBusError& e) + { + log<level::ERR>("Failed to fetch object from dbus", + entry("INTERFACE=%s", session::sessionIntf), + entry("ERRMSG=%s", e.what())); + return ipmi::responseUnspecifiedError(); + } - std::memcpy(chunk, output.c_str(), output.length()); - return output.length(); + if (state == static_cast<uint8_t>(session::State::active)) + { + return ipmi::responseSuccess( + res.sessionHandle, res.totalSessionCount, res.activeSessionCount, + std::make_tuple(res.userID, res.privLevel, res.channelNumber, + res.remoteIpAddr, res.macAddr, res.remotePort)); } - catch (const std::out_of_range& e) + else if (state == static_cast<uint8_t>(session::State::tearDownInProgress)) { - // The position was beyond the end. - return -EINVAL; + res.sessionHandle = 0; + return ipmi::responseSuccess(res.sessionHandle, res.totalSessionCount, + res.activeSessionCount, std::nullopt); } + + return ipmi::responseInvalidFieldRequest(); } -/** - * Packs the Get Sys Info Request Item into the response. - * - * @param[in] paramString - the parameter. - * @param[in] setSelector - the selector - * @param[in,out] resp - the System info response. - * @return The number of bytes packed or failure from splitStringParam(). - */ -static int packGetSysInfoResp(const std::string& paramString, - uint8_t setSelector, IpmiSysInfoResp* resp) +static std::unique_ptr<SysInfoParamStore> sysInfoParamStore; + +static std::string sysInfoReadSystemName() { - uint8_t* dataBuffer = resp->stringDataN; - resp->setSelector = setSelector; - if (resp->setSelector == 0) // First chunk has only 14 bytes. + // Use the BMC hostname as the "System Name." + char hostname[HOST_NAME_MAX + 1] = {}; + if (gethostname(hostname, HOST_NAME_MAX) != 0) { - resp->encoding = 0; - resp->stringLen = paramString.length(); - dataBuffer = resp->stringData0; + perror("System info parameter: system name"); } - return splitStringParam(paramString, resp->setSelector, dataBuffer); + return hostname; } -ipmi_ret_t ipmi_app_get_system_info(ipmi_netfn_t netfn, ipmi_cmd_t cmd, - ipmi_request_t request, - ipmi_response_t response, - ipmi_data_len_t dataLen, - ipmi_context_t context) +static constexpr uint8_t revisionOnly = 0x80; +static constexpr uint8_t paramRevision = 0x11; +static constexpr size_t configParameterLength = 16; + +static constexpr size_t smallChunkSize = 14; +static constexpr size_t fullChunkSize = 16; +static constexpr uint8_t progressMask = 0x3; + +static constexpr uint8_t setComplete = 0x0; +static constexpr uint8_t setInProgress = 0x1; +static constexpr uint8_t commitWrite = 0x2; +static uint8_t transferStatus = setComplete; + +static constexpr uint8_t configDataOverhead = 2; + +// For EFI based system, 256 bytes is recommended. +static constexpr size_t maxBytesPerParameter = 256; + +namespace ipmi { - IpmiSysInfoResp resp = {}; - size_t respLen = 0; - uint8_t* const reqData = static_cast<uint8_t*>(request); - std::string paramString; - bool found; - std::tuple<bool, std::string> ret; - constexpr int minRequestSize = 4; - constexpr int paramSelector = 1; - constexpr uint8_t revisionOnly = 0x80; - const uint8_t paramRequested = reqData[paramSelector]; - int rc; +constexpr Cc ccParmNotSupported = 0x80; +constexpr Cc ccSetInProgressActive = 0x81; +constexpr Cc ccSystemInfoParameterSetReadOnly = 0x82; - if (*dataLen < minRequestSize) +static inline auto responseParmNotSupported() +{ + return response(ccParmNotSupported); +} +static inline auto responseSetInProgressActive() +{ + return response(ccSetInProgressActive); +} +static inline auto responseSystemInfoParameterSetReadOnly() +{ + return response(ccSystemInfoParameterSetReadOnly); +} +} // namespace ipmi + +ipmi::RspType<uint8_t, // Parameter revision + std::optional<uint8_t>, // data1 / setSelector / ProgressStatus + std::optional<std::vector<uint8_t>>> // data2-17 + ipmiAppGetSystemInfo(uint8_t getRevision, uint8_t paramSelector, + uint8_t setSelector, uint8_t BlockSelector) +{ + if (getRevision & revisionOnly) { - return IPMI_CC_REQ_DATA_LEN_INVALID; + return ipmi::responseSuccess(paramRevision, std::nullopt, std::nullopt); } - *dataLen = 0; // default to 0. - - // Parameters revision as of IPMI spec v2.0 rev. 1.1 (Feb 11, 2014 E6) - resp.paramRevision = 0x11; - if (reqData[0] & revisionOnly) // Get parameter revision only + if (paramSelector == 0) { - respLen = 1; - goto writeResponse; + return ipmi::responseSuccess(paramRevision, transferStatus, + std::nullopt); } - // The "Set In Progress" parameter can be used for rollback of parameter - // data and is not implemented. - if (paramRequested == 0) + if (BlockSelector != 0) // 00h if parameter does not require a block number { - resp.byteData = 0; - respLen = 2; - goto writeResponse; + return ipmi::responseParmNotSupported(); } if (sysInfoParamStore == nullptr) @@ -996,30 +1310,323 @@ ipmi_ret_t ipmi_app_get_system_info(ipmi_netfn_t netfn, ipmi_cmd_t cmd, } // Parameters other than Set In Progress are assumed to be strings. - ret = sysInfoParamStore->lookup(paramRequested); - found = std::get<0>(ret); - paramString = std::get<1>(ret); + std::tuple<bool, std::string> ret = + sysInfoParamStore->lookup(paramSelector); + bool found = std::get<0>(ret); if (!found) { - return IPMI_CC_SYSTEM_INFO_PARAMETER_NOT_SUPPORTED; + return ipmi::responseParmNotSupported(); + } + std::string& paramString = std::get<1>(ret); + std::vector<uint8_t> configData; + size_t count = 0; + if (setSelector == 0) + { // First chunk has only 14 bytes. + configData.emplace_back(0); // encoding + configData.emplace_back(paramString.length()); // string length + count = std::min(paramString.length(), smallChunkSize); + configData.resize(count + configDataOverhead); + std::copy_n(paramString.begin(), count, + configData.begin() + configDataOverhead); // 14 bytes thunk } - // TODO: Cache each parameter across multiple calls, until the whole string - // has been read out. Otherwise, it's possible for a parameter to change - // between requests for its chunks, returning chunks incoherent with each - // other. For now, the parameter store is simply required to have only - // idempotent callbacks. - rc = packGetSysInfoResp(paramString, reqData[2], &resp); - if (rc == -EINVAL) + else { - return IPMI_CC_RESPONSE_ERROR; + size_t offset = (setSelector * fullChunkSize) - configDataOverhead; + if (offset >= paramString.length()) + { + return ipmi::responseParmOutOfRange(); + } + count = std::min(paramString.length() - offset, fullChunkSize); + configData.resize(count); + std::copy_n(paramString.begin() + offset, count, + configData.begin()); // 16 bytes chunk + } + return ipmi::responseSuccess(paramRevision, setSelector, configData); +} + +ipmi::RspType<> ipmiAppSetSystemInfo(uint8_t paramSelector, uint8_t data1, + std::vector<uint8_t> configData) +{ + if (paramSelector == 0) + { + // attempt to set the 'set in progress' value (in parameter #0) + // when not in the set complete state. + if ((transferStatus != setComplete) && (data1 == setInProgress)) + { + return ipmi::responseSetInProgressActive(); + } + // only following 2 states are supported + if (data1 > setInProgress) + { + phosphor::logging::log<phosphor::logging::level::ERR>( + "illegal SetInProgress status"); + return ipmi::responseInvalidFieldRequest(); + } + + transferStatus = data1 & progressMask; + return ipmi::responseSuccess(); + } + + if (configData.size() > configParameterLength) + { + return ipmi::responseInvalidFieldRequest(); } - respLen = sizeof(resp); // Write entire string data chunk in response. + if (!sysInfoParamStore) + { + sysInfoParamStore = std::make_unique<SysInfoParamStore>(); + sysInfoParamStore->update(IPMI_SYSINFO_SYSTEM_NAME, + sysInfoReadSystemName); + } + + // lookup + std::tuple<bool, std::string> ret = + sysInfoParamStore->lookup(paramSelector); + bool found = std::get<0>(ret); + std::string& paramString = std::get<1>(ret); + if (!found) + { + // parameter does not exist. Init new + paramString = ""; + } -writeResponse: - std::memcpy(response, &resp, sizeof(resp)); - *dataLen = respLen; - return IPMI_CC_OK; + uint8_t setSelector = data1; + size_t count = 0; + if (setSelector == 0) // First chunk has only 14 bytes. + { + size_t stringLen = configData.at(1); // string length + // maxBytesPerParamter is 256. It will always be greater than stringLen + // (unit8_t) if maxBytes changes in future, then following line is + // needed. + // stringLen = std::min(stringLen, maxBytesPerParameter); + count = std::min(stringLen, smallChunkSize); + count = std::min(count, configData.size()); + paramString.resize(stringLen); // reserve space + std::copy_n(configData.begin() + configDataOverhead, count, + paramString.begin()); + } + else + { + size_t offset = (setSelector * fullChunkSize) - configDataOverhead; + if (offset >= paramString.length()) + { + return ipmi::responseParmOutOfRange(); + } + count = std::min(paramString.length() - offset, configData.size()); + std::copy_n(configData.begin(), count, paramString.begin() + offset); + } + sysInfoParamStore->update(paramSelector, paramString); + return ipmi::responseSuccess(); +} + +#ifdef ENABLE_I2C_WHITELIST_CHECK +inline std::vector<uint8_t> convertStringToData(const std::string& command) +{ + std::istringstream iss(command); + std::string token; + std::vector<uint8_t> dataValue; + while (std::getline(iss, token, ' ')) + { + dataValue.emplace_back( + static_cast<uint8_t>(std::stoul(token, nullptr, base_16))); + } + return dataValue; +} + +static bool populateI2CMasterWRWhitelist() +{ + nlohmann::json data = nullptr; + std::ifstream jsonFile(i2cMasterWRWhitelistFile); + + if (!jsonFile.good()) + { + log<level::WARNING>("i2c white list file not found!", + entry("FILE_NAME: %s", i2cMasterWRWhitelistFile)); + return false; + } + + try + { + data = nlohmann::json::parse(jsonFile, nullptr, false); + } + catch (nlohmann::json::parse_error& e) + { + log<level::ERR>("Corrupted i2c white list config file", + entry("FILE_NAME: %s", i2cMasterWRWhitelistFile), + entry("MSG: %s", e.what())); + return false; + } + + try + { + // Example JSON Structure format + // "filters": [ + // { + // "Description": "Allow full read - ignore first byte write value + // for 0x40 to 0x4F", + // "busId": "0x01", + // "slaveAddr": "0x40", + // "slaveAddrMask": "0x0F", + // "command": "0x00", + // "commandMask": "0xFF" + // }, + // { + // "Description": "Allow full read - first byte match 0x05 and + // ignore second byte", + // "busId": "0x01", + // "slaveAddr": "0x57", + // "slaveAddrMask": "0x00", + // "command": "0x05 0x00", + // "commandMask": "0x00 0xFF" + // },] + + nlohmann::json filters = data[filtersStr].get<nlohmann::json>(); + std::vector<i2cMasterWRWhitelist>& whitelist = getWRWhitelist(); + for (const auto& it : filters.items()) + { + nlohmann::json filter = it.value(); + if (filter.is_null()) + { + log<level::ERR>( + "Corrupted I2C master write read whitelist config file", + entry("FILE_NAME: %s", i2cMasterWRWhitelistFile)); + return false; + } + const std::vector<uint8_t>& writeData = + convertStringToData(filter[cmdStr].get<std::string>()); + const std::vector<uint8_t>& writeDataMask = + convertStringToData(filter[cmdMaskStr].get<std::string>()); + if (writeDataMask.size() != writeData.size()) + { + log<level::ERR>("I2C master write read whitelist filter " + "mismatch for command & mask size"); + return false; + } + whitelist.push_back( + {static_cast<uint8_t>(std::stoul( + filter[busIdStr].get<std::string>(), nullptr, base_16)), + static_cast<uint8_t>( + std::stoul(filter[slaveAddrStr].get<std::string>(), + nullptr, base_16)), + static_cast<uint8_t>( + std::stoul(filter[slaveAddrMaskStr].get<std::string>(), + nullptr, base_16)), + writeData, writeDataMask}); + } + if (whitelist.size() != filters.size()) + { + log<level::ERR>( + "I2C master write read whitelist filter size mismatch"); + return false; + } + } + catch (std::exception& e) + { + log<level::ERR>("I2C master write read whitelist unexpected exception", + entry("ERROR=%s", e.what())); + return false; + } + return true; +} + +static inline bool isWriteDataWhitelisted(const std::vector<uint8_t>& data, + const std::vector<uint8_t>& dataMask, + const std::vector<uint8_t>& writeData) +{ + std::vector<uint8_t> processedDataBuf(data.size()); + std::vector<uint8_t> processedReqBuf(dataMask.size()); + std::transform(writeData.begin(), writeData.end(), dataMask.begin(), + processedReqBuf.begin(), std::bit_or<uint8_t>()); + std::transform(data.begin(), data.end(), dataMask.begin(), + processedDataBuf.begin(), std::bit_or<uint8_t>()); + + return (processedDataBuf == processedReqBuf); +} + +static bool isCmdWhitelisted(uint8_t busId, uint8_t slaveAddr, + std::vector<uint8_t>& writeData) +{ + std::vector<i2cMasterWRWhitelist>& whiteList = getWRWhitelist(); + for (const auto& wlEntry : whiteList) + { + if ((busId == wlEntry.busId) && + ((slaveAddr | wlEntry.slaveAddrMask) == + (wlEntry.slaveAddr | wlEntry.slaveAddrMask))) + { + const std::vector<uint8_t>& dataMask = wlEntry.dataMask; + // Skip as no-match, if requested write data is more than the + // write data mask size + if (writeData.size() > dataMask.size()) + { + continue; + } + if (isWriteDataWhitelisted(wlEntry.data, dataMask, writeData)) + { + return true; + } + } + } + return false; +} +#else +static bool populateI2CMasterWRWhitelist() +{ + log<level::INFO>( + "I2C_WHITELIST_CHECK is disabled, do not populate whitelist"); + return true; +} +#endif // ENABLE_I2C_WHITELIST_CHECK + +/** @brief implements master write read IPMI command which can be used for + * low-level I2C/SMBus write, read or write-read access + * @param isPrivateBus -to indicate private bus usage + * @param busId - bus id + * @param channelNum - channel number + * @param reserved - skip 1 bit + * @param slaveAddr - slave address + * @param read count - number of bytes to be read + * @param writeData - data to be written + * + * @returns IPMI completion code plus response data + * - readData - i2c response data + */ +ipmi::RspType<std::vector<uint8_t>> + ipmiMasterWriteRead(bool isPrivateBus, uint3_t busId, uint4_t channelNum, + bool reserved, uint7_t slaveAddr, uint8_t readCount, + std::vector<uint8_t> writeData) +{ + if (readCount > maxIPMIWriteReadSize) + { + log<level::ERR>("Master write read command: Read count exceeds limit"); + return ipmi::responseParmOutOfRange(); + } + const size_t writeCount = writeData.size(); + if (!readCount && !writeCount) + { + log<level::ERR>("Master write read command: Read & write count are 0"); + return ipmi::responseInvalidFieldRequest(); + } +#ifdef ENABLE_I2C_WHITELIST_CHECK + if (!isCmdWhitelisted(static_cast<uint8_t>(busId), + static_cast<uint8_t>(slaveAddr), writeData)) + { + log<level::ERR>("Master write read request blocked!", + entry("BUS=%d", static_cast<uint8_t>(busId)), + entry("ADDR=0x%x", static_cast<uint8_t>(slaveAddr))); + return ipmi::responseInvalidFieldRequest(); + } +#endif // ENABLE_I2C_WHITELIST_CHECK + std::vector<uint8_t> readBuf(readCount); + std::string i2cBus = + "/dev/i2c-" + std::to_string(static_cast<uint8_t>(busId)); + + ipmi::Cc ret = ipmi::i2cWriteRead(i2cBus, static_cast<uint8_t>(slaveAddr), + writeData, readBuf); + if (ret != ipmi::ccSuccess) + { + return ipmi::response(ret); + } + return ipmi::responseSuccess(readBuf); } void register_netfn_app_functions() @@ -1039,13 +1646,23 @@ void register_netfn_app_functions() ipmi::app::cmdResetWatchdogTimer, ipmi::Privilege::Operator, ipmiAppResetWatchdogTimer); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, + ipmi::app::cmdGetSessionInfo, ipmi::Privilege::User, + ipmiAppGetSessionInfo); + // <Set Watchdog Timer> - ipmi_register_callback(NETFUN_APP, IPMI_CMD_SET_WD, NULL, - ipmi_app_watchdog_set, PRIVILEGE_OPERATOR); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, + ipmi::app::cmdSetWatchdogTimer, + ipmi::Privilege::Operator, ipmiSetWatchdogTimer); + + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, + ipmi::app::cmdCloseSession, ipmi::Privilege::Callback, + ipmiAppCloseSession); // <Get Watchdog Timer> - ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_WD, NULL, - ipmi_app_watchdog_get, PRIVILEGE_OPERATOR); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, + ipmi::app::cmdGetWatchdogTimer, ipmi::Privilege::User, + ipmiGetWatchdogTimer); // <Get Self Test Results> ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, @@ -1053,27 +1670,48 @@ void register_netfn_app_functions() ipmi::Privilege::User, ipmiAppGetSelfTestResults); // <Get Device GUID> - ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_DEVICE_GUID, NULL, - ipmi_app_get_device_guid, PRIVILEGE_USER); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, + ipmi::app::cmdGetDeviceGuid, ipmi::Privilege::User, + ipmiAppGetDeviceGuid); // <Set ACPI Power State> - ipmi_register_callback(NETFUN_APP, IPMI_CMD_SET_ACPI, NULL, - ipmi_app_set_acpi_power_state, PRIVILEGE_ADMIN); - + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, + ipmi::app::cmdSetAcpiPowerState, + ipmi::Privilege::Admin, ipmiSetAcpiPowerState); // <Get ACPI Power State> - ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_ACPI, NULL, - ipmi_app_get_acpi_power_state, PRIVILEGE_ADMIN); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, + ipmi::app::cmdGetAcpiPowerState, + ipmi::Privilege::User, ipmiGetAcpiPowerState); + + // Note: For security reason, this command will be registered only when + // there are proper I2C Master write read whitelist + if (populateI2CMasterWRWhitelist()) + { + // Note: For security reasons, registering master write read as admin + // privilege command, even though IPMI 2.0 specification allows it as + // operator privilege. + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, + ipmi::app::cmdMasterWriteRead, + ipmi::Privilege::Admin, ipmiMasterWriteRead); + } // <Get System GUID Command> - ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_SYS_GUID, NULL, - ipmi_app_get_sys_guid, PRIVILEGE_USER); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, + ipmi::app::cmdGetSystemGuid, ipmi::Privilege::User, + ipmiAppGetSystemGuid); // <Get Channel Cipher Suites Command> - ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_CHAN_CIPHER_SUITES, NULL, - getChannelCipherSuites, PRIVILEGE_CALLBACK); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, + ipmi::app::cmdGetChannelCipherSuites, + ipmi::Privilege::None, getChannelCipherSuites); // <Get System Info Command> - ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_SYSTEM_INFO, NULL, - ipmi_app_get_system_info, PRIVILEGE_USER); + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, + ipmi::app::cmdGetSystemInfoParameters, + ipmi::Privilege::User, ipmiAppGetSystemInfo); + // <Set System Info Command> + ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp, + ipmi::app::cmdSetSystemInfoParameters, + ipmi::Privilege::Admin, ipmiAppSetSystemInfo); return; } |