1 /* 2 // Copyright (c) 2018 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 #pragma once 17 18 #include "account_service.hpp" 19 #include "app.hpp" 20 #include "async_resp.hpp" 21 #include "dbus_utility.hpp" 22 #include "generated/enums/virtual_media.hpp" 23 #include "query.hpp" 24 #include "registries/privilege_registry.hpp" 25 #include "utils/json_utils.hpp" 26 27 #include <boost/process/async_pipe.hpp> 28 #include <boost/url/format.hpp> 29 #include <boost/url/url_view.hpp> 30 31 #include <array> 32 #include <ranges> 33 #include <string_view> 34 35 namespace redfish 36 { 37 38 enum class VmMode 39 { 40 Invalid, 41 Legacy, 42 Proxy 43 }; 44 45 inline VmMode 46 parseObjectPathAndGetMode(const sdbusplus::message::object_path& itemPath, 47 const std::string& resName) 48 { 49 std::string thisPath = itemPath.filename(); 50 BMCWEB_LOG_DEBUG("Filename: {}, ThisPath: {}", itemPath.str, thisPath); 51 52 if (thisPath.empty()) 53 { 54 return VmMode::Invalid; 55 } 56 57 if (thisPath != resName) 58 { 59 return VmMode::Invalid; 60 } 61 62 auto mode = itemPath.parent_path(); 63 auto type = mode.parent_path(); 64 65 if (mode.filename().empty() || type.filename().empty()) 66 { 67 return VmMode::Invalid; 68 } 69 70 if (type.filename() != "VirtualMedia") 71 { 72 return VmMode::Invalid; 73 } 74 std::string modeStr = mode.filename(); 75 if (modeStr == "Legacy") 76 { 77 return VmMode::Legacy; 78 } 79 if (modeStr == "Proxy") 80 { 81 return VmMode::Proxy; 82 } 83 return VmMode::Invalid; 84 } 85 86 using CheckItemHandler = 87 std::function<void(const std::string& service, const std::string& resName, 88 const std::shared_ptr<bmcweb::AsyncResp>&, 89 const std::pair<sdbusplus::message::object_path, 90 dbus::utility::DBusInterfacesMap>&)>; 91 92 inline void 93 findAndParseObject(const std::string& service, const std::string& resName, 94 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 95 CheckItemHandler&& handler) 96 { 97 sdbusplus::message::object_path path("/xyz/openbmc_project/VirtualMedia"); 98 dbus::utility::getManagedObjects( 99 service, path, 100 [service, resName, asyncResp, 101 handler](const boost::system::error_code& ec, 102 const dbus::utility::ManagedObjectType& subtree) { 103 if (ec) 104 { 105 BMCWEB_LOG_DEBUG("DBUS response error"); 106 107 return; 108 } 109 110 for (const auto& item : subtree) 111 { 112 VmMode mode = parseObjectPathAndGetMode(item.first, resName); 113 if (mode != VmMode::Invalid) 114 { 115 handler(service, resName, asyncResp, item); 116 return; 117 } 118 } 119 120 BMCWEB_LOG_DEBUG("Parent item not found"); 121 asyncResp->res.result(boost::beast::http::status::not_found); 122 }); 123 } 124 125 /** 126 * @brief Function extracts transfer protocol name from URI. 127 */ 128 inline std::string getTransferProtocolTypeFromUri(const std::string& imageUri) 129 { 130 boost::urls::result<boost::urls::url_view> url = 131 boost::urls::parse_uri(imageUri); 132 if (!url) 133 { 134 return "None"; 135 } 136 std::string_view scheme = url->scheme(); 137 if (scheme == "smb") 138 { 139 return "CIFS"; 140 } 141 if (scheme == "https") 142 { 143 return "HTTPS"; 144 } 145 146 return "None"; 147 } 148 149 /** 150 * @brief Read all known properties from VM object interfaces 151 */ 152 inline void 153 vmParseInterfaceObject(const dbus::utility::DBusInterfacesMap& interfaces, 154 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 155 { 156 for (const auto& [interface, values] : interfaces) 157 { 158 if (interface == "xyz.openbmc_project.VirtualMedia.MountPoint") 159 { 160 for (const auto& [property, value] : values) 161 { 162 if (property == "EndpointId") 163 { 164 const std::string* endpointIdValue = 165 std::get_if<std::string>(&value); 166 if (endpointIdValue == nullptr) 167 { 168 continue; 169 } 170 if (!endpointIdValue->empty()) 171 { 172 // Proxy mode 173 asyncResp->res 174 .jsonValue["Oem"]["OpenBMC"]["WebSocketEndpoint"] = 175 *endpointIdValue; 176 asyncResp->res.jsonValue["TransferProtocolType"] = 177 "OEM"; 178 } 179 } 180 if (property == "ImageURL") 181 { 182 const std::string* imageUrlValue = 183 std::get_if<std::string>(&value); 184 if (imageUrlValue != nullptr && !imageUrlValue->empty()) 185 { 186 std::filesystem::path filePath = *imageUrlValue; 187 if (!filePath.has_filename()) 188 { 189 // this will handle https share, which not 190 // necessarily has to have filename given. 191 asyncResp->res.jsonValue["ImageName"] = ""; 192 } 193 else 194 { 195 asyncResp->res.jsonValue["ImageName"] = 196 filePath.filename(); 197 } 198 199 asyncResp->res.jsonValue["Image"] = *imageUrlValue; 200 asyncResp->res.jsonValue["TransferProtocolType"] = 201 getTransferProtocolTypeFromUri(*imageUrlValue); 202 203 asyncResp->res.jsonValue["ConnectedVia"] = 204 virtual_media::ConnectedVia::URI; 205 } 206 } 207 if (property == "WriteProtected") 208 { 209 const bool* writeProtectedValue = std::get_if<bool>(&value); 210 if (writeProtectedValue != nullptr) 211 { 212 asyncResp->res.jsonValue["WriteProtected"] = 213 *writeProtectedValue; 214 } 215 } 216 } 217 } 218 if (interface == "xyz.openbmc_project.VirtualMedia.Process") 219 { 220 for (const auto& [property, value] : values) 221 { 222 if (property == "Active") 223 { 224 const bool* activeValue = std::get_if<bool>(&value); 225 if (activeValue == nullptr) 226 { 227 BMCWEB_LOG_DEBUG("Value Active not found"); 228 return; 229 } 230 asyncResp->res.jsonValue["Inserted"] = *activeValue; 231 232 if (*activeValue) 233 { 234 asyncResp->res.jsonValue["ConnectedVia"] = 235 virtual_media::ConnectedVia::Applet; 236 } 237 } 238 } 239 } 240 } 241 } 242 243 /** 244 * @brief Fill template for Virtual Media Item. 245 */ 246 inline nlohmann::json vmItemTemplate(const std::string& name, 247 const std::string& resName) 248 { 249 nlohmann::json item; 250 item["@odata.id"] = boost::urls::format( 251 "/redfish/v1/Managers/{}/VirtualMedia/{}", name, resName); 252 253 item["@odata.type"] = "#VirtualMedia.v1_3_0.VirtualMedia"; 254 item["Name"] = "Virtual Removable Media"; 255 item["Id"] = resName; 256 item["WriteProtected"] = true; 257 item["ConnectedVia"] = virtual_media::ConnectedVia::NotConnected; 258 item["MediaTypes"] = nlohmann::json::array_t({"CD", "USBStick"}); 259 item["TransferMethod"] = "Stream"; 260 item["Oem"]["OpenBMC"]["@odata.type"] = 261 "#OemVirtualMedia.v1_0_0.VirtualMedia"; 262 item["Oem"]["OpenBMC"]["@odata.id"] = boost::urls::format( 263 "/redfish/v1/Managers/{}/VirtualMedia/{}#/Oem/OpenBMC", name, resName); 264 265 return item; 266 } 267 268 /** 269 * @brief Fills collection data 270 */ 271 inline void getVmResourceList(std::shared_ptr<bmcweb::AsyncResp> asyncResp, 272 const std::string& service, 273 const std::string& name) 274 { 275 BMCWEB_LOG_DEBUG("Get available Virtual Media resources."); 276 sdbusplus::message::object_path objPath( 277 "/xyz/openbmc_project/VirtualMedia"); 278 dbus::utility::getManagedObjects( 279 service, objPath, 280 [name, asyncResp{std::move(asyncResp)}]( 281 const boost::system::error_code& ec, 282 const dbus::utility::ManagedObjectType& subtree) { 283 if (ec) 284 { 285 BMCWEB_LOG_DEBUG("DBUS response error"); 286 return; 287 } 288 nlohmann::json& members = asyncResp->res.jsonValue["Members"]; 289 members = nlohmann::json::array(); 290 291 for (const auto& object : subtree) 292 { 293 nlohmann::json item; 294 std::string path = object.first.filename(); 295 if (path.empty()) 296 { 297 continue; 298 } 299 300 item["@odata.id"] = boost::urls::format( 301 "/redfish/v1/Managers/{}/VirtualMedia/{}", name, path); 302 members.emplace_back(std::move(item)); 303 } 304 asyncResp->res.jsonValue["Members@odata.count"] = members.size(); 305 }); 306 } 307 308 inline void 309 afterGetVmData(const std::string& name, const std::string& /*service*/, 310 const std::string& resName, 311 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 312 const std::pair<sdbusplus::message::object_path, 313 dbus::utility::DBusInterfacesMap>& item) 314 { 315 VmMode mode = parseObjectPathAndGetMode(item.first, resName); 316 if (mode == VmMode::Invalid) 317 { 318 return; 319 } 320 321 asyncResp->res.jsonValue = vmItemTemplate(name, resName); 322 323 // Check if dbus path is Legacy type 324 if (mode == VmMode::Legacy) 325 { 326 asyncResp->res.jsonValue["Actions"]["#VirtualMedia.InsertMedia"] 327 ["target"] = boost::urls::format( 328 "/redfish/v1/Managers/{}/VirtualMedia/{}/Actions/VirtualMedia.InsertMedia", 329 name, resName); 330 } 331 332 vmParseInterfaceObject(item.second, asyncResp); 333 334 asyncResp->res.jsonValue["Actions"]["#VirtualMedia.EjectMedia"] 335 ["target"] = boost::urls::format( 336 "/redfish/v1/Managers/{}/VirtualMedia/{}/Actions/VirtualMedia.EjectMedia", 337 name, resName); 338 } 339 340 /** 341 * @brief Fills data for specific resource 342 */ 343 inline void getVmData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 344 const std::string& service, const std::string& name, 345 const std::string& resName) 346 { 347 BMCWEB_LOG_DEBUG("Get Virtual Media resource data."); 348 349 findAndParseObject(service, resName, asyncResp, 350 std::bind_front(afterGetVmData, name)); 351 } 352 353 /** 354 * @brief Transfer protocols supported for InsertMedia action. 355 * 356 */ 357 enum class TransferProtocol 358 { 359 https, 360 smb, 361 invalid 362 }; 363 364 /** 365 * @brief Function extracts transfer protocol type from URI. 366 * 367 */ 368 inline std::optional<TransferProtocol> 369 getTransferProtocolFromUri(boost::urls::url_view imageUri) 370 { 371 std::string_view scheme = imageUri.scheme(); 372 if (scheme == "smb") 373 { 374 return TransferProtocol::smb; 375 } 376 if (scheme == "https") 377 { 378 return TransferProtocol::https; 379 } 380 if (!scheme.empty()) 381 { 382 return TransferProtocol::invalid; 383 } 384 385 return {}; 386 } 387 388 /** 389 * @brief Function convert transfer protocol from string param. 390 * 391 */ 392 inline std::optional<TransferProtocol> getTransferProtocolFromParam( 393 const std::optional<std::string>& transferProtocolType) 394 { 395 if (!transferProtocolType) 396 { 397 return {}; 398 } 399 400 if (*transferProtocolType == "CIFS") 401 { 402 return TransferProtocol::smb; 403 } 404 405 if (*transferProtocolType == "HTTPS") 406 { 407 return TransferProtocol::https; 408 } 409 410 return TransferProtocol::invalid; 411 } 412 413 /** 414 * @brief Function extends URI with transfer protocol type. 415 * 416 */ 417 inline std::string 418 getUriWithTransferProtocol(const std::string& imageUri, 419 const TransferProtocol& transferProtocol) 420 { 421 if (transferProtocol == TransferProtocol::smb) 422 { 423 return "smb://" + imageUri; 424 } 425 426 if (transferProtocol == TransferProtocol::https) 427 { 428 return "https://" + imageUri; 429 } 430 431 return imageUri; 432 } 433 434 struct InsertMediaActionParams 435 { 436 std::optional<std::string> imageUrl; 437 std::optional<std::string> userName; 438 std::optional<std::string> password; 439 std::optional<std::string> transferMethod; 440 std::optional<std::string> transferProtocolType; 441 std::optional<bool> writeProtected = true; 442 std::optional<bool> inserted; 443 }; 444 445 template <typename T> 446 static void secureCleanup(T& value) 447 { 448 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) 449 auto raw = const_cast<typename T::value_type*>(value.data()); 450 explicit_bzero(raw, value.size() * sizeof(*raw)); 451 } 452 453 class Credentials 454 { 455 public: 456 Credentials(std::string&& user, std::string&& password) : 457 userBuf(std::move(user)), passBuf(std::move(password)) 458 {} 459 460 ~Credentials() 461 { 462 secureCleanup(userBuf); 463 secureCleanup(passBuf); 464 } 465 466 const std::string& user() 467 { 468 return userBuf; 469 } 470 471 const std::string& password() 472 { 473 return passBuf; 474 } 475 476 Credentials() = delete; 477 Credentials(const Credentials&) = delete; 478 Credentials& operator=(const Credentials&) = delete; 479 Credentials(Credentials&&) = delete; 480 Credentials& operator=(Credentials&&) = delete; 481 482 private: 483 std::string userBuf; 484 std::string passBuf; 485 }; 486 487 class CredentialsProvider 488 { 489 public: 490 template <typename T> 491 struct Deleter 492 { 493 void operator()(T* buff) const 494 { 495 if (buff) 496 { 497 secureCleanup(*buff); 498 delete buff; 499 } 500 } 501 }; 502 503 using Buffer = std::vector<char>; 504 using SecureBuffer = std::unique_ptr<Buffer, Deleter<Buffer>>; 505 // Using explicit definition instead of std::function to avoid implicit 506 // conversions eg. stack copy instead of reference 507 using FormatterFunc = void(const std::string& username, 508 const std::string& password, Buffer& dest); 509 510 CredentialsProvider(std::string&& user, std::string&& password) : 511 credentials(std::move(user), std::move(password)) 512 {} 513 514 const std::string& user() 515 { 516 return credentials.user(); 517 } 518 519 const std::string& password() 520 { 521 return credentials.password(); 522 } 523 524 SecureBuffer pack(FormatterFunc* formatter) 525 { 526 SecureBuffer packed{new Buffer{}}; 527 if (formatter != nullptr) 528 { 529 formatter(credentials.user(), credentials.password(), *packed); 530 } 531 532 return packed; 533 } 534 535 private: 536 Credentials credentials; 537 }; 538 539 // Wrapper for boost::async_pipe ensuring proper pipe cleanup 540 class SecurePipe 541 { 542 public: 543 using unix_fd = sdbusplus::message::unix_fd; 544 545 SecurePipe(boost::asio::io_context& io, 546 CredentialsProvider::SecureBuffer&& bufferIn) : 547 impl(io), 548 buffer{std::move(bufferIn)} 549 {} 550 551 ~SecurePipe() 552 { 553 // Named pipe needs to be explicitly removed 554 impl.close(); 555 } 556 557 SecurePipe(const SecurePipe&) = delete; 558 SecurePipe(SecurePipe&&) = delete; 559 SecurePipe& operator=(const SecurePipe&) = delete; 560 SecurePipe& operator=(SecurePipe&&) = delete; 561 562 unix_fd fd() const 563 { 564 return unix_fd{impl.native_source()}; 565 } 566 567 template <typename WriteHandler> 568 void asyncWrite(WriteHandler&& handler) 569 { 570 impl.async_write_some(boost::asio::buffer(*buffer), 571 std::forward<WriteHandler>(handler)); 572 } 573 574 const std::string name; 575 boost::process::async_pipe impl; 576 CredentialsProvider::SecureBuffer buffer; 577 }; 578 579 /** 580 * @brief Function transceives data with dbus directly. 581 * 582 * All BMC state properties will be retrieved before sending reset request. 583 */ 584 inline void doMountVmLegacy(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 585 const std::string& service, const std::string& name, 586 const std::string& imageUrl, const bool rw, 587 std::string&& userName, std::string&& password) 588 { 589 constexpr const size_t secretLimit = 1024; 590 591 std::shared_ptr<SecurePipe> secretPipe; 592 dbus::utility::DbusVariantType unixFd = -1; 593 594 if (!userName.empty() || !password.empty()) 595 { 596 // Encapsulate in safe buffer 597 CredentialsProvider credentials(std::move(userName), 598 std::move(password)); 599 600 // Payload must contain data + NULL delimiters 601 if (credentials.user().size() + credentials.password().size() + 2 > 602 secretLimit) 603 { 604 BMCWEB_LOG_ERROR("Credentials too long to handle"); 605 messages::unrecognizedRequestBody(asyncResp->res); 606 return; 607 } 608 609 // Pack secret 610 auto secret = credentials.pack( 611 [](const auto& user, const auto& pass, auto& buff) { 612 std::ranges::copy(user, std::back_inserter(buff)); 613 buff.push_back('\0'); 614 std::ranges::copy(pass, std::back_inserter(buff)); 615 buff.push_back('\0'); 616 }); 617 618 // Open pipe 619 secretPipe = std::make_shared<SecurePipe>( 620 crow::connections::systemBus->get_io_context(), std::move(secret)); 621 unixFd = secretPipe->fd(); 622 623 // Pass secret over pipe 624 secretPipe->asyncWrite( 625 [asyncResp](const boost::system::error_code& ec, std::size_t) { 626 if (ec) 627 { 628 BMCWEB_LOG_ERROR("Failed to pass secret: {}", ec); 629 messages::internalError(asyncResp->res); 630 } 631 }); 632 } 633 634 crow::connections::systemBus->async_method_call( 635 [asyncResp, secretPipe](const boost::system::error_code& ec, 636 bool success) { 637 if (ec) 638 { 639 BMCWEB_LOG_ERROR("Bad D-Bus request error: {}", ec); 640 messages::internalError(asyncResp->res); 641 } 642 else if (!success) 643 { 644 BMCWEB_LOG_ERROR("Service responded with error"); 645 messages::generalError(asyncResp->res); 646 } 647 }, 648 service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name, 649 "xyz.openbmc_project.VirtualMedia.Legacy", "Mount", imageUrl, rw, 650 unixFd); 651 } 652 653 /** 654 * @brief Function validate parameters of insert media request. 655 * 656 */ 657 inline void validateParams(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 658 const std::string& service, 659 const std::string& resName, 660 InsertMediaActionParams& actionParams) 661 { 662 BMCWEB_LOG_DEBUG("Validation started"); 663 // required param imageUrl must not be empty 664 if (!actionParams.imageUrl) 665 { 666 BMCWEB_LOG_ERROR("Request action parameter Image is empty."); 667 668 messages::propertyValueFormatError(asyncResp->res, "<empty>", "Image"); 669 670 return; 671 } 672 673 // optional param inserted must be true 674 if (actionParams.inserted && !*actionParams.inserted) 675 { 676 BMCWEB_LOG_ERROR( 677 "Request action optional parameter Inserted must be true."); 678 679 messages::actionParameterNotSupported(asyncResp->res, "Inserted", 680 "InsertMedia"); 681 682 return; 683 } 684 685 // optional param transferMethod must be stream 686 if (actionParams.transferMethod && 687 (*actionParams.transferMethod != "Stream")) 688 { 689 BMCWEB_LOG_ERROR("Request action optional parameter " 690 "TransferMethod must be Stream."); 691 692 messages::actionParameterNotSupported(asyncResp->res, "TransferMethod", 693 "InsertMedia"); 694 695 return; 696 } 697 boost::urls::result<boost::urls::url_view> url = 698 boost::urls::parse_uri(*actionParams.imageUrl); 699 if (!url) 700 { 701 messages::actionParameterValueFormatError( 702 asyncResp->res, *actionParams.imageUrl, "Image", "InsertMedia"); 703 return; 704 } 705 std::optional<TransferProtocol> uriTransferProtocolType = 706 getTransferProtocolFromUri(*url); 707 708 std::optional<TransferProtocol> paramTransferProtocolType = 709 getTransferProtocolFromParam(actionParams.transferProtocolType); 710 711 // ImageUrl does not contain valid protocol type 712 if (uriTransferProtocolType && 713 *uriTransferProtocolType == TransferProtocol::invalid) 714 { 715 BMCWEB_LOG_ERROR("Request action parameter ImageUrl must " 716 "contain specified protocol type from list: " 717 "(smb, https)."); 718 719 messages::resourceAtUriInUnknownFormat(asyncResp->res, *url); 720 721 return; 722 } 723 724 // transferProtocolType should contain value from list 725 if (paramTransferProtocolType && 726 *paramTransferProtocolType == TransferProtocol::invalid) 727 { 728 BMCWEB_LOG_ERROR("Request action parameter TransferProtocolType " 729 "must be provided with value from list: " 730 "(CIFS, HTTPS)."); 731 732 messages::propertyValueNotInList( 733 asyncResp->res, actionParams.transferProtocolType.value_or(""), 734 "TransferProtocolType"); 735 return; 736 } 737 738 // valid transfer protocol not provided either with URI nor param 739 if (!uriTransferProtocolType && !paramTransferProtocolType) 740 { 741 BMCWEB_LOG_ERROR("Request action parameter ImageUrl must " 742 "contain specified protocol type or param " 743 "TransferProtocolType must be provided."); 744 745 messages::resourceAtUriInUnknownFormat(asyncResp->res, *url); 746 747 return; 748 } 749 750 // valid transfer protocol provided both with URI and param 751 if (paramTransferProtocolType && uriTransferProtocolType) 752 { 753 // check if protocol is the same for URI and param 754 if (*paramTransferProtocolType != *uriTransferProtocolType) 755 { 756 BMCWEB_LOG_ERROR("Request action parameter " 757 "TransferProtocolType must contain the " 758 "same protocol type as protocol type " 759 "provided with param imageUrl."); 760 761 messages::actionParameterValueTypeError( 762 asyncResp->res, actionParams.transferProtocolType.value_or(""), 763 "TransferProtocolType", "InsertMedia"); 764 765 return; 766 } 767 } 768 769 // validation passed, add protocol to URI if needed 770 if (!uriTransferProtocolType && paramTransferProtocolType) 771 { 772 actionParams.imageUrl = getUriWithTransferProtocol( 773 *actionParams.imageUrl, *paramTransferProtocolType); 774 } 775 776 if (!actionParams.userName) 777 { 778 actionParams.userName = ""; 779 } 780 781 if (!actionParams.password) 782 { 783 actionParams.password = ""; 784 } 785 786 doMountVmLegacy(asyncResp, service, resName, *actionParams.imageUrl, 787 !(actionParams.writeProtected.value_or(false)), 788 std::move(*actionParams.userName), 789 std::move(*actionParams.password)); 790 } 791 792 /** 793 * @brief Function transceives data with dbus directly. 794 * 795 * All BMC state properties will be retrieved before sending reset request. 796 */ 797 inline void doEjectAction(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 798 const std::string& service, const std::string& name, 799 bool legacy) 800 { 801 // Legacy mount requires parameter with image 802 if (legacy) 803 { 804 crow::connections::systemBus->async_method_call( 805 [asyncResp](const boost::system::error_code& ec) { 806 if (ec) 807 { 808 BMCWEB_LOG_ERROR("Bad D-Bus request error: {}", ec); 809 810 messages::internalError(asyncResp->res); 811 return; 812 } 813 }, 814 service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name, 815 "xyz.openbmc_project.VirtualMedia.Legacy", "Unmount"); 816 } 817 else // proxy 818 { 819 crow::connections::systemBus->async_method_call( 820 [asyncResp](const boost::system::error_code& ec) { 821 if (ec) 822 { 823 BMCWEB_LOG_ERROR("Bad D-Bus request error: {}", ec); 824 825 messages::internalError(asyncResp->res); 826 return; 827 } 828 }, 829 service, "/xyz/openbmc_project/VirtualMedia/Proxy/" + name, 830 "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount"); 831 } 832 } 833 834 inline void handleManagersVirtualMediaActionInsertPost( 835 crow::App& app, const crow::Request& req, 836 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 837 const std::string& name, const std::string& resName) 838 { 839 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 840 { 841 return; 842 } 843 844 constexpr std::string_view action = "VirtualMedia.InsertMedia"; 845 if (name != "bmc") 846 { 847 messages::resourceNotFound(asyncResp->res, action, resName); 848 849 return; 850 } 851 InsertMediaActionParams actionParams; 852 853 // Read obligatory parameters (url of image) 854 if (!json_util::readJsonAction( 855 req, asyncResp->res, "Image", actionParams.imageUrl, 856 "WriteProtected", actionParams.writeProtected, "UserName", 857 actionParams.userName, "Password", actionParams.password, 858 "Inserted", actionParams.inserted, "TransferMethod", 859 actionParams.transferMethod, "TransferProtocolType", 860 actionParams.transferProtocolType)) 861 { 862 return; 863 } 864 865 dbus::utility::getDbusObject( 866 "/xyz/openbmc_project/VirtualMedia", {}, 867 [asyncResp, action, actionParams, 868 resName](const boost::system::error_code& ec, 869 const dbus::utility::MapperGetObject& getObjectType) mutable { 870 if (ec) 871 { 872 BMCWEB_LOG_ERROR("ObjectMapper::GetObject call failed: {}", ec); 873 messages::resourceNotFound(asyncResp->res, action, resName); 874 875 return; 876 } 877 878 std::string service = getObjectType.begin()->first; 879 BMCWEB_LOG_DEBUG("GetObjectType: {}", service); 880 881 sdbusplus::message::object_path path( 882 "/xyz/openbmc_project/VirtualMedia"); 883 dbus::utility::getManagedObjects( 884 service, path, 885 [service, resName, action, actionParams, asyncResp]( 886 const boost::system::error_code& ec2, 887 const dbus::utility::ManagedObjectType& subtree) mutable { 888 if (ec2) 889 { 890 // Not possible in proxy mode 891 BMCWEB_LOG_DEBUG("InsertMedia not " 892 "allowed in proxy mode"); 893 messages::resourceNotFound(asyncResp->res, action, resName); 894 895 return; 896 } 897 for (const auto& object : subtree) 898 { 899 VmMode mode = parseObjectPathAndGetMode(object.first, resName); 900 if (mode == VmMode::Legacy) 901 { 902 validateParams(asyncResp, service, resName, actionParams); 903 904 return; 905 } 906 } 907 BMCWEB_LOG_DEBUG("Parent item not found"); 908 messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName); 909 }); 910 }); 911 } 912 913 inline void handleManagersVirtualMediaActionEject( 914 crow::App& app, const crow::Request& req, 915 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 916 const std::string& managerName, const std::string& resName) 917 { 918 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 919 { 920 return; 921 } 922 923 constexpr std::string_view action = "VirtualMedia.EjectMedia"; 924 if (managerName != "bmc") 925 { 926 messages::resourceNotFound(asyncResp->res, action, resName); 927 928 return; 929 } 930 931 dbus::utility::getDbusObject( 932 "/xyz/openbmc_project/VirtualMedia", {}, 933 [asyncResp, action, 934 resName](const boost::system::error_code& ec2, 935 const dbus::utility::MapperGetObject& getObjectType) { 936 if (ec2) 937 { 938 BMCWEB_LOG_ERROR("ObjectMapper::GetObject call failed: {}", ec2); 939 messages::internalError(asyncResp->res); 940 941 return; 942 } 943 std::string service = getObjectType.begin()->first; 944 BMCWEB_LOG_DEBUG("GetObjectType: {}", service); 945 946 sdbusplus::message::object_path path( 947 "/xyz/openbmc_project/VirtualMedia"); 948 dbus::utility::getManagedObjects( 949 service, path, 950 [resName, service, action, 951 asyncResp](const boost::system::error_code& ec, 952 const dbus::utility::ManagedObjectType& subtree) { 953 if (ec) 954 { 955 BMCWEB_LOG_ERROR("ObjectMapper : No Service found"); 956 messages::resourceNotFound(asyncResp->res, action, resName); 957 return; 958 } 959 960 for (const auto& object : subtree) 961 { 962 VmMode mode = parseObjectPathAndGetMode(object.first, resName); 963 if (mode != VmMode::Invalid) 964 { 965 doEjectAction(asyncResp, service, resName, 966 mode == VmMode::Legacy); 967 return; 968 } 969 } 970 BMCWEB_LOG_DEBUG("Parent item not found"); 971 messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName); 972 }); 973 }); 974 } 975 976 inline void handleManagersVirtualMediaCollectionGet( 977 crow::App& app, const crow::Request& req, 978 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 979 const std::string& name) 980 { 981 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 982 { 983 return; 984 } 985 if (name != "bmc") 986 { 987 messages::resourceNotFound(asyncResp->res, "VirtualMedia", name); 988 989 return; 990 } 991 992 asyncResp->res.jsonValue["@odata.type"] = 993 "#VirtualMediaCollection.VirtualMediaCollection"; 994 asyncResp->res.jsonValue["Name"] = "Virtual Media Services"; 995 asyncResp->res.jsonValue["@odata.id"] = 996 boost::urls::format("/redfish/v1/Managers/{}/VirtualMedia", name); 997 998 dbus::utility::getDbusObject( 999 "/xyz/openbmc_project/VirtualMedia", {}, 1000 [asyncResp, name](const boost::system::error_code& ec, 1001 const dbus::utility::MapperGetObject& getObjectType) { 1002 if (ec) 1003 { 1004 BMCWEB_LOG_ERROR("ObjectMapper::GetObject call failed: {}", ec); 1005 messages::internalError(asyncResp->res); 1006 1007 return; 1008 } 1009 std::string service = getObjectType.begin()->first; 1010 BMCWEB_LOG_DEBUG("GetObjectType: {}", service); 1011 1012 getVmResourceList(asyncResp, service, name); 1013 }); 1014 } 1015 1016 inline void 1017 handleVirtualMediaGet(crow::App& app, const crow::Request& req, 1018 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1019 const std::string& name, const std::string& resName) 1020 { 1021 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1022 { 1023 return; 1024 } 1025 if (name != "bmc") 1026 { 1027 messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName); 1028 1029 return; 1030 } 1031 1032 dbus::utility::getDbusObject( 1033 "/xyz/openbmc_project/VirtualMedia", {}, 1034 [asyncResp, name, 1035 resName](const boost::system::error_code& ec, 1036 const dbus::utility::MapperGetObject& getObjectType) { 1037 if (ec) 1038 { 1039 BMCWEB_LOG_ERROR("ObjectMapper::GetObject call failed: {}", ec); 1040 messages::internalError(asyncResp->res); 1041 1042 return; 1043 } 1044 std::string service = getObjectType.begin()->first; 1045 BMCWEB_LOG_DEBUG("GetObjectType: {}", service); 1046 1047 getVmData(asyncResp, service, name, resName); 1048 }); 1049 } 1050 1051 inline void requestNBDVirtualMediaRoutes(App& app) 1052 { 1053 BMCWEB_ROUTE( 1054 app, 1055 "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/VirtualMedia.InsertMedia") 1056 .privileges(redfish::privileges::postVirtualMedia) 1057 .methods(boost::beast::http::verb::post)(std::bind_front( 1058 handleManagersVirtualMediaActionInsertPost, std::ref(app))); 1059 1060 BMCWEB_ROUTE( 1061 app, 1062 "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/VirtualMedia.EjectMedia") 1063 .privileges(redfish::privileges::postVirtualMedia) 1064 .methods(boost::beast::http::verb::post)(std::bind_front( 1065 handleManagersVirtualMediaActionEject, std::ref(app))); 1066 1067 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/") 1068 .privileges(redfish::privileges::getVirtualMediaCollection) 1069 .methods(boost::beast::http::verb::get)(std::bind_front( 1070 handleManagersVirtualMediaCollectionGet, std::ref(app))); 1071 1072 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/<str>/") 1073 .privileges(redfish::privileges::getVirtualMedia) 1074 .methods(boost::beast::http::verb::get)( 1075 std::bind_front(handleVirtualMediaGet, std::ref(app))); 1076 } 1077 1078 } // namespace redfish 1079