| /* |
| // 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 <boost/container/flat_map.hpp> |
| #include <boost/process/async_pipe.hpp> |
| #include <boost/type_traits/has_dereference.hpp> |
| #include <node.hpp> |
| #include <utils/json_utils.hpp> |
| // for GetObjectType and ManagedObjectType |
| #include <account_service.hpp> |
| #include <boost/url/url_view.hpp> |
| |
| namespace redfish |
| |
| { |
| /** |
| * @brief Function extracts transfer protocol name from URI. |
| */ |
| static std::string getTransferProtocolTypeFromUri(const std::string& imageUri) |
| { |
| try |
| { |
| std::string_view scheme = boost::urls::url_view(imageUri).scheme(); |
| if (scheme == "smb") |
| { |
| return "CIFS"; |
| } |
| else if (scheme == "https") |
| { |
| return "HTTPS"; |
| } |
| } |
| catch (std::exception& p) |
| { |
| BMCWEB_LOG_ERROR << p.what(); |
| } |
| return "None"; |
| } |
| |
| /** |
| * @brief Read all known properties from VM object interfaces |
| */ |
| static void |
| vmParseInterfaceObject(const DbusInterfaceType& interface, |
| const std::shared_ptr<bmcweb::AsyncResp>& aResp) |
| { |
| const auto mountPointIface = |
| interface.find("xyz.openbmc_project.VirtualMedia.MountPoint"); |
| if (mountPointIface == interface.cend()) |
| { |
| BMCWEB_LOG_DEBUG << "Interface MountPoint not found"; |
| return; |
| } |
| |
| const auto processIface = |
| interface.find("xyz.openbmc_project.VirtualMedia.Process"); |
| if (processIface == interface.cend()) |
| { |
| BMCWEB_LOG_DEBUG << "Interface Process not found"; |
| return; |
| } |
| |
| const auto endpointIdProperty = mountPointIface->second.find("EndpointId"); |
| if (endpointIdProperty == mountPointIface->second.cend()) |
| { |
| BMCWEB_LOG_DEBUG << "Property EndpointId not found"; |
| return; |
| } |
| |
| const auto activeProperty = processIface->second.find("Active"); |
| if (activeProperty == processIface->second.cend()) |
| { |
| BMCWEB_LOG_DEBUG << "Property Active not found"; |
| return; |
| } |
| |
| const bool* activeValue = std::get_if<bool>(&activeProperty->second); |
| if (!activeValue) |
| { |
| BMCWEB_LOG_DEBUG << "Value Active not found"; |
| return; |
| } |
| |
| const std::string* endpointIdValue = |
| std::get_if<std::string>(&endpointIdProperty->second); |
| if (endpointIdValue) |
| { |
| if (!endpointIdValue->empty()) |
| { |
| // Proxy mode |
| aResp->res.jsonValue["Oem"]["OpenBMC"]["WebSocketEndpoint"] = |
| *endpointIdValue; |
| aResp->res.jsonValue["TransferProtocolType"] = "OEM"; |
| aResp->res.jsonValue["Inserted"] = *activeValue; |
| if (*activeValue == true) |
| { |
| aResp->res.jsonValue["ConnectedVia"] = "Applet"; |
| } |
| } |
| else |
| { |
| // Legacy mode |
| for (const auto& property : mountPointIface->second) |
| { |
| if (property.first == "ImageURL") |
| { |
| const std::string* imageUrlValue = |
| std::get_if<std::string>(&property.second); |
| if (imageUrlValue && !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["Inserted"] = *activeValue; |
| aResp->res.jsonValue["TransferProtocolType"] = |
| getTransferProtocolTypeFromUri(*imageUrlValue); |
| |
| if (*activeValue == true) |
| { |
| aResp->res.jsonValue["ConnectedVia"] = "URI"; |
| } |
| } |
| } |
| else if (property.first == "WriteProtected") |
| { |
| const bool* writeProtectedValue = |
| std::get_if<bool>(&property.second); |
| if (writeProtectedValue) |
| { |
| aResp->res.jsonValue["WriteProtected"] = |
| *writeProtectedValue; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * @brief Fill template for Virtual Media Item. |
| */ |
| static nlohmann::json vmItemTemplate(const std::string& name, |
| const std::string& resName) |
| { |
| nlohmann::json item; |
| item["@odata.id"] = |
| "/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["MediaTypes"] = {"CD", "USBStick"}; |
| item["TransferMethod"] = "Stream"; |
| item["Oem"]["OpenBMC"]["@odata.type"] = |
| "#OemVirtualMedia.v1_0_0.VirtualMedia"; |
| |
| return item; |
| } |
| |
| /** |
| * @brief Fills collection data |
| */ |
| static void getVmResourceList(std::shared_ptr<bmcweb::AsyncResp> aResp, |
| const std::string& service, |
| const std::string& name) |
| { |
| BMCWEB_LOG_DEBUG << "Get available Virtual Media resources."; |
| crow::connections::systemBus->async_method_call( |
| [name, aResp{std::move(aResp)}](const boost::system::error_code ec, |
| 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"] = |
| "/redfish/v1/Managers/" + name + "/VirtualMedia/" + path; |
| |
| members.emplace_back(std::move(item)); |
| } |
| aResp->res.jsonValue["Members@odata.count"] = members.size(); |
| }, |
| service, "/xyz/openbmc_project/VirtualMedia", |
| "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); |
| } |
| |
| /** |
| * @brief Fills data for specific resource |
| */ |
| static 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."; |
| |
| crow::connections::systemBus->async_method_call( |
| [resName, name, aResp](const boost::system::error_code ec, |
| ManagedObjectType& subtree) { |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "DBUS response error"; |
| |
| return; |
| } |
| |
| for (auto& item : subtree) |
| { |
| std::string thispath = item.first.filename(); |
| if (thispath.empty()) |
| { |
| continue; |
| } |
| |
| if (thispath != resName) |
| { |
| continue; |
| } |
| |
| aResp->res.jsonValue = vmItemTemplate(name, resName); |
| |
| // Check if dbus path is Legacy type |
| if (thispath.find("VirtualMedia/Legacy") != std::string::npos) |
| { |
| aResp->res.jsonValue["Actions"]["#VirtualMedia.InsertMedia"] |
| ["target"] = |
| "/redfish/v1/Managers/" + name + "/VirtualMedia/" + |
| resName + "/Actions/VirtualMedia.InsertMedia"; |
| } |
| |
| vmParseInterfaceObject(item.second, aResp); |
| |
| aResp->res.jsonValue["Actions"]["#VirtualMedia.EjectMedia"] |
| ["target"] = |
| "/redfish/v1/Managers/" + name + "/VirtualMedia/" + |
| resName + "/Actions/VirtualMedia.EjectMedia"; |
| |
| return; |
| } |
| |
| messages::resourceNotFound( |
| aResp->res, "#VirtualMedia.v1_3_0.VirtualMedia", resName); |
| }, |
| service, "/xyz/openbmc_project/VirtualMedia", |
| "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); |
| } |
| |
| /** |
| @brief InsertMedia action class |
| */ |
| class VirtualMediaActionInsertMedia : public Node |
| { |
| public: |
| VirtualMediaActionInsertMedia(App& app) : |
| Node(app, |
| "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/" |
| "VirtualMedia.InsertMedia", |
| std::string(), std::string()) |
| { |
| entityPrivileges = { |
| {boost::beast::http::verb::get, {{"Login"}}}, |
| {boost::beast::http::verb::head, {{"Login"}}}, |
| {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, |
| {boost::beast::http::verb::put, {{"ConfigureManager"}}}, |
| {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, |
| {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; |
| } |
| |
| private: |
| /** |
| * @brief Transfer protocols supported for InsertMedia action. |
| * |
| */ |
| enum class TransferProtocol |
| { |
| https, |
| smb, |
| invalid |
| }; |
| |
| /** |
| * @brief Function extracts transfer protocol type from URI. |
| * |
| */ |
| std::optional<TransferProtocol> |
| getTransferProtocolFromUri(const std::string& imageUri) |
| { |
| try |
| { |
| std::string_view scheme = boost::urls::url_view(imageUri).scheme(); |
| if (scheme == "smb") |
| { |
| return TransferProtocol::smb; |
| } |
| if (scheme == "https") |
| { |
| return TransferProtocol::https; |
| } |
| else if (!scheme.empty()) |
| { |
| return TransferProtocol::invalid; |
| } |
| } |
| catch (std::exception& p) |
| { |
| BMCWEB_LOG_ERROR << p.what(); |
| } |
| |
| return {}; |
| } |
| |
| /** |
| * @brief Function convert transfer protocol from string param. |
| * |
| */ |
| 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. |
| * |
| */ |
| 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; |
| } |
| |
| /** |
| * @brief Function validate parameters of insert media request. |
| * |
| */ |
| bool validateParams(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| std::string& imageUrl, |
| const std::optional<bool>& inserted, |
| const std::optional<std::string>& transferMethod, |
| const std::optional<std::string>& transferProtocolType) |
| { |
| BMCWEB_LOG_DEBUG << "Validation started"; |
| // required param imageUrl must not be empty |
| if (imageUrl.empty()) |
| { |
| BMCWEB_LOG_ERROR << "Request action parameter Image is empty."; |
| |
| messages::propertyValueFormatError(asyncResp->res, "<empty>", |
| "Image"); |
| |
| return false; |
| } |
| |
| // optional param inserted must be true |
| if ((inserted != std::nullopt) && (*inserted != true)) |
| { |
| BMCWEB_LOG_ERROR |
| << "Request action optional parameter Inserted must be true."; |
| |
| messages::actionParameterNotSupported(asyncResp->res, "Inserted", |
| "InsertMedia"); |
| |
| return false; |
| } |
| |
| // optional param transferMethod must be stream |
| if ((transferMethod != std::nullopt) && (*transferMethod != "Stream")) |
| { |
| BMCWEB_LOG_ERROR << "Request action optional parameter " |
| "TransferMethod must be Stream."; |
| |
| messages::actionParameterNotSupported( |
| asyncResp->res, "TransferMethod", "InsertMedia"); |
| |
| return false; |
| } |
| |
| std::optional<TransferProtocol> uriTransferProtocolType = |
| getTransferProtocolFromUri(imageUrl); |
| |
| std::optional<TransferProtocol> paramTransferProtocolType = |
| getTransferProtocolFromParam(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, imageUrl); |
| |
| return false; |
| } |
| |
| // 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, *transferProtocolType, "TransferProtocolType"); |
| return false; |
| } |
| |
| // 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, imageUrl); |
| |
| return false; |
| } |
| |
| // 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, *transferProtocolType, |
| "TransferProtocolType", "InsertMedia"); |
| |
| return false; |
| } |
| } |
| |
| // validation passed |
| // add protocol to URI if needed |
| if (uriTransferProtocolType == std::nullopt) |
| { |
| imageUrl = getUriWithTransferProtocol(imageUrl, |
| *paramTransferProtocolType); |
| } |
| |
| return true; |
| } |
| |
| /** |
| * @brief Function handles POST method request. |
| * |
| * Analyzes POST body message before sends Reset request data to dbus. |
| */ |
| void doPost(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const crow::Request& req, |
| const std::vector<std::string>& params) override |
| { |
| if (params.size() != 2) |
| { |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| // take resource name from URL |
| const std::string& resName = params[1]; |
| |
| if (params[0] != "bmc") |
| { |
| messages::resourceNotFound(asyncResp->res, "VirtualMedia.Insert", |
| resName); |
| |
| return; |
| } |
| |
| crow::connections::systemBus->async_method_call( |
| [this, asyncResp, req, |
| resName](const boost::system::error_code ec, |
| const GetObjectType& 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; |
| |
| crow::connections::systemBus->async_method_call( |
| [this, service, resName, req, |
| asyncResp](const boost::system::error_code ec, |
| ManagedObjectType& subtree) { |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "DBUS response error"; |
| |
| return; |
| } |
| |
| for (const auto& object : subtree) |
| { |
| const std::string& path = |
| static_cast<const std::string&>(object.first); |
| |
| std::size_t lastIndex = path.rfind('/'); |
| if (lastIndex == std::string::npos) |
| { |
| continue; |
| } |
| |
| lastIndex += 1; |
| |
| if (path.substr(lastIndex) == resName) |
| { |
| lastIndex = path.rfind("Proxy"); |
| if (lastIndex != std::string::npos) |
| { |
| // Not possible in proxy mode |
| BMCWEB_LOG_DEBUG << "InsertMedia not " |
| "allowed in proxy mode"; |
| messages::resourceNotFound( |
| asyncResp->res, |
| "VirtualMedia.InsertMedia", resName); |
| |
| return; |
| } |
| |
| lastIndex = path.rfind("Legacy"); |
| if (lastIndex == std::string::npos) |
| { |
| continue; |
| } |
| |
| // Legacy mode |
| 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; |
| |
| // Read obligatory parameters (url of image) |
| if (!json_util::readJson( |
| req, asyncResp->res, "Image", imageUrl, |
| "WriteProtected", writeProtected, |
| "UserName", userName, "Password", |
| password, "Inserted", inserted, |
| "TransferMethod", transferMethod, |
| "TransferProtocolType", |
| transferProtocolType)) |
| { |
| BMCWEB_LOG_DEBUG << "Image is not provided"; |
| return; |
| } |
| |
| bool paramsValid = validateParams( |
| asyncResp->res, imageUrl, inserted, |
| transferMethod, transferProtocolType); |
| |
| if (paramsValid == false) |
| { |
| return; |
| } |
| |
| // manager is irrelevant for VirtualMedia dbus |
| // calls |
| doMountVmLegacy(asyncResp, service, resName, |
| imageUrl, !(*writeProtected), |
| std::move(*userName), |
| std::move(*password)); |
| |
| return; |
| } |
| } |
| BMCWEB_LOG_DEBUG << "Parent item not found"; |
| messages::resourceNotFound(asyncResp->res, |
| "VirtualMedia", resName); |
| }, |
| service, "/xyz/openbmc_project/VirtualMedia", |
| "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); |
| }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetObject", |
| "/xyz/openbmc_project/VirtualMedia", std::array<const char*, 0>()); |
| } |
| |
| template <typename T> |
| static void secureCleanup(T& value) |
| { |
| 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; |
| } |
| |
| private: |
| Credentials() = delete; |
| Credentials(const Credentials&) = delete; |
| Credentials& operator=(const Credentials&) = delete; |
| |
| 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) |
| { |
| formatter(credentials.user(), credentials.password(), *packed); |
| } |
| |
| return packed; |
| } |
| |
| private: |
| Credentials credentials; |
| }; |
| |
| // Wrapper for boost::async_pipe ensuring proper pipe cleanup |
| template <typename Buffer> |
| class Pipe |
| { |
| public: |
| using unix_fd = sdbusplus::message::unix_fd; |
| |
| Pipe(boost::asio::io_context& io, Buffer&& buffer) : |
| impl(io), buffer{std::move(buffer)} |
| {} |
| |
| ~Pipe() |
| { |
| // Named pipe needs to be explicitly removed |
| impl.close(); |
| } |
| |
| unix_fd fd() |
| { |
| return unix_fd{impl.native_source()}; |
| } |
| |
| template <typename WriteHandler> |
| void asyncWrite(WriteHandler&& handler) |
| { |
| impl.async_write_some(data(), std::forward<WriteHandler>(handler)); |
| } |
| |
| private: |
| // Specialization for pointer types |
| template <typename B = Buffer> |
| typename std::enable_if<boost::has_dereference<B>::value, |
| boost::asio::const_buffer>::type |
| data() |
| { |
| return boost::asio::buffer(*buffer); |
| } |
| |
| template <typename B = Buffer> |
| typename std::enable_if<!boost::has_dereference<B>::value, |
| boost::asio::const_buffer>::type |
| data() |
| { |
| return boost::asio::buffer(buffer); |
| } |
| |
| const std::string name; |
| boost::process::async_pipe impl; |
| Buffer buffer; |
| }; |
| |
| /** |
| * @brief Function transceives data with dbus directly. |
| * |
| * All BMC state properties will be retrieved before sending reset request. |
| */ |
| 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) |
| { |
| using SecurePipe = Pipe<CredentialsProvider::SecureBuffer>; |
| constexpr const size_t secretLimit = 1024; |
| |
| std::shared_ptr<SecurePipe> secretPipe; |
| std::variant<int, SecurePipe::unix_fd> 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>( |
| crow::connections::systemBus->get_io_context(), |
| 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); |
| } |
| }); |
| } |
| |
| crow::connections::systemBus->async_method_call( |
| [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 EjectMedia action class |
| */ |
| class VirtualMediaActionEjectMedia : public Node |
| { |
| public: |
| VirtualMediaActionEjectMedia(App& app) : |
| Node(app, |
| "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/" |
| "VirtualMedia.EjectMedia", |
| std::string(), std::string()) |
| { |
| entityPrivileges = { |
| {boost::beast::http::verb::get, {{"Login"}}}, |
| {boost::beast::http::verb::head, {{"Login"}}}, |
| {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, |
| {boost::beast::http::verb::put, {{"ConfigureManager"}}}, |
| {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, |
| {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; |
| } |
| |
| private: |
| /** |
| * @brief Function handles POST method request. |
| * |
| * Analyzes POST body message before sends Reset request data to dbus. |
| */ |
| void doPost(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const crow::Request& req, |
| const std::vector<std::string>& params) override |
| { |
| if (params.size() != 2) |
| { |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| // take resource name from URL |
| const std::string& resName = params[1]; |
| |
| if (params[0] != "bmc") |
| { |
| messages::resourceNotFound(asyncResp->res, "VirtualMedia.Eject", |
| resName); |
| |
| return; |
| } |
| |
| crow::connections::systemBus->async_method_call( |
| [this, asyncResp{std::move(asyncResp)}, req, |
| resName](const boost::system::error_code ec, |
| const GetObjectType& 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; |
| |
| crow::connections::systemBus->async_method_call( |
| [this, resName, service, req, |
| asyncResp{asyncResp}](const boost::system::error_code ec, |
| ManagedObjectType& subtree) { |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "DBUS response error"; |
| |
| return; |
| } |
| |
| for (const auto& object : subtree) |
| { |
| const std::string& path = |
| static_cast<const std::string&>(object.first); |
| |
| std::size_t lastIndex = path.rfind('/'); |
| if (lastIndex == std::string::npos) |
| { |
| continue; |
| } |
| |
| lastIndex += 1; |
| |
| if (path.substr(lastIndex) == resName) |
| { |
| lastIndex = path.rfind("Proxy"); |
| if (lastIndex != std::string::npos) |
| { |
| // Proxy mode |
| doVmAction(asyncResp, service, resName, |
| false); |
| } |
| |
| lastIndex = path.rfind("Legacy"); |
| if (lastIndex != std::string::npos) |
| { |
| // Legacy mode |
| doVmAction(asyncResp, service, resName, |
| true); |
| } |
| |
| return; |
| } |
| } |
| BMCWEB_LOG_DEBUG << "Parent item not found"; |
| messages::resourceNotFound(asyncResp->res, |
| "VirtualMedia", resName); |
| }, |
| service, "/xyz/openbmc_project/VirtualMedia", |
| "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); |
| }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetObject", |
| "/xyz/openbmc_project/VirtualMedia", std::array<const char*, 0>()); |
| } |
| |
| /** |
| * @brief Function transceives data with dbus directly. |
| * |
| * All BMC state properties will be retrieved before sending reset request. |
| */ |
| void doVmAction(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) |
| { |
| crow::connections::systemBus->async_method_call( |
| [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 |
| { |
| crow::connections::systemBus->async_method_call( |
| [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"); |
| } |
| } |
| }; |
| |
| class VirtualMediaCollection : public Node |
| { |
| public: |
| /* |
| * Default Constructor |
| */ |
| VirtualMediaCollection(App& app) : |
| Node(app, "/redfish/v1/Managers/<str>/VirtualMedia/", std::string()) |
| { |
| entityPrivileges = { |
| {boost::beast::http::verb::get, {{"Login"}}}, |
| {boost::beast::http::verb::head, {{"Login"}}}, |
| {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, |
| {boost::beast::http::verb::put, {{"ConfigureManager"}}}, |
| {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, |
| {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; |
| } |
| |
| private: |
| /** |
| * Functions triggers appropriate requests on DBus |
| */ |
| void doGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const crow::Request&, |
| const std::vector<std::string>& params) override |
| { |
| |
| // Check if there is required param, truly entering this shall be |
| // impossible |
| if (params.size() != 1) |
| { |
| messages::internalError(asyncResp->res); |
| |
| return; |
| } |
| |
| const std::string& name = params[0]; |
| |
| 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"] = |
| "/redfish/v1/Managers/" + name + "/VirtualMedia"; |
| |
| crow::connections::systemBus->async_method_call( |
| [asyncResp, name](const boost::system::error_code ec, |
| const GetObjectType& 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); |
| }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetObject", |
| "/xyz/openbmc_project/VirtualMedia", std::array<const char*, 0>()); |
| } |
| }; |
| |
| class VirtualMedia : public Node |
| { |
| public: |
| /* |
| * Default Constructor |
| */ |
| VirtualMedia(App& app) : |
| Node(app, "/redfish/v1/Managers/<str>/VirtualMedia/<str>/", |
| std::string(), std::string()) |
| { |
| entityPrivileges = { |
| {boost::beast::http::verb::get, {{"Login"}}}, |
| {boost::beast::http::verb::head, {{"Login"}}}, |
| {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, |
| {boost::beast::http::verb::put, {{"ConfigureManager"}}}, |
| {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, |
| {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; |
| } |
| |
| private: |
| /** |
| * Functions triggers appropriate requests on DBus |
| */ |
| void doGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const crow::Request&, |
| const std::vector<std::string>& params) override |
| { |
| // Check if there is required param, truly entering this shall be |
| // impossible |
| if (params.size() != 2) |
| { |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| const std::string& name = params[0]; |
| const std::string& resName = params[1]; |
| |
| if (name != "bmc") |
| { |
| messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName); |
| |
| return; |
| } |
| |
| crow::connections::systemBus->async_method_call( |
| [asyncResp, name, resName](const boost::system::error_code ec, |
| const GetObjectType& 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); |
| }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetObject", |
| "/xyz/openbmc_project/VirtualMedia", std::array<const char*, 0>()); |
| } |
| }; |
| |
| } // namespace redfish |