|  | /* | 
|  | // 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 "account_service.hpp" | 
|  | #include "app.hpp" | 
|  | #include "async_resp.hpp" | 
|  | #include "dbus_utility.hpp" | 
|  | #include "generated/enums/virtual_media.hpp" | 
|  | #include "managed_store.hpp" | 
|  | #include "query.hpp" | 
|  | #include "registries/privilege_registry.hpp" | 
|  | #include "utils/json_utils.hpp" | 
|  |  | 
|  | #include <boost/process/async_pipe.hpp> | 
|  | #include <boost/url/url_view.hpp> | 
|  |  | 
|  | #include <array> | 
|  | #include <cstdint> | 
|  | #include <string_view> | 
|  |  | 
|  | #include "managed_store.hpp" | 
|  |  | 
|  | #ifdef UNIT_TEST_BUILD | 
|  | #include "test/g3/mock_managed_store.hpp" // NOLINT | 
|  | #endif | 
|  |  | 
|  | namespace redfish | 
|  | { | 
|  |  | 
|  | enum class VmMode : std::uint8_t | 
|  | { | 
|  | Invalid, | 
|  | Legacy, | 
|  | Proxy | 
|  | }; | 
|  |  | 
|  | inline VmMode | 
|  | parseObjectPathAndGetMode(const sdbusplus::message::object_path& itemPath, | 
|  | const std::string& resName) | 
|  | { | 
|  | std::string thisPath = itemPath.filename(); | 
|  | BMCWEB_LOG_DEBUG << "Filename: " << itemPath.str | 
|  | << ", ThisPath: " << thisPath; | 
|  |  | 
|  | if (thisPath.empty()) | 
|  | { | 
|  | return VmMode::Invalid; | 
|  | } | 
|  |  | 
|  | if (thisPath != resName) | 
|  | { | 
|  | return VmMode::Invalid; | 
|  | } | 
|  |  | 
|  | auto mode = itemPath.parent_path(); | 
|  | auto type = mode.parent_path(); | 
|  |  | 
|  | if (mode.filename().empty() || type.filename().empty()) | 
|  | { | 
|  | return VmMode::Invalid; | 
|  | } | 
|  |  | 
|  | if (type.filename() != "VirtualMedia") | 
|  | { | 
|  | return VmMode::Invalid; | 
|  | } | 
|  | std::string modeStr = mode.filename(); | 
|  | if (modeStr == "Legacy") | 
|  | { | 
|  | return VmMode::Legacy; | 
|  | } | 
|  | if (modeStr == "Proxy") | 
|  | { | 
|  | return VmMode::Proxy; | 
|  | } | 
|  | return VmMode::Invalid; | 
|  | } | 
|  |  | 
|  | using CheckItemHandler = | 
|  | std::function<void(const std::string& service, const std::string& resName, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>&, | 
|  | const std::pair<sdbusplus::message::object_path, | 
|  | dbus::utility::DBusInteracesMap>&)>; | 
|  |  | 
|  | inline void findAndParseObject(const std::string& service, | 
|  | const std::string& resName, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& aResp, | 
|  | CheckItemHandler&& handler) | 
|  | { | 
|  | managedStore::ManagedObjectStoreContext context(aResp); | 
|  | managedStore::GetManagedObjectStore()->getManagedObjectsWithContext( | 
|  | service, {"/xyz/openbmc_project/VirtualMedia"}, context, | 
|  | [service, resName, aResp, | 
|  | handler](const boost::system::error_code& ec, | 
|  | const dbus::utility::ManagedObjectType& subtree) { | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "DBUS response error"; | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | for (const auto& item : subtree) | 
|  | { | 
|  | VmMode mode = parseObjectPathAndGetMode(item.first, resName); | 
|  | if (mode != VmMode::Invalid) | 
|  | { | 
|  | handler(service, resName, aResp, item); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | BMCWEB_LOG_DEBUG << "Parent item not found"; | 
|  | aResp->res.result(boost::beast::http::status::not_found); | 
|  | }); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Function extracts transfer protocol name from URI. | 
|  | */ | 
|  | inline std::string getTransferProtocolTypeFromUri(const std::string& imageUri) | 
|  | { | 
|  | auto url = boost::urls::parse_uri(imageUri); | 
|  | if (!url) | 
|  | { | 
|  | return "None"; | 
|  | } | 
|  | std::string_view scheme = url->scheme(); | 
|  | if (scheme == "smb") | 
|  | { | 
|  | return "CIFS"; | 
|  | } | 
|  | if (scheme == "https") | 
|  | { | 
|  | return "HTTPS"; | 
|  | } | 
|  |  | 
|  | return "None"; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Read all known properties from VM object interfaces | 
|  | */ | 
|  | inline void | 
|  | vmParseInterfaceObject(const dbus::utility::DBusInteracesMap& interfaces, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& aResp) | 
|  | { | 
|  | for (const auto& [interface, values] : interfaces) | 
|  | { | 
|  | if (interface == "xyz.openbmc_project.VirtualMedia.MountPoint") | 
|  | { | 
|  | for (const auto& [property, value] : values) | 
|  | { | 
|  | if (property == "EndpointId") | 
|  | { | 
|  | const std::string* endpointIdValue = | 
|  | std::get_if<std::string>(&value); | 
|  | if (endpointIdValue == nullptr) | 
|  | { | 
|  | continue; | 
|  | } | 
|  | if (!endpointIdValue->empty()) | 
|  | { | 
|  | // Proxy mode | 
|  | aResp->res | 
|  | .jsonValue["Oem"]["OpenBMC"]["WebSocketEndpoint"] = | 
|  | *endpointIdValue; | 
|  | aResp->res.jsonValue["TransferProtocolType"] = "OEM"; | 
|  | } | 
|  | } | 
|  | if (property == "ImageURL") | 
|  | { | 
|  | const std::string* imageUrlValue = | 
|  | std::get_if<std::string>(&value); | 
|  | if (imageUrlValue != nullptr && !imageUrlValue->empty()) | 
|  | { | 
|  | std::filesystem::path filePath = *imageUrlValue; | 
|  | if (!filePath.has_filename()) | 
|  | { | 
|  | // this will handle https share, which not | 
|  | // necessarily has to have filename given. | 
|  | aResp->res.jsonValue["ImageName"] = ""; | 
|  | } | 
|  | else | 
|  | { | 
|  | aResp->res.jsonValue["ImageName"] = | 
|  | filePath.filename(); | 
|  | } | 
|  |  | 
|  | aResp->res.jsonValue["Image"] = *imageUrlValue; | 
|  | aResp->res.jsonValue["TransferProtocolType"] = | 
|  | getTransferProtocolTypeFromUri(*imageUrlValue); | 
|  |  | 
|  | aResp->res.jsonValue["ConnectedVia"] = | 
|  | virtual_media::ConnectedVia::URI; | 
|  | } | 
|  | } | 
|  | if (property == "WriteProtected") | 
|  | { | 
|  | const bool* writeProtectedValue = std::get_if<bool>(&value); | 
|  | if (writeProtectedValue != nullptr) | 
|  | { | 
|  | aResp->res.jsonValue["WriteProtected"] = | 
|  | *writeProtectedValue; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | if (interface == "xyz.openbmc_project.VirtualMedia.Process") | 
|  | { | 
|  | for (const auto& [property, value] : values) | 
|  | { | 
|  | if (property == "Active") | 
|  | { | 
|  | const bool* activeValue = std::get_if<bool>(&value); | 
|  | if (activeValue == nullptr) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "Value Active not found"; | 
|  | return; | 
|  | } | 
|  | aResp->res.jsonValue["Inserted"] = *activeValue; | 
|  |  | 
|  | if (*activeValue) | 
|  | { | 
|  | aResp->res.jsonValue["ConnectedVia"] = | 
|  | virtual_media::ConnectedVia::Applet; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Fill template for Virtual Media Item. | 
|  | */ | 
|  | inline nlohmann::json vmItemTemplate(const std::string& name, | 
|  | const std::string& resName) | 
|  | { | 
|  | nlohmann::json item; | 
|  | item["@odata.id"] = crow::utility::urlFromPieces( | 
|  | "redfish", "v1", "Managers", name, "VirtualMedia", resName); | 
|  |  | 
|  | item["@odata.type"] = "#VirtualMedia.v1_3_0.VirtualMedia"; | 
|  | item["Name"] = "Virtual Removable Media"; | 
|  | item["Id"] = resName; | 
|  | item["WriteProtected"] = true; | 
|  | item["ConnectedVia"] = virtual_media::ConnectedVia::NotConnected; | 
|  | item["MediaTypes"] = nlohmann::json::array_t({"CD", "USBStick"}); | 
|  | item["TransferMethod"] = "Stream"; | 
|  | item["Oem"]["OpenBMC"]["@odata.type"] = | 
|  | "#OemVirtualMedia.v1_0_0.VirtualMedia"; | 
|  |  | 
|  | return item; | 
|  | } | 
|  |  | 
|  | /** | 
|  | *  @brief Fills collection data | 
|  | */ | 
|  | inline void getVmResourceList(std::shared_ptr<bmcweb::AsyncResp> aResp, | 
|  | const std::string& service, | 
|  | const std::string& name) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "Get available Virtual Media resources."; | 
|  | managedStore::ManagedObjectStoreContext context(aResp); | 
|  | managedStore::GetManagedObjectStore()->getManagedObjectsWithContext( | 
|  | service, {"/xyz/openbmc_project/VirtualMedia"}, context, | 
|  | [name, aResp{std::move(aResp)}]( | 
|  | const boost::system::error_code& ec, | 
|  | const dbus::utility::ManagedObjectType& subtree) { | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "DBUS response error"; | 
|  | return; | 
|  | } | 
|  | nlohmann::json& members = aResp->res.jsonValue["Members"]; | 
|  | members = nlohmann::json::array(); | 
|  |  | 
|  | for (const auto& object : subtree) | 
|  | { | 
|  | nlohmann::json item; | 
|  | std::string path = object.first.filename(); | 
|  | if (path.empty()) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | item["@odata.id"] = crow::utility::urlFromPieces( | 
|  | "redfish", "v1", "Managers", name, "VirtualMedia", path); | 
|  | members.emplace_back(std::move(item)); | 
|  | } | 
|  | aResp->res.jsonValue["Members@odata.count"] = members.size(); | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void | 
|  | afterGetVmData(const std::string& name, const std::string& /*service*/, | 
|  | const std::string& resName, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::pair<sdbusplus::message::object_path, | 
|  | dbus::utility::DBusInteracesMap>& item) | 
|  | { | 
|  | VmMode mode = parseObjectPathAndGetMode(item.first, resName); | 
|  | if (mode == VmMode::Invalid) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | asyncResp->res.jsonValue = vmItemTemplate(name, resName); | 
|  |  | 
|  | // Check if dbus path is Legacy type | 
|  | if (mode == VmMode::Legacy) | 
|  | { | 
|  | asyncResp->res | 
|  | .jsonValue["Actions"]["#VirtualMedia.InsertMedia"]["target"] = | 
|  | crow::utility::urlFromPieces("redfish", "v1", "Managers", name, | 
|  | "VirtualMedia", resName, "Actions", | 
|  | "VirtualMedia.InsertMedia"); | 
|  | } | 
|  |  | 
|  | vmParseInterfaceObject(item.second, asyncResp); | 
|  |  | 
|  | asyncResp->res.jsonValue["Actions"]["#VirtualMedia.EjectMedia"]["target"] = | 
|  | crow::utility::urlFromPieces("redfish", "v1", "Managers", name, | 
|  | "VirtualMedia", resName, "Actions", | 
|  | "VirtualMedia.EjectMedia"); | 
|  | } | 
|  |  | 
|  | /** | 
|  | *  @brief Fills data for specific resource | 
|  | */ | 
|  | inline void getVmData(const std::shared_ptr<bmcweb::AsyncResp>& aResp, | 
|  | const std::string& service, const std::string& name, | 
|  | const std::string& resName) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "Get Virtual Media resource data."; | 
|  |  | 
|  | findAndParseObject(service, resName, aResp, | 
|  | std::bind_front(afterGetVmData, name)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Transfer protocols supported for InsertMedia action. | 
|  | * | 
|  | */ | 
|  | enum class TransferProtocol : std::uint8_t | 
|  | { | 
|  | https, | 
|  | smb, | 
|  | invalid | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * @brief Function extracts transfer protocol type from URI. | 
|  | * | 
|  | */ | 
|  | inline std::optional<TransferProtocol> | 
|  | getTransferProtocolFromUri(boost::urls::url_view imageUri) // NOLINT | 
|  | { | 
|  | std::string_view scheme = imageUri.scheme(); | 
|  | if (scheme == "smb") | 
|  | { | 
|  | return TransferProtocol::smb; | 
|  | } | 
|  | if (scheme == "https") | 
|  | { | 
|  | return TransferProtocol::https; | 
|  | } | 
|  | if (!scheme.empty()) | 
|  | { | 
|  | return TransferProtocol::invalid; | 
|  | } | 
|  |  | 
|  | return {}; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Function convert transfer protocol from string param. | 
|  | * | 
|  | */ | 
|  | inline std::optional<TransferProtocol> getTransferProtocolFromParam( | 
|  | const std::optional<std::string>& transferProtocolType) | 
|  | { | 
|  | if (transferProtocolType == std::nullopt) | 
|  | { | 
|  | return {}; | 
|  | } | 
|  |  | 
|  | if (*transferProtocolType == "CIFS") | 
|  | { | 
|  | return TransferProtocol::smb; | 
|  | } | 
|  |  | 
|  | if (*transferProtocolType == "HTTPS") | 
|  | { | 
|  | return TransferProtocol::https; | 
|  | } | 
|  |  | 
|  | return TransferProtocol::invalid; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Function extends URI with transfer protocol type. | 
|  | * | 
|  | */ | 
|  | inline std::string | 
|  | getUriWithTransferProtocol(const std::string& imageUri, | 
|  | const TransferProtocol& transferProtocol) | 
|  | { | 
|  | if (transferProtocol == TransferProtocol::smb) | 
|  | { | 
|  | return "smb://" + imageUri; | 
|  | } | 
|  |  | 
|  | if (transferProtocol == TransferProtocol::https) | 
|  | { | 
|  | return "https://" + imageUri; | 
|  | } | 
|  |  | 
|  | return imageUri; | 
|  | } | 
|  |  | 
|  | struct InsertMediaActionParams | 
|  | { | 
|  | std::optional<std::string> imageUrl; | 
|  | std::optional<std::string> userName; | 
|  | std::optional<std::string> password; | 
|  | std::optional<std::string> transferMethod; | 
|  | std::optional<std::string> transferProtocolType; | 
|  | std::optional<bool> writeProtected = true; | 
|  | std::optional<bool> inserted; | 
|  | }; | 
|  |  | 
|  | template <typename T> | 
|  | static void secureCleanup(T& value) | 
|  | { | 
|  | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) | 
|  | auto raw = const_cast<typename T::value_type*>(value.data()); | 
|  | explicit_bzero(raw, value.size() * sizeof(*raw)); | 
|  | } | 
|  |  | 
|  | class Credentials | 
|  | { | 
|  | public: | 
|  | Credentials(std::string&& user, std::string&& password) : | 
|  | userBuf(std::move(user)), passBuf(std::move(password)) | 
|  | {} | 
|  |  | 
|  | ~Credentials() | 
|  | { | 
|  | secureCleanup(userBuf); | 
|  | secureCleanup(passBuf); | 
|  | } | 
|  |  | 
|  | const std::string& user() | 
|  | { | 
|  | return userBuf; | 
|  | } | 
|  |  | 
|  | const std::string& password() | 
|  | { | 
|  | return passBuf; | 
|  | } | 
|  |  | 
|  | Credentials() = delete; | 
|  | Credentials(const Credentials&) = delete; | 
|  | Credentials& operator=(const Credentials&) = delete; | 
|  | Credentials(Credentials&&) = delete; | 
|  | Credentials& operator=(Credentials&&) = delete; | 
|  |  | 
|  | private: | 
|  | std::string userBuf; | 
|  | std::string passBuf; | 
|  | }; | 
|  |  | 
|  | class CredentialsProvider | 
|  | { | 
|  | public: | 
|  | template <typename T> | 
|  | struct Deleter | 
|  | { | 
|  | void operator()(T* buff) const | 
|  | { | 
|  | if (buff) | 
|  | { | 
|  | secureCleanup(*buff); | 
|  | delete buff; | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | using Buffer = std::vector<char>; | 
|  | using SecureBuffer = std::unique_ptr<Buffer, Deleter<Buffer>>; | 
|  | // Using explicit definition instead of std::function to avoid implicit | 
|  | // conversions eg. stack copy instead of reference | 
|  | using FormatterFunc = void(const std::string& username, | 
|  | const std::string& password, Buffer& dest); | 
|  |  | 
|  | CredentialsProvider(std::string&& user, std::string&& password) : | 
|  | credentials(std::move(user), std::move(password)) | 
|  | {} | 
|  |  | 
|  | const std::string& user() | 
|  | { | 
|  | return credentials.user(); | 
|  | } | 
|  |  | 
|  | const std::string& password() | 
|  | { | 
|  | return credentials.password(); | 
|  | } | 
|  |  | 
|  | SecureBuffer pack(FormatterFunc* formatter) | 
|  | { | 
|  | SecureBuffer packed{new Buffer{}}; | 
|  | if (formatter != nullptr) | 
|  | { | 
|  | formatter(credentials.user(), credentials.password(), *packed); | 
|  | } | 
|  |  | 
|  | return packed; | 
|  | } | 
|  |  | 
|  | private: | 
|  | Credentials credentials; | 
|  | }; | 
|  |  | 
|  | // Wrapper for boost::async_pipe ensuring proper pipe cleanup | 
|  | class SecurePipe | 
|  | { | 
|  | public: | 
|  | using unix_fd = sdbusplus::message::unix_fd; | 
|  |  | 
|  | SecurePipe(boost::asio::io_context& io, | 
|  | CredentialsProvider::SecureBuffer&& bufferIn) : | 
|  | impl(io), | 
|  | buffer{std::move(bufferIn)} | 
|  | {} | 
|  |  | 
|  | ~SecurePipe() | 
|  | { | 
|  | // Named pipe needs to be explicitly removed | 
|  | impl.close(); | 
|  | } | 
|  |  | 
|  | SecurePipe(const SecurePipe&) = delete; | 
|  | SecurePipe(SecurePipe&&) = delete; | 
|  | SecurePipe& operator=(const SecurePipe&) = delete; | 
|  | SecurePipe& operator=(SecurePipe&&) = delete; | 
|  |  | 
|  | unix_fd fd() const | 
|  | { | 
|  | return unix_fd{impl.native_source()}; | 
|  | } | 
|  |  | 
|  | template <typename WriteHandler> | 
|  | void asyncWrite(WriteHandler&& handler) | 
|  | { | 
|  | impl.async_write_some(boost::asio::buffer(*buffer), | 
|  | std::forward<WriteHandler>(handler)); | 
|  | } | 
|  |  | 
|  | const std::string name; | 
|  | boost::process::async_pipe impl; | 
|  | CredentialsProvider::SecureBuffer buffer; | 
|  | }; | 
|  |  | 
|  | /** | 
|  | * @brief Function transceives data with dbus directly. | 
|  | * | 
|  | * All BMC state properties will be retrieved before sending reset request. | 
|  | */ | 
|  | inline void doMountVmLegacy(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& service, const std::string& name, | 
|  | const std::string& imageUrl, const bool rw, | 
|  | std::string&& userName, std::string&& password) | 
|  | { | 
|  | constexpr const size_t secretLimit = 1024; | 
|  |  | 
|  | std::shared_ptr<SecurePipe> secretPipe; | 
|  | dbus::utility::DbusVariantType unixFd = -1; | 
|  |  | 
|  | if (!userName.empty() || !password.empty()) | 
|  | { | 
|  | // Encapsulate in safe buffer | 
|  | CredentialsProvider credentials(std::move(userName), | 
|  | std::move(password)); | 
|  |  | 
|  | // Payload must contain data + NULL delimiters | 
|  | if (credentials.user().size() + credentials.password().size() + 2 > | 
|  | secretLimit) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Credentials too long to handle"; | 
|  | messages::unrecognizedRequestBody(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Pack secret | 
|  | auto secret = credentials.pack( | 
|  | [](const auto& user, const auto& pass, auto& buff) { | 
|  | std::copy(user.begin(), user.end(), std::back_inserter(buff)); | 
|  | buff.push_back('\0'); | 
|  | std::copy(pass.begin(), pass.end(), std::back_inserter(buff)); | 
|  | buff.push_back('\0'); | 
|  | }); | 
|  |  | 
|  | // Open pipe | 
|  | secretPipe = std::make_shared<SecurePipe>( | 
|  | managedStore::GetManagedObjectStore()->GetIoContext(), std::move(secret)); | 
|  | unixFd = secretPipe->fd(); | 
|  |  | 
|  | // Pass secret over pipe | 
|  | secretPipe->asyncWrite( | 
|  | [asyncResp](const boost::system::error_code& ec, std::size_t) { | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Failed to pass secret: " << ec; | 
|  | messages::internalError(asyncResp->res); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
|  | asyncResp->strand_, | 
|  | [asyncResp, secretPipe](const boost::system::error_code& ec, | 
|  | bool success) { | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; | 
|  | messages::internalError(asyncResp->res); | 
|  | } | 
|  | else if (!success) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Service responded with error"; | 
|  | messages::generalError(asyncResp->res); | 
|  | } | 
|  | }, | 
|  | service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name, | 
|  | "xyz.openbmc_project.VirtualMedia.Legacy", "Mount", imageUrl, rw, | 
|  | unixFd); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Function validate parameters of insert media request. | 
|  | * | 
|  | */ | 
|  | inline void validateParams(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& service, | 
|  | const std::string& resName, | 
|  | InsertMediaActionParams& actionParams) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG << "Validation started"; | 
|  | // required param imageUrl must not be empty | 
|  | if (!actionParams.imageUrl) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Request action parameter Image is empty."; | 
|  |  | 
|  | messages::propertyValueFormatError(asyncResp->res, "<empty>", "Image"); | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | // optional param inserted must be true | 
|  | if ((actionParams.inserted != std::nullopt) && !*actionParams.inserted) | 
|  | { | 
|  | BMCWEB_LOG_ERROR | 
|  | << "Request action optional parameter Inserted must be true."; | 
|  |  | 
|  | messages::actionParameterNotSupported(asyncResp->res, "Inserted", | 
|  | "InsertMedia"); | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | // optional param transferMethod must be stream | 
|  | if ((actionParams.transferMethod != std::nullopt) && | 
|  | (*actionParams.transferMethod != "Stream")) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Request action optional parameter " | 
|  | "TransferMethod must be Stream."; | 
|  |  | 
|  | messages::actionParameterNotSupported(asyncResp->res, "TransferMethod", | 
|  | "InsertMedia"); | 
|  |  | 
|  | return; | 
|  | } | 
|  | auto url = boost::urls::parse_uri(*actionParams.imageUrl); | 
|  | if (!url) | 
|  | { | 
|  | messages::actionParameterValueFormatError( | 
|  | asyncResp->res, *actionParams.imageUrl, "Image", "InsertMedia"); | 
|  | return; | 
|  | } | 
|  | std::optional<TransferProtocol> uriTransferProtocolType = | 
|  | getTransferProtocolFromUri(*url); | 
|  |  | 
|  | std::optional<TransferProtocol> paramTransferProtocolType = | 
|  | getTransferProtocolFromParam(actionParams.transferProtocolType); | 
|  |  | 
|  | // ImageUrl does not contain valid protocol type | 
|  | if (*uriTransferProtocolType == TransferProtocol::invalid) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Request action parameter ImageUrl must " | 
|  | "contain specified protocol type from list: " | 
|  | "(smb, https)."; | 
|  |  | 
|  | messages::resourceAtUriInUnknownFormat(asyncResp->res, *url); | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | // transferProtocolType should contain value from list | 
|  | if (*paramTransferProtocolType == TransferProtocol::invalid) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Request action parameter TransferProtocolType " | 
|  | "must be provided with value from list: " | 
|  | "(CIFS, HTTPS)."; | 
|  |  | 
|  | messages::propertyValueNotInList(asyncResp->res, | 
|  | *actionParams.transferProtocolType, | 
|  | "TransferProtocolType"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // valid transfer protocol not provided either with URI nor param | 
|  | if ((uriTransferProtocolType == std::nullopt) && | 
|  | (paramTransferProtocolType == std::nullopt)) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Request action parameter ImageUrl must " | 
|  | "contain specified protocol type or param " | 
|  | "TransferProtocolType must be provided."; | 
|  |  | 
|  | messages::resourceAtUriInUnknownFormat(asyncResp->res, *url); | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | // valid transfer protocol provided both with URI and param | 
|  | if ((paramTransferProtocolType != std::nullopt) && | 
|  | (uriTransferProtocolType != std::nullopt)) | 
|  | { | 
|  | // check if protocol is the same for URI and param | 
|  | if (*paramTransferProtocolType != *uriTransferProtocolType) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Request action parameter " | 
|  | "TransferProtocolType must  contain the " | 
|  | "same protocol type as protocol type " | 
|  | "provided with param imageUrl."; | 
|  |  | 
|  | messages::actionParameterValueTypeError( | 
|  | asyncResp->res, *actionParams.transferProtocolType, | 
|  | "TransferProtocolType", "InsertMedia"); | 
|  |  | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | // validation passed, add protocol to URI if needed | 
|  | if (uriTransferProtocolType == std::nullopt) | 
|  | { | 
|  | actionParams.imageUrl = getUriWithTransferProtocol( | 
|  | *actionParams.imageUrl, *paramTransferProtocolType); | 
|  | } | 
|  |  | 
|  | doMountVmLegacy(asyncResp, service, resName, *actionParams.imageUrl, | 
|  | !(*actionParams.writeProtected), | 
|  | std::move(*actionParams.userName), | 
|  | std::move(*actionParams.password)); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Function transceives data with dbus directly. | 
|  | * | 
|  | * All BMC state properties will be retrieved before sending reset request. | 
|  | */ | 
|  | inline void doEjectAction(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& service, const std::string& name, | 
|  | bool legacy) | 
|  | { | 
|  |  | 
|  | // Legacy mount requires parameter with image | 
|  | if (legacy) | 
|  | { | 
|  | managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
|  | asyncResp->strand_, | 
|  | [asyncResp](const boost::system::error_code& ec) { | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; | 
|  |  | 
|  | messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  | }, | 
|  | service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name, | 
|  | "xyz.openbmc_project.VirtualMedia.Legacy", "Unmount"); | 
|  | } | 
|  | else // proxy | 
|  | { | 
|  | managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
|  | asyncResp->strand_, | 
|  | [asyncResp](const boost::system::error_code& ec) { | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; | 
|  |  | 
|  | messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  | }, | 
|  | service, "/xyz/openbmc_project/VirtualMedia/Proxy/" + name, | 
|  | "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount"); | 
|  | } | 
|  | } | 
|  |  | 
|  | inline void handleManagersVirtualMediaActionInsertPost( | 
|  | crow::App& app, const crow::Request& req, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& name, const std::string& resName) | 
|  | { | 
|  | if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | constexpr std::string_view action = "VirtualMedia.InsertMedia"; | 
|  | if (name != "bmc") | 
|  | { | 
|  | messages::resourceNotFound(asyncResp->res, action, resName); | 
|  |  | 
|  | return; | 
|  | } | 
|  | InsertMediaActionParams actionParams; | 
|  |  | 
|  | // Read obligatory parameters (url of image) | 
|  | if (!json_util::readJsonAction( | 
|  | req, asyncResp->res, "Image", actionParams.imageUrl, | 
|  | "WriteProtected", actionParams.writeProtected, "UserName", | 
|  | actionParams.userName, "Password", actionParams.password, | 
|  | "Inserted", actionParams.inserted, "TransferMethod", | 
|  | actionParams.transferMethod, "TransferProtocolType", | 
|  | actionParams.transferProtocolType)) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | managedStore::ManagedObjectStoreContext context(asyncResp); | 
|  | managedStore::GetManagedObjectStore()->getDbusObject( | 
|  | "/xyz/openbmc_project/VirtualMedia", {}, context, | 
|  | [asyncResp, action, actionParams, resName, | 
|  | context](const boost::system::error_code& ec, | 
|  | const dbus::utility::MapperGetObject& getObjectType) mutable { | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << ec; | 
|  | messages::resourceNotFound(asyncResp->res, action, resName); | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::string service = getObjectType.begin()->first; | 
|  | BMCWEB_LOG_DEBUG << "GetObjectType: " << service; | 
|  |  | 
|  | managedStore::GetManagedObjectStore()->getManagedObjectsWithContext( | 
|  | service, {"/xyz/openbmc_project/VirtualMedia"}, context, | 
|  | [service, resName, action, actionParams, asyncResp]( | 
|  | const boost::system::error_code& ec2, | 
|  | const dbus::utility::ManagedObjectType& subtree) mutable { | 
|  | if (ec2) | 
|  | { | 
|  | // Not possible in proxy mode | 
|  | BMCWEB_LOG_DEBUG << "InsertMedia not " | 
|  | "allowed in proxy mode"; | 
|  | messages::resourceNotFound(asyncResp->res, action, resName); | 
|  |  | 
|  | return; | 
|  | } | 
|  | for (const auto& object : subtree) | 
|  | { | 
|  | VmMode mode = parseObjectPathAndGetMode(object.first, resName); | 
|  | if (mode == VmMode::Proxy) | 
|  | { | 
|  | validateParams(asyncResp, service, resName, actionParams); | 
|  |  | 
|  | return; | 
|  | } | 
|  | } | 
|  | BMCWEB_LOG_DEBUG << "Parent item not found"; | 
|  | messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void handleManagersVirtualMediaActionEject( | 
|  | crow::App& app, const crow::Request& req, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& managerName, const std::string& resName) | 
|  | { | 
|  | if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | constexpr std::string_view action = "VirtualMedia.EjectMedia"; | 
|  | if (managerName != "bmc") | 
|  | { | 
|  | messages::resourceNotFound(asyncResp->res, action, resName); | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | managedStore::ManagedObjectStoreContext context(asyncResp); | 
|  | managedStore::GetManagedObjectStore()->getDbusObject( | 
|  | "/xyz/openbmc_project/VirtualMedia", {}, context, | 
|  | [asyncResp, action, resName, | 
|  | context](const boost::system::error_code& ec2, | 
|  | const dbus::utility::MapperGetObject& getObjectType) { | 
|  | if (ec2) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << ec2; | 
|  | messages::internalError(asyncResp->res); | 
|  |  | 
|  | return; | 
|  | } | 
|  | std::string service = getObjectType.begin()->first; | 
|  | BMCWEB_LOG_DEBUG << "GetObjectType: " << service; | 
|  |  | 
|  | managedStore::GetManagedObjectStore()->getManagedObjectsWithContext( | 
|  | service, {"/xyz/openbmc_project/VirtualMedia"}, context, | 
|  | [resName, service, action, | 
|  | asyncResp](const boost::system::error_code& ec, | 
|  | const dbus::utility::ManagedObjectType& subtree) { | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "ObjectMapper : No Service found"; | 
|  | messages::resourceNotFound(asyncResp->res, action, resName); | 
|  | return; | 
|  | } | 
|  |  | 
|  | for (const auto& object : subtree) | 
|  | { | 
|  |  | 
|  | VmMode mode = parseObjectPathAndGetMode(object.first, resName); | 
|  | if (mode != VmMode::Invalid) | 
|  | { | 
|  | doEjectAction(asyncResp, service, resName, | 
|  | mode == VmMode::Legacy); | 
|  | } | 
|  | } | 
|  | BMCWEB_LOG_DEBUG << "Parent item not found"; | 
|  | messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName); | 
|  | }); | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void handleManagersVirtualMediaCollectionGet( | 
|  | crow::App& app, const crow::Request& req, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& name) | 
|  | { | 
|  | if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
|  | { | 
|  | return; | 
|  | } | 
|  | if (name != "bmc") | 
|  | { | 
|  | messages::resourceNotFound(asyncResp->res, "VirtualMedia", name); | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | asyncResp->res.jsonValue["@odata.type"] = | 
|  | "#VirtualMediaCollection.VirtualMediaCollection"; | 
|  | asyncResp->res.jsonValue["Name"] = "Virtual Media Services"; | 
|  | asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( | 
|  | "redfish", "v1", "Managers", name, "VirtualMedia"); | 
|  |  | 
|  | managedStore::ManagedObjectStoreContext context(asyncResp); | 
|  | managedStore::GetManagedObjectStore()->getDbusObject( | 
|  | "/xyz/openbmc_project/VirtualMedia", {}, context, | 
|  | [asyncResp, name](const boost::system::error_code& ec, | 
|  | const dbus::utility::MapperGetObject& getObjectType) { | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << ec; | 
|  | messages::internalError(asyncResp->res); | 
|  |  | 
|  | return; | 
|  | } | 
|  | std::string service = getObjectType.begin()->first; | 
|  | BMCWEB_LOG_DEBUG << "GetObjectType: " << service; | 
|  |  | 
|  | getVmResourceList(asyncResp, service, name); | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void | 
|  | handleVirtualMediaGet(crow::App& app, const crow::Request& req, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& name, const std::string& resName) | 
|  | { | 
|  | if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
|  | { | 
|  | return; | 
|  | } | 
|  | if (name != "bmc") | 
|  | { | 
|  | messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName); | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | managedStore::ManagedObjectStoreContext context(asyncResp); | 
|  | managedStore::GetManagedObjectStore()->getDbusObject( | 
|  | "/xyz/openbmc_project/VirtualMedia", {}, context, | 
|  | [asyncResp, name, | 
|  | resName](const boost::system::error_code& ec, | 
|  | const dbus::utility::MapperGetObject& getObjectType) { | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << ec; | 
|  | messages::internalError(asyncResp->res); | 
|  |  | 
|  | return; | 
|  | } | 
|  | std::string service = getObjectType.begin()->first; | 
|  | BMCWEB_LOG_DEBUG << "GetObjectType: " << service; | 
|  |  | 
|  | getVmData(asyncResp, service, name, resName); | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void requestNBDVirtualMediaRoutes(App& app) | 
|  | { | 
|  | BMCWEB_ROUTE( | 
|  | app, | 
|  | "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/VirtualMedia.InsertMedia") | 
|  | .privileges(redfish::privileges::postVirtualMedia) | 
|  | .methods(boost::beast::http::verb::post)(std::bind_front( | 
|  | handleManagersVirtualMediaActionInsertPost, std::ref(app))); | 
|  |  | 
|  | BMCWEB_ROUTE( | 
|  | app, | 
|  | "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/VirtualMedia.EjectMedia") | 
|  | .privileges(redfish::privileges::postVirtualMedia) | 
|  | .methods(boost::beast::http::verb::post)(std::bind_front( | 
|  | handleManagersVirtualMediaActionEject, std::ref(app))); | 
|  |  | 
|  | BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/") | 
|  | .privileges(redfish::privileges::getVirtualMediaCollection) | 
|  | .methods(boost::beast::http::verb::get)(std::bind_front( | 
|  | handleManagersVirtualMediaCollectionGet, std::ref(app))); | 
|  |  | 
|  | BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/<str>/") | 
|  | .privileges(redfish::privileges::getVirtualMedia) | 
|  | .methods(boost::beast::http::verb::get)( | 
|  | std::bind_front(handleVirtualMediaGet, std::ref(app))); | 
|  | } | 
|  |  | 
|  | } // namespace redfish |