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