// Copyright (c) 2018 Intel Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #pragma once #include "app.hpp" #include "async_resp.hpp" #include "boost_formatters.hpp" #include "dbus_singleton.hpp" #include "dbus_utility.hpp" #include "http_request.hpp" #include "http_response.hpp" #include "json_formatters.hpp" #include "logging.hpp" #include "parsing.hpp" #include "routing.hpp" #include "str_utility.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // IWYU pragma: no_include // IWYU pragma: no_include // IWYU pragma: no_include // IWYU pragma: no_include // IWYU pragma: no_include // IWYU pragma: no_include // IWYU pragma: no_include // IWYU pragma: no_include namespace crow { namespace openbmc_mapper { const constexpr char* notFoundMsg = "404 Not Found"; const constexpr char* badReqMsg = "400 Bad Request"; const constexpr char* methodNotAllowedMsg = "405 Method Not Allowed"; const constexpr char* forbiddenMsg = "403 Forbidden"; const constexpr char* unsupportedMediaMsg = "415 Unsupported Media Type"; const constexpr char* methodFailedMsg = "500 Method Call Failed"; const constexpr char* methodOutputFailedMsg = "500 Method Output Error"; const constexpr char* notFoundDesc = "org.freedesktop.DBus.Error.FileNotFound: path or object not found"; const constexpr char* propNotFoundDesc = "The specified property cannot be found"; const constexpr char* noJsonDesc = "No JSON object could be decoded"; const constexpr char* invalidContentType = "Content-type header is missing or invalid"; const constexpr char* methodNotFoundDesc = "The specified method cannot be found"; const constexpr char* methodNotAllowedDesc = "Method not allowed"; const constexpr char* forbiddenPropDesc = "The specified property cannot be created"; const constexpr char* forbiddenResDesc = "The specified resource cannot be created"; inline bool validateFilename(const std::string& filename) { static std::regex validFilename(R"(^[\w\- ]+(\.?[\w\- ]*)$)"); return std::regex_match(filename, validFilename); } inline void setErrorResponse(crow::Response& res, boost::beast::http::status result, const std::string& desc, std::string_view msg) { res.result(result); res.jsonValue["data"]["description"] = desc; res.jsonValue["message"] = msg; res.jsonValue["status"] = "error"; } inline void introspectObjects(const std::string& processName, const std::string& objectPath, const std::shared_ptr& transaction) { if (transaction->res.jsonValue.is_null()) { transaction->res.jsonValue["status"] = "ok"; transaction->res.jsonValue["bus_name"] = processName; transaction->res.jsonValue["objects"] = nlohmann::json::array(); } crow::connections::systemBus->async_method_call( [transaction, processName{std::string(processName)}, objectPath{std::string(objectPath)}]( const boost::system::error_code& ec, const std::string& introspectXml) { if (ec) { BMCWEB_LOG_ERROR( "Introspect call failed with error: {} on process: {} path: {}", ec.message(), processName, objectPath); return; } nlohmann::json::object_t object; object["path"] = objectPath; transaction->res.jsonValue["objects"].emplace_back(std::move(object)); tinyxml2::XMLDocument doc; doc.Parse(introspectXml.c_str()); tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node"); if (pRoot == nullptr) { BMCWEB_LOG_ERROR("XML document failed to parse {} {}", processName, objectPath); } else { tinyxml2::XMLElement* node = pRoot->FirstChildElement("node"); while (node != nullptr) { const char* childPath = node->Attribute("name"); if (childPath != nullptr) { std::string newpath; if (objectPath != "/") { newpath += objectPath; } newpath += std::string("/") + childPath; // introspect the subobjects as well introspectObjects(processName, newpath, transaction); } node = node->NextSiblingElement("node"); } } }, processName, objectPath, "org.freedesktop.DBus.Introspectable", "Introspect"); } inline void getPropertiesForEnumerate( const std::string& objectPath, const std::string& service, const std::string& interface, const std::shared_ptr& asyncResp) { BMCWEB_LOG_DEBUG("getPropertiesForEnumerate {} {} {}", objectPath, service, interface); sdbusplus::asio::getAllProperties( *crow::connections::systemBus, service, objectPath, interface, [asyncResp, objectPath, service, interface](const boost::system::error_code& ec, const dbus::utility::DBusPropertiesMap& propertiesList) { if (ec) { BMCWEB_LOG_ERROR( "GetAll on path {} iface {} service {} failed with code {}", objectPath, interface, service, ec); return; } nlohmann::json& dataJson = asyncResp->res.jsonValue["data"]; nlohmann::json& objectJson = dataJson[objectPath]; if (objectJson.is_null()) { objectJson = nlohmann::json::object(); } for (const auto& [name, value] : propertiesList) { nlohmann::json& propertyJson = objectJson[name]; std::visit( [&propertyJson](auto&& val) { if constexpr (std::is_same_v, sdbusplus::message::unix_fd>) { propertyJson = val.fd; } else { propertyJson = val; } }, value); } }); } // Find any results that weren't picked up by ObjectManagers, to be // called after all ObjectManagers are searched for and called. inline void findRemainingObjectsForEnumerate( const std::string& objectPath, const std::shared_ptr& subtree, const std::shared_ptr& asyncResp) { BMCWEB_LOG_DEBUG("findRemainingObjectsForEnumerate"); const nlohmann::json& dataJson = asyncResp->res.jsonValue["data"]; for (const auto& [path, interface_map] : *subtree) { if (path == objectPath) { // An enumerate does not return the target path's properties continue; } if (dataJson.find(path) == dataJson.end()) { for (const auto& [service, interfaces] : interface_map) { for (const auto& interface : interfaces) { if (!interface.starts_with("org.freedesktop.DBus")) { getPropertiesForEnumerate(path, service, interface, asyncResp); } } } } } } struct InProgressEnumerateData { InProgressEnumerateData( const std::string& objectPathIn, const std::shared_ptr& asyncRespIn) : objectPath(objectPathIn), asyncResp(asyncRespIn) {} ~InProgressEnumerateData() { try { findRemainingObjectsForEnumerate(objectPath, subtree, asyncResp); } catch (...) { BMCWEB_LOG_CRITICAL( "findRemainingObjectsForEnumerate threw exception"); } } InProgressEnumerateData(const InProgressEnumerateData&) = delete; InProgressEnumerateData(InProgressEnumerateData&&) = delete; InProgressEnumerateData& operator=(const InProgressEnumerateData&) = delete; InProgressEnumerateData& operator=(InProgressEnumerateData&&) = delete; const std::string objectPath; std::shared_ptr subtree; std::shared_ptr asyncResp; }; inline void getManagedObjectsForEnumerate( const std::string& objectName, const std::string& objectManagerPath, const std::string& connectionName, const std::shared_ptr& transaction) { BMCWEB_LOG_DEBUG( "getManagedObjectsForEnumerate {} object_manager_path {} connection_name {}", objectName, objectManagerPath, connectionName); sdbusplus::message::object_path path(objectManagerPath); dbus::utility::getManagedObjects( connectionName, path, [transaction, objectName, connectionName](const boost::system::error_code& ec, const dbus::utility::ManagedObjectType& objects) { if (ec) { BMCWEB_LOG_ERROR( "GetManagedObjects on path {} on connection {} failed with code {}", objectName, connectionName, ec); return; } nlohmann::json& dataJson = transaction->asyncResp->res.jsonValue["data"]; for (const auto& objectPath : objects) { if (objectPath.first.str.starts_with(objectName)) { BMCWEB_LOG_DEBUG("Reading object {}", objectPath.first.str); nlohmann::json& objectJson = dataJson[objectPath.first.str]; if (objectJson.is_null()) { objectJson = nlohmann::json::object(); } for (const auto& interface : objectPath.second) { for (const auto& property : interface.second) { nlohmann::json& propertyJson = objectJson[property.first]; std::visit( [&propertyJson](auto&& val) { if constexpr (std::is_same_v< std::decay_t, sdbusplus::message::unix_fd>) { propertyJson = val.fd; } else { propertyJson = val; } }, property.second); } } } for (const auto& interface : objectPath.second) { if (interface.first == "org.freedesktop.DBus.ObjectManager") { getManagedObjectsForEnumerate(objectPath.first.str, objectPath.first.str, connectionName, transaction); } } } }); } inline void findObjectManagerPathForEnumerate( const std::string& objectName, const std::string& connectionName, const std::shared_ptr& transaction) { BMCWEB_LOG_DEBUG("Finding objectmanager for path {} on connection:{}", objectName, connectionName); crow::connections::systemBus->async_method_call( [transaction, objectName, connectionName]( const boost::system::error_code& ec, const dbus::utility::MapperGetAncestorsResponse& objects) { if (ec) { BMCWEB_LOG_ERROR("GetAncestors on path {} failed with code {}", objectName, ec); return; } for (const auto& pathGroup : objects) { for (const auto& connectionGroup : pathGroup.second) { if (connectionGroup.first == connectionName) { // Found the object manager path for this resource. getManagedObjectsForEnumerate(objectName, pathGroup.first, connectionName, transaction); return; } } } }, "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetAncestors", objectName, std::array{"org.freedesktop.DBus.ObjectManager"}); } // Uses GetObject to add the object info about the target /enumerate path to // the results of GetSubTree, as GetSubTree will not return info for the // target path, and then continues on enumerating the rest of the tree. inline void getObjectAndEnumerate( const std::shared_ptr& transaction) { dbus::utility::getDbusObject( transaction->objectPath, {}, [transaction](const boost::system::error_code& ec, const dbus::utility::MapperGetObject& objects) { if (ec) { BMCWEB_LOG_ERROR("GetObject for path {} failed with code {}", transaction->objectPath, ec); return; } BMCWEB_LOG_DEBUG("GetObject for {} has {} entries", transaction->objectPath, objects.size()); if (!objects.empty()) { transaction->subtree->emplace_back(transaction->objectPath, objects); } // Map indicating connection name, and the path where the object // manager exists boost::container::flat_map< std::string, std::string, std::less<>, std::vector>> connections; for (const auto& object : *(transaction->subtree)) { for (const auto& connection : object.second) { for (const auto& interface : connection.second) { BMCWEB_LOG_DEBUG("{} has interface {}", connection.first, interface); if (interface == "org.freedesktop.DBus.ObjectManager") { BMCWEB_LOG_DEBUG("found object manager path {}", object.first); connections[connection.first] = object.first; } } } } BMCWEB_LOG_DEBUG("Got {} connections", connections.size()); for (const auto& connection : connections) { // If we already know where the object manager is, we don't // need to search for it, we can call directly in to // getManagedObjects if (!connection.second.empty()) { getManagedObjectsForEnumerate(transaction->objectPath, connection.second, connection.first, transaction); } else { // otherwise we need to find the object manager path // before we can continue findObjectManagerPathForEnumerate( transaction->objectPath, connection.first, transaction); } } }); } // Structure for storing data on an in progress action struct InProgressActionData { explicit InProgressActionData( const std::shared_ptr& res) : asyncResp(res) {} ~InProgressActionData() { // Methods could have been called across different owners // and interfaces, where some calls failed and some passed. // // The rules for this are: // * if no method was called - error // * if a method failed and none passed - error // (converse: if at least one method passed - OK) // * for the method output: // * if output processing didn't fail, return the data // Only deal with method returns if nothing failed earlier if (asyncResp->res.result() == boost::beast::http::status::ok) { if (!methodPassed) { if (!methodFailed) { setErrorResponse(asyncResp->res, boost::beast::http::status::not_found, methodNotFoundDesc, notFoundMsg); } } else { if (outputFailed) { setErrorResponse( asyncResp->res, boost::beast::http::status::internal_server_error, "Method output failure", methodOutputFailedMsg); } else { asyncResp->res.jsonValue["status"] = "ok"; asyncResp->res.jsonValue["message"] = "200 OK"; asyncResp->res.jsonValue["data"] = methodResponse; } } } } InProgressActionData(const InProgressActionData&) = delete; InProgressActionData(InProgressActionData&&) = delete; InProgressActionData& operator=(const InProgressActionData&) = delete; InProgressActionData& operator=(InProgressActionData&&) = delete; void setErrorStatus(const std::string& desc) { setErrorResponse(asyncResp->res, boost::beast::http::status::bad_request, desc, badReqMsg); } std::shared_ptr asyncResp; std::string path; std::string methodName; std::string interfaceName; bool methodPassed = false; bool methodFailed = false; bool outputFailed = false; bool convertedToArray = false; nlohmann::json methodResponse; nlohmann::json arguments; }; inline std::vector dbusArgSplit(const std::string& string) { std::vector ret; if (string.empty()) { return ret; } ret.emplace_back(""); int containerDepth = 0; for (std::string::const_iterator character = string.begin(); character != string.end(); character++) { ret.back() += *character; switch (*character) { case ('a'): break; case ('('): case ('{'): containerDepth++; break; case ('}'): case (')'): containerDepth--; if (containerDepth == 0) { if (character + 1 != string.end()) { ret.emplace_back(""); } } break; default: if (containerDepth == 0) { if (character + 1 != string.end()) { ret.emplace_back(""); } } break; } } return ret; } inline int convertJsonToDbus(sd_bus_message* m, const std::string& argType, const nlohmann::json& inputJson) { int r = 0; BMCWEB_LOG_DEBUG("Converting {} to type: {}", inputJson, argType); const std::vector argTypes = dbusArgSplit(argType); // Assume a single object for now. const nlohmann::json* j = &inputJson; nlohmann::json::const_iterator jIt = inputJson.begin(); for (const std::string& argCode : argTypes) { // If we are decoding multiple objects, grab the pointer to the // iterator, and increment it for the next loop if (argTypes.size() > 1) { if (jIt == inputJson.end()) { return -2; } j = &*jIt; jIt++; } const int64_t* intValue = j->get_ptr(); const std::string* stringValue = j->get_ptr(); const double* doubleValue = j->get_ptr(); const bool* b = j->get_ptr(); int64_t v = 0; double d = 0.0; // Do some basic type conversions that make sense. uint can be // converted to int. int and uint can be converted to double if (intValue == nullptr) { const uint64_t* uintValue = j->get_ptr(); if (uintValue != nullptr) { v = static_cast(*uintValue); intValue = &v; } } if (doubleValue == nullptr) { const uint64_t* uintValue = j->get_ptr(); if (uintValue != nullptr) { d = static_cast(*uintValue); doubleValue = &d; } } if (doubleValue == nullptr) { if (intValue != nullptr) { d = static_cast(*intValue); doubleValue = &d; } } if (argCode == "s") { if (stringValue == nullptr) { return -1; } r = sd_bus_message_append_basic( m, argCode[0], static_cast(stringValue->data())); if (r < 0) { return r; } } else if (argCode == "i") { if (intValue == nullptr) { return -1; } if ((*intValue < std::numeric_limits::lowest()) || (*intValue > std::numeric_limits::max())) { return -ERANGE; } int32_t i = static_cast(*intValue); r = sd_bus_message_append_basic(m, argCode[0], &i); if (r < 0) { return r; } } else if (argCode == "b") { // lots of ways bool could be represented here. Try them all int boolInt = 0; if (intValue != nullptr) { if (*intValue == 1) { boolInt = 1; } else if (*intValue == 0) { boolInt = 0; } else { return -ERANGE; } } else if (b != nullptr) { boolInt = *b ? 1 : 0; } else if (stringValue != nullptr) { if (!stringValue->empty()) { if (stringValue->front() == 't' || stringValue->front() == 'T') { boolInt = 1; } } } else { return -1; } r = sd_bus_message_append_basic(m, argCode[0], &boolInt); if (r < 0) { return r; } } else if (argCode == "n") { if (intValue == nullptr) { return -1; } if ((*intValue < std::numeric_limits::lowest()) || (*intValue > std::numeric_limits::max())) { return -ERANGE; } int16_t n = static_cast(*intValue); r = sd_bus_message_append_basic(m, argCode[0], &n); if (r < 0) { return r; } } else if (argCode == "x") { if (intValue == nullptr) { return -1; } r = sd_bus_message_append_basic(m, argCode[0], intValue); if (r < 0) { return r; } } else if (argCode == "y") { const uint64_t* uintValue = j->get_ptr(); if (uintValue == nullptr) { return -1; } if (*uintValue > std::numeric_limits::max()) { return -ERANGE; } uint8_t y = static_cast(*uintValue); r = sd_bus_message_append_basic(m, argCode[0], &y); } else if (argCode == "q") { const uint64_t* uintValue = j->get_ptr(); if (uintValue == nullptr) { return -1; } if (*uintValue > std::numeric_limits::max()) { return -ERANGE; } uint16_t q = static_cast(*uintValue); r = sd_bus_message_append_basic(m, argCode[0], &q); } else if (argCode == "u") { const uint64_t* uintValue = j->get_ptr(); if (uintValue == nullptr) { return -1; } if (*uintValue > std::numeric_limits::max()) { return -ERANGE; } uint32_t u = static_cast(*uintValue); r = sd_bus_message_append_basic(m, argCode[0], &u); } else if (argCode == "t") { const uint64_t* uintValue = j->get_ptr(); if (uintValue == nullptr) { return -1; } r = sd_bus_message_append_basic(m, argCode[0], uintValue); } else if (argCode == "d") { if (doubleValue == nullptr) { return -1; } if ((*doubleValue < std::numeric_limits::lowest()) || (*doubleValue > std::numeric_limits::max())) { return -ERANGE; } r = sd_bus_message_append_basic(m, argCode[0], doubleValue); if (r < 0) { return r; } } else if (argCode.starts_with("a")) { std::string containedType = argCode.substr(1); r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, containedType.c_str()); if (r < 0) { return r; } for (const auto& it : *j) { r = convertJsonToDbus(m, containedType, it); if (r < 0) { return r; } } sd_bus_message_close_container(m); } else if (argCode.starts_with("v")) { std::string containedType = argCode.substr(1); BMCWEB_LOG_DEBUG("variant type: {} appending variant of type: {}", argCode, containedType); r = sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT, containedType.c_str()); if (r < 0) { return r; } r = convertJsonToDbus(m, containedType, inputJson); if (r < 0) { return r; } r = sd_bus_message_close_container(m); if (r < 0) { return r; } } else if (argCode.starts_with("(") && argCode.ends_with(")")) { std::string containedType = argCode.substr(1, argCode.size() - 2); r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, containedType.c_str()); if (r < 0) { return r; } nlohmann::json::const_iterator it = j->begin(); for (const std::string& argCode2 : dbusArgSplit(containedType)) { if (it == j->end()) { return -1; } r = convertJsonToDbus(m, argCode2, *it); if (r < 0) { return r; } it++; } r = sd_bus_message_close_container(m); } else if (argCode.starts_with("{") && argCode.ends_with("}")) { std::string containedType = argCode.substr(1, argCode.size() - 2); r = sd_bus_message_open_container(m, SD_BUS_TYPE_DICT_ENTRY, containedType.c_str()); if (r < 0) { return r; } std::vector codes = dbusArgSplit(containedType); if (codes.size() != 2) { return -1; } const std::string& keyType = codes[0]; const std::string& valueType = codes[1]; const nlohmann::json::object_t* arr = j->get_ptr(); if (arr == nullptr) { return -1; } for (const auto& it : *arr) { r = convertJsonToDbus(m, keyType, it.first); if (r < 0) { return r; } r = convertJsonToDbus(m, valueType, it.second); if (r < 0) { return r; } } r = sd_bus_message_close_container(m); } else { return -2; } if (r < 0) { return r; } if (argTypes.size() > 1) { jIt++; } } return r; } template int readMessageItem(const std::string& typeCode, sdbusplus::message_t& m, nlohmann::json& data) { T value; // When T == char*, this warning fires. Unclear how to resolve // Given that sd-bus takes a void pointer to a char*, and that's // Not something we can fix. // NOLINTNEXTLINE(bugprone-multi-level-implicit-pointer-conversion) int r = sd_bus_message_read_basic(m.get(), typeCode.front(), &value); if (r < 0) { BMCWEB_LOG_ERROR("sd_bus_message_read_basic on type {} failed!", typeCode); return r; } data = value; return 0; } int convertDBusToJSON(const std::string& returnType, sdbusplus::message_t& m, nlohmann::json& response); inline int readDictEntryFromMessage(const std::string& typeCode, sdbusplus::message_t& m, nlohmann::json& object) { std::vector types = dbusArgSplit(typeCode); if (types.size() != 2) { BMCWEB_LOG_ERROR("wrong number contained types in dictionary: {}", types.size()); return -1; } int r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_DICT_ENTRY, typeCode.c_str()); if (r < 0) { BMCWEB_LOG_ERROR("sd_bus_message_enter_container with rc {}", r); return r; } nlohmann::json key; r = convertDBusToJSON(types[0], m, key); if (r < 0) { return r; } const std::string* keyPtr = key.get_ptr(); if (keyPtr == nullptr) { // json doesn't support non-string keys. If we hit this condition, // convert the result to a string so we can proceed key = key.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); keyPtr = key.get_ptr(); // in theory this can't fail now, but lets be paranoid about it // anyway if (keyPtr == nullptr) { return -1; } } nlohmann::json& value = object[*keyPtr]; r = convertDBusToJSON(types[1], m, value); if (r < 0) { return r; } r = sd_bus_message_exit_container(m.get()); if (r < 0) { BMCWEB_LOG_ERROR("sd_bus_message_exit_container failed"); return r; } return 0; } inline int readArrayFromMessage(const std::string& typeCode, sdbusplus::message_t& m, nlohmann::json& data) { if (typeCode.size() < 2) { BMCWEB_LOG_ERROR("Type code {} too small for an array", typeCode); return -1; } std::string containedType = typeCode.substr(1); int r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_ARRAY, containedType.c_str()); if (r < 0) { BMCWEB_LOG_ERROR("sd_bus_message_enter_container failed with rc {}", r); return r; } bool dict = containedType.starts_with("{") && containedType.ends_with("}"); if (dict) { // Remove the { } containedType = containedType.substr(1, containedType.size() - 2); data = nlohmann::json::object(); } else { data = nlohmann::json::array(); } while (true) { r = sd_bus_message_at_end(m.get(), 0); if (r < 0) { BMCWEB_LOG_ERROR("sd_bus_message_at_end failed"); return r; } if (r > 0) { break; } // Dictionaries are only ever seen in an array if (dict) { r = readDictEntryFromMessage(containedType, m, data); if (r < 0) { return r; } } else { data.push_back(nlohmann::json()); r = convertDBusToJSON(containedType, m, data.back()); if (r < 0) { return r; } } } r = sd_bus_message_exit_container(m.get()); if (r < 0) { BMCWEB_LOG_ERROR("sd_bus_message_exit_container failed"); return r; } return 0; } inline int readStructFromMessage(const std::string& typeCode, sdbusplus::message_t& m, nlohmann::json& data) { if (typeCode.size() < 3) { BMCWEB_LOG_ERROR("Type code {} too small for a struct", typeCode); return -1; } std::string containedTypes = typeCode.substr(1, typeCode.size() - 2); std::vector types = dbusArgSplit(containedTypes); int r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_STRUCT, containedTypes.c_str()); if (r < 0) { BMCWEB_LOG_ERROR("sd_bus_message_enter_container failed with rc {}", r); return r; } for (const std::string& type : types) { data.push_back(nlohmann::json()); r = convertDBusToJSON(type, m, data.back()); if (r < 0) { return r; } } r = sd_bus_message_exit_container(m.get()); if (r < 0) { BMCWEB_LOG_ERROR("sd_bus_message_exit_container failed"); return r; } return 0; } inline int readVariantFromMessage(sdbusplus::message_t& m, nlohmann::json& data) { const char* containerType = nullptr; int r = sd_bus_message_peek_type(m.get(), nullptr, &containerType); if (r < 0) { BMCWEB_LOG_ERROR("sd_bus_message_peek_type failed"); return r; } r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_VARIANT, containerType); if (r < 0) { BMCWEB_LOG_ERROR("sd_bus_message_enter_container failed with rc {}", r); return r; } r = convertDBusToJSON(containerType, m, data); if (r < 0) { return r; } r = sd_bus_message_exit_container(m.get()); if (r < 0) { BMCWEB_LOG_ERROR("sd_bus_message_enter_container failed"); return r; } return 0; } inline int convertDBusToJSON(const std::string& returnType, sdbusplus::message_t& m, nlohmann::json& response) { int r = 0; const std::vector returnTypes = dbusArgSplit(returnType); for (const std::string& typeCode : returnTypes) { nlohmann::json* thisElement = &response; if (returnTypes.size() > 1) { response.push_back(nlohmann::json{}); thisElement = &response.back(); } if (typeCode == "s" || typeCode == "g" || typeCode == "o") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode == "b") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } *thisElement = static_cast(thisElement->get()); } else if (typeCode == "u") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode == "i") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode == "x") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode == "t") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode == "n") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode == "q") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode == "y") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode == "d") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode == "h") { r = readMessageItem(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode.starts_with("a")) { r = readArrayFromMessage(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode.starts_with("(") && typeCode.ends_with(")")) { r = readStructFromMessage(typeCode, m, *thisElement); if (r < 0) { return r; } } else if (typeCode.starts_with("v")) { r = readVariantFromMessage(m, *thisElement); if (r < 0) { return r; } } else { BMCWEB_LOG_ERROR("Invalid D-Bus signature type {}", typeCode); return -2; } } return 0; } inline void handleMethodResponse( const std::shared_ptr& transaction, sdbusplus::message_t& m, const std::string& returnType) { nlohmann::json data; int r = convertDBusToJSON(returnType, m, data); if (r < 0) { transaction->outputFailed = true; return; } if (data.is_null()) { return; } if (transaction->methodResponse.is_null()) { transaction->methodResponse = std::move(data); return; } // If they're both dictionaries or arrays, merge into one. // Otherwise, make the results an array with every result // an entry. Could also just fail in that case, but it // seems better to get the data back somehow. nlohmann::json::object_t* dataobj = data.get_ptr(); if (transaction->methodResponse.is_object() && dataobj != nullptr) { for (auto& obj : *dataobj) { // Note: Will overwrite the data for a duplicate key transaction->methodResponse.emplace(obj.first, std::move(obj.second)); } return; } nlohmann::json::array_t* dataarr = data.get_ptr(); if (transaction->methodResponse.is_array() && dataarr != nullptr) { for (auto& obj : *dataarr) { transaction->methodResponse.emplace_back(std::move(obj)); } return; } if (!transaction->convertedToArray) { // They are different types. May as well turn them into an array nlohmann::json j = std::move(transaction->methodResponse); transaction->methodResponse = nlohmann::json::array(); transaction->methodResponse.emplace_back(std::move(j)); transaction->methodResponse.emplace_back(std::move(data)); transaction->convertedToArray = true; } else { transaction->methodResponse.emplace_back(std::move(data)); } } inline void findActionOnInterface( const std::shared_ptr& transaction, const std::string& connectionName) { BMCWEB_LOG_DEBUG("findActionOnInterface for connection {}", connectionName); crow::connections::systemBus->async_method_call( [transaction, connectionName{std::string(connectionName)}]( const boost::system::error_code& ec, const std::string& introspectXml) { BMCWEB_LOG_DEBUG("got xml:\n {}", introspectXml); if (ec) { BMCWEB_LOG_ERROR( "Introspect call failed with error: {} on process: {}", ec.message(), connectionName); return; } tinyxml2::XMLDocument doc; doc.Parse(introspectXml.data(), introspectXml.size()); tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node"); if (pRoot == nullptr) { BMCWEB_LOG_ERROR("XML document failed to parse {}", connectionName); return; } tinyxml2::XMLElement* interfaceNode = pRoot->FirstChildElement("interface"); while (interfaceNode != nullptr) { const char* thisInterfaceName = interfaceNode->Attribute("name"); if (thisInterfaceName != nullptr) { if (!transaction->interfaceName.empty() && (transaction->interfaceName != thisInterfaceName)) { interfaceNode = interfaceNode->NextSiblingElement("interface"); continue; } tinyxml2::XMLElement* methodNode = interfaceNode->FirstChildElement("method"); while (methodNode != nullptr) { const char* thisMethodName = methodNode->Attribute("name"); BMCWEB_LOG_DEBUG("Found method: {}", thisMethodName); if (thisMethodName != nullptr && thisMethodName == transaction->methodName) { BMCWEB_LOG_DEBUG( "Found method named {} on interface {}", thisMethodName, thisInterfaceName); sdbusplus::message_t m = crow::connections::systemBus->new_method_call( connectionName.c_str(), transaction->path.c_str(), thisInterfaceName, transaction->methodName.c_str()); tinyxml2::XMLElement* argumentNode = methodNode->FirstChildElement("arg"); std::string returnType; // Find the output type while (argumentNode != nullptr) { const char* argDirection = argumentNode->Attribute("direction"); const char* argType = argumentNode->Attribute("type"); if (argDirection != nullptr && argType != nullptr && std::string(argDirection) == "out") { returnType = argType; break; } argumentNode = argumentNode->NextSiblingElement("arg"); } auto argIt = transaction->arguments.begin(); argumentNode = methodNode->FirstChildElement("arg"); while (argumentNode != nullptr) { const char* argDirection = argumentNode->Attribute("direction"); const char* argType = argumentNode->Attribute("type"); if (argDirection != nullptr && argType != nullptr && std::string(argDirection) == "in") { if (argIt == transaction->arguments.end()) { transaction->setErrorStatus( "Invalid method args"); return; } if (convertJsonToDbus(m.get(), std::string(argType), *argIt) < 0) { transaction->setErrorStatus( "Invalid method arg type"); return; } argIt++; } argumentNode = argumentNode->NextSiblingElement("arg"); } crow::connections::systemBus->async_send( m, [transaction, returnType]( const boost::system::error_code& ec2, sdbusplus::message_t& m2) { if (ec2) { transaction->methodFailed = true; const sd_bus_error* e = m2.get_error(); if (e != nullptr) { setErrorResponse( transaction->asyncResp->res, boost::beast::http::status::bad_request, e->name, e->message); } else { setErrorResponse( transaction->asyncResp->res, boost::beast::http::status::bad_request, "Method call failed", methodFailedMsg); } return; } transaction->methodPassed = true; handleMethodResponse(transaction, m2, returnType); }); break; } methodNode = methodNode->NextSiblingElement("method"); } } interfaceNode = interfaceNode->NextSiblingElement("interface"); } }, connectionName, transaction->path, "org.freedesktop.DBus.Introspectable", "Introspect"); } inline void handleAction(const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& objectPath, const std::string& methodName) { BMCWEB_LOG_DEBUG("handleAction on path: {} and method {}", objectPath, methodName); nlohmann::json requestDbusData; JsonParseResult ret = parseRequestAsJson(req, requestDbusData); if (ret == JsonParseResult::BadContentType) { setErrorResponse(asyncResp->res, boost::beast::http::status::unsupported_media_type, invalidContentType, unsupportedMediaMsg); return; } if (ret != JsonParseResult::Success) { setErrorResponse(asyncResp->res, boost::beast::http::status::bad_request, noJsonDesc, badReqMsg); return; } nlohmann::json::iterator data = requestDbusData.find("data"); if (data == requestDbusData.end()) { setErrorResponse(asyncResp->res, boost::beast::http::status::bad_request, noJsonDesc, badReqMsg); return; } if (!data->is_array()) { setErrorResponse(asyncResp->res, boost::beast::http::status::bad_request, noJsonDesc, badReqMsg); return; } auto transaction = std::make_shared(asyncResp); transaction->path = objectPath; transaction->methodName = methodName; transaction->arguments = std::move(*data); dbus::utility::getDbusObject( objectPath, {}, [transaction]( const boost::system::error_code& ec, const std::vector>>& interfaceNames) { if (ec || interfaceNames.empty()) { BMCWEB_LOG_ERROR("Can't find object"); setErrorResponse(transaction->asyncResp->res, boost::beast::http::status::not_found, notFoundDesc, notFoundMsg); return; } BMCWEB_LOG_DEBUG("GetObject returned {} object(s)", interfaceNames.size()); for (const std::pair>& object : interfaceNames) { findActionOnInterface(transaction, object.first); } }); } inline void handleDelete(const std::shared_ptr& asyncResp, const std::string& objectPath) { BMCWEB_LOG_DEBUG("handleDelete on path: {}", objectPath); dbus::utility::getDbusObject( objectPath, {}, [asyncResp, objectPath]( const boost::system::error_code& ec, const std::vector>>& interfaceNames) { if (ec || interfaceNames.empty()) { BMCWEB_LOG_ERROR("Can't find object"); setErrorResponse(asyncResp->res, boost::beast::http::status::method_not_allowed, methodNotAllowedDesc, methodNotAllowedMsg); return; } auto transaction = std::make_shared(asyncResp); transaction->path = objectPath; transaction->methodName = "Delete"; transaction->interfaceName = "xyz.openbmc_project.Object.Delete"; for (const std::pair>& object : interfaceNames) { findActionOnInterface(transaction, object.first); } }); } inline void handleList(const std::shared_ptr& asyncResp, const std::string& objectPath, int32_t depth = 0) { dbus::utility::getSubTreePaths( objectPath, depth, {}, [asyncResp]( const boost::system::error_code& ec, const dbus::utility::MapperGetSubTreePathsResponse& objectPaths) { if (ec) { setErrorResponse(asyncResp->res, boost::beast::http::status::not_found, notFoundDesc, notFoundMsg); } else { asyncResp->res.jsonValue["status"] = "ok"; asyncResp->res.jsonValue["message"] = "200 OK"; asyncResp->res.jsonValue["data"] = objectPaths; } }); } inline void handleEnumerate(const std::shared_ptr& asyncResp, const std::string& objectPath) { BMCWEB_LOG_DEBUG("Doing enumerate on {}", objectPath); asyncResp->res.jsonValue["message"] = "200 OK"; asyncResp->res.jsonValue["status"] = "ok"; asyncResp->res.jsonValue["data"] = nlohmann::json::object(); dbus::utility::getSubTree( objectPath, 0, {}, [objectPath, asyncResp]( const boost::system::error_code& ec, const dbus::utility::MapperGetSubTreeResponse& objectNames) { auto transaction = std::make_shared(objectPath, asyncResp); transaction->subtree = std::make_shared( objectNames); if (ec) { BMCWEB_LOG_ERROR("GetSubTree failed on {}", transaction->objectPath); setErrorResponse(transaction->asyncResp->res, boost::beast::http::status::not_found, notFoundDesc, notFoundMsg); return; } // Add the data for the path passed in to the results // as if GetSubTree returned it, and continue on enumerating getObjectAndEnumerate(transaction); }); } inline void handleGet(const std::shared_ptr& asyncResp, std::string& objectPath, std::string& destProperty) { BMCWEB_LOG_DEBUG("handleGet: {} prop:{}", objectPath, destProperty); std::shared_ptr propertyName = std::make_shared(std::move(destProperty)); std::shared_ptr path = std::make_shared(std::move(objectPath)); dbus::utility::getDbusObject( *path, {}, [asyncResp, path, propertyName](const boost::system::error_code& ec, const dbus::utility::MapperGetObject& objectNames) { if (ec || objectNames.empty()) { setErrorResponse(asyncResp->res, boost::beast::http::status::not_found, notFoundDesc, notFoundMsg); return; } std::shared_ptr response = std::make_shared(nlohmann::json::object()); // The mapper should never give us an empty interface names // list, but check anyway for (const std::pair>& connection : objectNames) { const std::vector& interfaceNames = connection.second; if (interfaceNames.empty()) { setErrorResponse(asyncResp->res, boost::beast::http::status::not_found, notFoundDesc, notFoundMsg); return; } for (const std::string& interface : interfaceNames) { sdbusplus::message_t m = crow::connections::systemBus->new_method_call( connection.first.c_str(), path->c_str(), "org.freedesktop.DBus.Properties", "GetAll"); m.append(interface); crow::connections::systemBus->async_send( m, [asyncResp, response, propertyName](const boost::system::error_code& ec2, sdbusplus::message_t& msg) { if (ec2) { BMCWEB_LOG_ERROR("Bad dbus request error: {}", ec2); } else { nlohmann::json properties; int r = convertDBusToJSON("a{sv}", msg, properties); if (r < 0) { BMCWEB_LOG_ERROR("convertDBusToJSON failed"); } else { nlohmann::json::object_t* obj = properties.get_ptr(); if (obj == nullptr) { return; } for (auto& prop : *obj) { // if property name is empty, or // matches our search query, add it // to the response json if (propertyName->empty()) { (*response)[prop.first] = std::move(prop.second); } else if (prop.first == *propertyName) { *response = std::move(prop.second); } } } } if (response.use_count() == 1) { if (!propertyName->empty() && response->empty()) { setErrorResponse( asyncResp->res, boost::beast::http::status::not_found, propNotFoundDesc, notFoundMsg); } else { asyncResp->res.jsonValue["status"] = "ok"; asyncResp->res.jsonValue["message"] = "200 OK"; asyncResp->res.jsonValue["data"] = *response; } } }); } } }); } struct AsyncPutRequest { explicit AsyncPutRequest(const std::shared_ptr& resIn) : asyncResp(resIn) {} ~AsyncPutRequest() { if (asyncResp->res.jsonValue.empty()) { setErrorResponse(asyncResp->res, boost::beast::http::status::forbidden, forbiddenMsg, forbiddenPropDesc); } } AsyncPutRequest(const AsyncPutRequest&) = delete; AsyncPutRequest(AsyncPutRequest&&) = delete; AsyncPutRequest& operator=(const AsyncPutRequest&) = delete; AsyncPutRequest& operator=(AsyncPutRequest&&) = delete; void setErrorStatus(const std::string& desc) { setErrorResponse(asyncResp->res, boost::beast::http::status::internal_server_error, desc, badReqMsg); } const std::shared_ptr asyncResp; std::string objectPath; std::string propertyName; nlohmann::json propertyValue; }; inline void handlePut(const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& objectPath, const std::string& destProperty) { if (destProperty.empty()) { setErrorResponse(asyncResp->res, boost::beast::http::status::forbidden, forbiddenResDesc, forbiddenMsg); return; } nlohmann::json requestDbusData; JsonParseResult ret = parseRequestAsJson(req, requestDbusData); if (ret == JsonParseResult::BadContentType) { setErrorResponse(asyncResp->res, boost::beast::http::status::unsupported_media_type, invalidContentType, unsupportedMediaMsg); return; } if (ret != JsonParseResult::Success) { setErrorResponse(asyncResp->res, boost::beast::http::status::bad_request, noJsonDesc, badReqMsg); return; } auto propertyIt = requestDbusData.find("data"); if (propertyIt == requestDbusData.end()) { setErrorResponse(asyncResp->res, boost::beast::http::status::bad_request, noJsonDesc, badReqMsg); return; } const nlohmann::json& propertySetValue = *propertyIt; auto transaction = std::make_shared(asyncResp); transaction->objectPath = objectPath; transaction->propertyName = destProperty; transaction->propertyValue = propertySetValue; dbus::utility::getDbusObject( transaction->objectPath, {}, [transaction](const boost::system::error_code& ec2, const dbus::utility::MapperGetObject& objectNames) { if (!ec2 && objectNames.empty()) { setErrorResponse(transaction->asyncResp->res, boost::beast::http::status::not_found, propNotFoundDesc, notFoundMsg); return; } for (const std::pair>& connection : objectNames) { const std::string& connectionName = connection.first; crow::connections::systemBus->async_method_call( [connectionName{std::string(connectionName)}, transaction](const boost::system::error_code& ec3, const std::string& introspectXml) { if (ec3) { BMCWEB_LOG_ERROR( "Introspect call failed with error: {} on process: {}", ec3.message(), connectionName); transaction->setErrorStatus("Unexpected Error"); return; } tinyxml2::XMLDocument doc; doc.Parse(introspectXml.c_str()); tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node"); if (pRoot == nullptr) { BMCWEB_LOG_ERROR("XML document failed to parse: {}", introspectXml); transaction->setErrorStatus("Unexpected Error"); return; } tinyxml2::XMLElement* ifaceNode = pRoot->FirstChildElement("interface"); while (ifaceNode != nullptr) { const char* interfaceName = ifaceNode->Attribute("name"); BMCWEB_LOG_DEBUG("found interface {}", interfaceName); tinyxml2::XMLElement* propNode = ifaceNode->FirstChildElement("property"); while (propNode != nullptr) { const char* propertyName = propNode->Attribute("name"); if (propertyName == nullptr) { BMCWEB_LOG_DEBUG("Couldn't find name property"); continue; } BMCWEB_LOG_DEBUG("Found property {}", propertyName); if (propertyName == transaction->propertyName) { const char* argType = propNode->Attribute("type"); if (argType != nullptr) { sdbusplus::message_t m = crow::connections::systemBus ->new_method_call( connectionName.c_str(), transaction->objectPath.c_str(), "org.freedesktop.DBus." "Properties", "Set"); m.append(interfaceName, transaction->propertyName); int r = sd_bus_message_open_container( m.get(), SD_BUS_TYPE_VARIANT, argType); if (r < 0) { transaction->setErrorStatus( "Unexpected Error"); return; } r = convertJsonToDbus( m.get(), argType, transaction->propertyValue); if (r < 0) { if (r == -ERANGE) { transaction->setErrorStatus( "Provided property value " "is out of range for the " "property type"); } else { transaction->setErrorStatus( "Invalid arg type"); } return; } r = sd_bus_message_close_container(m.get()); if (r < 0) { transaction->setErrorStatus( "Unexpected Error"); return; } crow::connections::systemBus->async_send( m, [transaction]( const boost::system::error_code& ec, sdbusplus::message_t& m2) { BMCWEB_LOG_DEBUG("sent"); if (ec) { const sd_bus_error* e = m2.get_error(); setErrorResponse( transaction->asyncResp->res, boost::beast::http::status:: forbidden, (e) != nullptr ? e->name : ec.category().name(), (e) != nullptr ? e->message : ec.message()); } else { transaction->asyncResp->res .jsonValue["status"] = "ok"; transaction->asyncResp->res .jsonValue["message"] = "200 OK"; transaction->asyncResp->res .jsonValue["data"] = nullptr; } }); } } propNode = propNode->NextSiblingElement("property"); } ifaceNode = ifaceNode->NextSiblingElement("interface"); } }, connectionName, transaction->objectPath, "org.freedesktop.DBus.Introspectable", "Introspect"); } }); } inline void handleDBusUrl(const crow::Request& req, const std::shared_ptr& asyncResp, std::string& objectPath) { // If accessing a single attribute, fill in and update objectPath, // otherwise leave destProperty blank std::string destProperty; const char* attrSeperator = "/attr/"; size_t attrPosition = objectPath.find(attrSeperator); if (attrPosition != std::string::npos) { destProperty = objectPath.substr(attrPosition + strlen(attrSeperator), objectPath.length()); objectPath.resize(attrPosition); } if (req.method() == boost::beast::http::verb::post) { constexpr const char* actionSeperator = "/action/"; size_t actionPosition = objectPath.find(actionSeperator); if (actionPosition != std::string::npos) { std::string postProperty = objectPath.substr((actionPosition + strlen(actionSeperator)), objectPath.length()); objectPath.resize(actionPosition); handleAction(req, asyncResp, objectPath, postProperty); return; } } else if (req.method() == boost::beast::http::verb::get) { if (objectPath.ends_with("/enumerate")) { objectPath.erase(objectPath.end() - sizeof("enumerate"), objectPath.end()); handleEnumerate(asyncResp, objectPath); } else if (objectPath.ends_with("/list")) { objectPath.erase(objectPath.end() - sizeof("list"), objectPath.end()); handleList(asyncResp, objectPath); } else { // Trim any trailing "/" at the end if (objectPath.ends_with("/")) { objectPath.pop_back(); handleList(asyncResp, objectPath, 1); } else { handleGet(asyncResp, objectPath, destProperty); } } return; } else if (req.method() == boost::beast::http::verb::put) { handlePut(req, asyncResp, objectPath, destProperty); return; } else if (req.method() == boost::beast::http::verb::delete_) { handleDelete(asyncResp, objectPath); return; } setErrorResponse(asyncResp->res, boost::beast::http::status::method_not_allowed, methodNotAllowedDesc, methodNotAllowedMsg); } inline void handleBusSystemPost(const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& processName, const std::string& requestedPath) { std::vector strs; bmcweb::split(strs, requestedPath, '/'); std::string objectPath; std::string interfaceName; std::string methodName; auto it = strs.begin(); if (it == strs.end()) { objectPath = "/"; } while (it != strs.end()) { // Check if segment contains ".". If it does, it must be an // interface if (it->find(".") != std::string::npos) { break; // This check is necessary as the trailing slash gets // parsed as part of our specifier above, which // causes the normal trailing backslash redirector to // fail. } if (!it->empty()) { objectPath += "/" + *it; } it++; } if (it != strs.end()) { interfaceName = *it; it++; // after interface, we might have a method name if (it != strs.end()) { methodName = *it; it++; } } if (it != strs.end()) { // if there is more levels past the method name, something // went wrong, return not found asyncResp->res.result(boost::beast::http::status::not_found); return; } if (interfaceName.empty()) { crow::connections::systemBus->async_method_call( [asyncResp, processName, objectPath](const boost::system::error_code& ec, const std::string& introspectXml) { if (ec) { BMCWEB_LOG_ERROR( "Introspect call failed with error: {} on process: {} path: {}", ec.message(), processName, objectPath); return; } tinyxml2::XMLDocument doc; doc.Parse(introspectXml.c_str()); tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node"); if (pRoot == nullptr) { BMCWEB_LOG_ERROR("XML document failed to parse {} {}", processName, objectPath); asyncResp->res.jsonValue["status"] = "XML parse error"; asyncResp->res.result( boost::beast::http::status::internal_server_error); return; } BMCWEB_LOG_DEBUG("{}", introspectXml); asyncResp->res.jsonValue["status"] = "ok"; asyncResp->res.jsonValue["bus_name"] = processName; asyncResp->res.jsonValue["object_path"] = objectPath; nlohmann::json& interfacesArray = asyncResp->res.jsonValue["interfaces"]; interfacesArray = nlohmann::json::array(); tinyxml2::XMLElement* interface = pRoot->FirstChildElement("interface"); while (interface != nullptr) { const char* ifaceName = interface->Attribute("name"); if (ifaceName != nullptr) { nlohmann::json::object_t interfaceObj; interfaceObj["name"] = ifaceName; interfacesArray.emplace_back(std::move(interfaceObj)); } interface = interface->NextSiblingElement("interface"); } }, processName, objectPath, "org.freedesktop.DBus.Introspectable", "Introspect"); } else if (methodName.empty()) { crow::connections::systemBus->async_method_call( [asyncResp, processName, objectPath, interfaceName](const boost::system::error_code& ec, const std::string& introspectXml) { if (ec) { BMCWEB_LOG_ERROR( "Introspect call failed with error: {} on process: {} path: {}", ec.message(), processName, objectPath); return; } tinyxml2::XMLDocument doc; doc.Parse(introspectXml.data(), introspectXml.size()); tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node"); if (pRoot == nullptr) { BMCWEB_LOG_ERROR("XML document failed to parse {} {}", processName, objectPath); asyncResp->res.result( boost::beast::http::status::internal_server_error); return; } asyncResp->res.jsonValue["status"] = "ok"; asyncResp->res.jsonValue["bus_name"] = processName; asyncResp->res.jsonValue["interface"] = interfaceName; asyncResp->res.jsonValue["object_path"] = objectPath; nlohmann::json& methodsArray = asyncResp->res.jsonValue["methods"]; methodsArray = nlohmann::json::array(); nlohmann::json& signalsArray = asyncResp->res.jsonValue["signals"]; signalsArray = nlohmann::json::array(); nlohmann::json& propertiesObj = asyncResp->res.jsonValue["properties"]; propertiesObj = nlohmann::json::object(); // if we know we're the only call, build the // json directly tinyxml2::XMLElement* interface = pRoot->FirstChildElement("interface"); while (interface != nullptr) { const char* ifaceName = interface->Attribute("name"); if (ifaceName != nullptr && ifaceName == interfaceName) { break; } interface = interface->NextSiblingElement("interface"); } if (interface == nullptr) { // if we got to the end of the list and // never found a match, throw 404 asyncResp->res.result(boost::beast::http::status::not_found); return; } tinyxml2::XMLElement* methods = interface->FirstChildElement("method"); while (methods != nullptr) { nlohmann::json argsArray = nlohmann::json::array(); tinyxml2::XMLElement* arg = methods->FirstChildElement("arg"); while (arg != nullptr) { nlohmann::json thisArg; for (const char* fieldName : std::array{ "name", "direction", "type"}) { const char* fieldValue = arg->Attribute(fieldName); if (fieldValue != nullptr) { thisArg[fieldName] = fieldValue; } } argsArray.emplace_back(std::move(thisArg)); arg = arg->NextSiblingElement("arg"); } const char* name = methods->Attribute("name"); if (name != nullptr) { std::string uri; uri.reserve(14 + processName.size() + objectPath.size() + interfaceName.size() + strlen(name)); uri += "/bus/system/"; uri += processName; uri += objectPath; uri += "/"; uri += interfaceName; uri += "/"; uri += name; nlohmann::json::object_t object; object["name"] = name; object["uri"] = std::move(uri); object["args"] = argsArray; methodsArray.emplace_back(std::move(object)); } methods = methods->NextSiblingElement("method"); } tinyxml2::XMLElement* signals = interface->FirstChildElement("signal"); while (signals != nullptr) { nlohmann::json argsArray = nlohmann::json::array(); tinyxml2::XMLElement* arg = signals->FirstChildElement("arg"); while (arg != nullptr) { const char* name = arg->Attribute("name"); const char* type = arg->Attribute("type"); if (name != nullptr && type != nullptr) { nlohmann::json::object_t params; params["name"] = name; params["type"] = type; argsArray.push_back(std::move(params)); } arg = arg->NextSiblingElement("arg"); } const char* name = signals->Attribute("name"); if (name != nullptr) { nlohmann::json::object_t object; object["name"] = name; object["args"] = argsArray; signalsArray.emplace_back(std::move(object)); } signals = signals->NextSiblingElement("signal"); } tinyxml2::XMLElement* property = interface->FirstChildElement("property"); while (property != nullptr) { const char* name = property->Attribute("name"); const char* type = property->Attribute("type"); if (type != nullptr && name != nullptr) { sdbusplus::message_t m = crow::connections::systemBus->new_method_call( processName.c_str(), objectPath.c_str(), "org.freedesktop." "DBus." "Properties", "Get"); m.append(interfaceName, name); nlohmann::json& propertyItem = propertiesObj[name]; crow::connections::systemBus->async_send( m, [&propertyItem, asyncResp](const boost::system::error_code& ec2, sdbusplus::message_t& msg) { if (ec2) { return; } int r = convertDBusToJSON("v", msg, propertyItem); if (r < 0) { BMCWEB_LOG_ERROR("Couldn't convert vector to json"); } }); } property = property->NextSiblingElement("property"); } }, processName, objectPath, "org.freedesktop.DBus.Introspectable", "Introspect"); } else { if (req.method() != boost::beast::http::verb::post) { asyncResp->res.result(boost::beast::http::status::not_found); return; } nlohmann::json requestDbusData; JsonParseResult ret = parseRequestAsJson(req, requestDbusData); if (ret == JsonParseResult::BadContentType) { setErrorResponse(asyncResp->res, boost::beast::http::status::unsupported_media_type, invalidContentType, unsupportedMediaMsg); return; } if (ret != JsonParseResult::Success) { setErrorResponse(asyncResp->res, boost::beast::http::status::bad_request, noJsonDesc, badReqMsg); return; } if (!requestDbusData.is_array()) { asyncResp->res.result(boost::beast::http::status::bad_request); return; } auto transaction = std::make_shared(asyncResp); transaction->path = objectPath; transaction->methodName = methodName; transaction->arguments = std::move(requestDbusData); findActionOnInterface(transaction, processName); } } inline void requestRoutes(App& app) { BMCWEB_ROUTE(app, "/bus/") .privileges({{"Login"}}) .methods(boost::beast::http::verb::get)( [](const crow::Request&, const std::shared_ptr& asyncResp) { nlohmann::json::array_t buses; nlohmann::json& bus = buses.emplace_back(); bus["name"] = "system"; asyncResp->res.jsonValue["busses"] = std::move(buses); asyncResp->res.jsonValue["status"] = "ok"; }); BMCWEB_ROUTE(app, "/bus/system/") .privileges({{"Login"}}) .methods(boost::beast::http::verb::get)( [](const crow::Request&, const std::shared_ptr& asyncResp) { auto myCallback = [asyncResp](const boost::system::error_code& ec, std::vector& names) { if (ec) { BMCWEB_LOG_ERROR("Dbus call failed with code {}", ec); asyncResp->res.result( boost::beast::http::status::internal_server_error); } else { std::ranges::sort(names); asyncResp->res.jsonValue["status"] = "ok"; auto& objectsSub = asyncResp->res.jsonValue["objects"]; for (const auto& name : names) { nlohmann::json::object_t object; object["name"] = name; objectsSub.emplace_back(std::move(object)); } } }; crow::connections::systemBus->async_method_call( std::move(myCallback), "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "ListNames"); }); BMCWEB_ROUTE(app, "/list/") .privileges({{"Login"}}) .methods(boost::beast::http::verb::get)( [](const crow::Request&, const std::shared_ptr& asyncResp) { handleList(asyncResp, "/"); }); BMCWEB_ROUTE(app, "/xyz/") .privileges({{"Login"}}) .methods(boost::beast::http::verb::get)( [](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& path) { std::string objectPath = "/xyz/" + path; handleDBusUrl(req, asyncResp, objectPath); }); BMCWEB_ROUTE(app, "/xyz/") .privileges({{"ConfigureComponents", "ConfigureManager"}}) .methods(boost::beast::http::verb::put, boost::beast::http::verb::post, boost::beast::http::verb::delete_)( [](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& path) { std::string objectPath = "/xyz/" + path; handleDBusUrl(req, asyncResp, objectPath); }); BMCWEB_ROUTE(app, "/org/") .privileges({{"Login"}}) .methods(boost::beast::http::verb::get)( [](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& path) { std::string objectPath = "/org/" + path; handleDBusUrl(req, asyncResp, objectPath); }); BMCWEB_ROUTE(app, "/org/") .privileges({{"ConfigureComponents", "ConfigureManager"}}) .methods(boost::beast::http::verb::put, boost::beast::http::verb::post, boost::beast::http::verb::delete_)( [](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& path) { std::string objectPath = "/org/" + path; handleDBusUrl(req, asyncResp, objectPath); }); BMCWEB_ROUTE(app, "/download/dump//") .privileges({{"ConfigureManager"}}) .methods(boost::beast::http::verb::get)( [](const crow::Request&, const std::shared_ptr& asyncResp, const std::string& dumpId) { if (!validateFilename(dumpId)) { asyncResp->res.result(boost::beast::http::status::bad_request); return; } std::filesystem::path loc("/var/lib/phosphor-debug-collector/dumps"); loc /= dumpId; if (!std::filesystem::exists(loc) || !std::filesystem::is_directory(loc)) { BMCWEB_LOG_ERROR("{}Not found", loc.string()); asyncResp->res.result(boost::beast::http::status::not_found); return; } std::filesystem::directory_iterator files(loc); for (const auto& file : files) { if (!asyncResp->res.openFile(file)) { continue; } asyncResp->res.addHeader(boost::beast::http::field::content_type, "application/octet-stream"); // Assuming only one dump file will be present in the dump // id directory std::string dumpFileName = file.path().filename().string(); // Filename should be in alphanumeric, dot and underscore // Its based on phosphor-debug-collector application // dumpfile format static std::regex dumpFileRegex("[a-zA-Z0-9\\._]+"); if (!std::regex_match(dumpFileName, dumpFileRegex)) { BMCWEB_LOG_ERROR("Invalid dump filename {}", dumpFileName); asyncResp->res.result(boost::beast::http::status::not_found); return; } std::string contentDispositionParam = "attachment; filename=\"" + dumpFileName + "\""; asyncResp->res.addHeader( boost::beast::http::field::content_disposition, contentDispositionParam); return; } asyncResp->res.result(boost::beast::http::status::not_found); return; }); BMCWEB_ROUTE(app, "/bus/system//") .privileges({{"Login"}}) .methods(boost::beast::http::verb::get)( [](const crow::Request&, const std::shared_ptr& asyncResp, const std::string& connection) { introspectObjects(connection, "/", asyncResp); }); BMCWEB_ROUTE(app, "/bus/system//") .privileges({{"ConfigureComponents", "ConfigureManager"}}) .methods(boost::beast::http::verb::get, boost::beast::http::verb::post)(handleBusSystemPost); } } // namespace openbmc_mapper } // namespace crow