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) 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 && !*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 && 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 && 712 *uriTransferProtocolType == TransferProtocol::invalid) 713 { 714 BMCWEB_LOG_ERROR("Request action parameter ImageUrl must " 715 "contain specified protocol type from list: " 716 "(smb, https)."); 717 718 messages::resourceAtUriInUnknownFormat(asyncResp->res, *url); 719 720 return; 721 } 722 723 // transferProtocolType should contain value from list 724 if (paramTransferProtocolType && 725 *paramTransferProtocolType == TransferProtocol::invalid) 726 { 727 BMCWEB_LOG_ERROR("Request action parameter TransferProtocolType " 728 "must be provided with value from list: " 729 "(CIFS, HTTPS)."); 730 731 messages::propertyValueNotInList( 732 asyncResp->res, actionParams.transferProtocolType.value_or(""), 733 "TransferProtocolType"); 734 return; 735 } 736 737 // valid transfer protocol not provided either with URI nor param 738 if (!uriTransferProtocolType && !paramTransferProtocolType) 739 { 740 BMCWEB_LOG_ERROR("Request action parameter ImageUrl must " 741 "contain specified protocol type or param " 742 "TransferProtocolType must be provided."); 743 744 messages::resourceAtUriInUnknownFormat(asyncResp->res, *url); 745 746 return; 747 } 748 749 // valid transfer protocol provided both with URI and param 750 if (paramTransferProtocolType && uriTransferProtocolType) 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.value_or(""), 762 "TransferProtocolType", "InsertMedia"); 763 764 return; 765 } 766 } 767 if (!paramTransferProtocolType) 768 { 769 messages::internalError(asyncResp->res); 770 return; 771 } 772 773 // validation passed, add protocol to URI if needed 774 if (!uriTransferProtocolType) 775 { 776 actionParams.imageUrl = getUriWithTransferProtocol( 777 *actionParams.imageUrl, *paramTransferProtocolType); 778 } 779 780 if (!actionParams.userName) 781 { 782 actionParams.userName = ""; 783 } 784 785 if (!actionParams.password) 786 { 787 actionParams.password = ""; 788 } 789 790 doMountVmLegacy(asyncResp, service, resName, *actionParams.imageUrl, 791 !(actionParams.writeProtected.value_or(false)), 792 std::move(*actionParams.userName), 793 std::move(*actionParams.password)); 794 } 795 796 /** 797 * @brief Function transceives data with dbus directly. 798 * 799 * All BMC state properties will be retrieved before sending reset request. 800 */ 801 inline void doEjectAction(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 802 const std::string& service, const std::string& name, 803 bool legacy) 804 { 805 // Legacy mount requires parameter with image 806 if (legacy) 807 { 808 crow::connections::systemBus->async_method_call( 809 [asyncResp](const boost::system::error_code& ec) { 810 if (ec) 811 { 812 BMCWEB_LOG_ERROR("Bad D-Bus request error: {}", ec); 813 814 messages::internalError(asyncResp->res); 815 return; 816 } 817 }, 818 service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name, 819 "xyz.openbmc_project.VirtualMedia.Legacy", "Unmount"); 820 } 821 else // proxy 822 { 823 crow::connections::systemBus->async_method_call( 824 [asyncResp](const boost::system::error_code& ec) { 825 if (ec) 826 { 827 BMCWEB_LOG_ERROR("Bad D-Bus request error: {}", ec); 828 829 messages::internalError(asyncResp->res); 830 return; 831 } 832 }, 833 service, "/xyz/openbmc_project/VirtualMedia/Proxy/" + name, 834 "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount"); 835 } 836 } 837 838 inline void handleManagersVirtualMediaActionInsertPost( 839 crow::App& app, const crow::Request& req, 840 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 841 const std::string& name, const std::string& resName) 842 { 843 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 844 { 845 return; 846 } 847 848 constexpr std::string_view action = "VirtualMedia.InsertMedia"; 849 if (name != "bmc") 850 { 851 messages::resourceNotFound(asyncResp->res, action, resName); 852 853 return; 854 } 855 InsertMediaActionParams actionParams; 856 857 // Read obligatory parameters (url of image) 858 if (!json_util::readJsonAction( 859 req, asyncResp->res, "Image", actionParams.imageUrl, 860 "WriteProtected", actionParams.writeProtected, "UserName", 861 actionParams.userName, "Password", actionParams.password, 862 "Inserted", actionParams.inserted, "TransferMethod", 863 actionParams.transferMethod, "TransferProtocolType", 864 actionParams.transferProtocolType)) 865 { 866 return; 867 } 868 869 dbus::utility::getDbusObject( 870 "/xyz/openbmc_project/VirtualMedia", {}, 871 [asyncResp, action, actionParams, 872 resName](const boost::system::error_code& ec, 873 const dbus::utility::MapperGetObject& getObjectType) mutable { 874 if (ec) 875 { 876 BMCWEB_LOG_ERROR("ObjectMapper::GetObject call failed: {}", ec); 877 messages::resourceNotFound(asyncResp->res, action, resName); 878 879 return; 880 } 881 882 std::string service = getObjectType.begin()->first; 883 BMCWEB_LOG_DEBUG("GetObjectType: {}", service); 884 885 sdbusplus::message::object_path path( 886 "/xyz/openbmc_project/VirtualMedia"); 887 dbus::utility::getManagedObjects( 888 service, path, 889 [service, resName, action, actionParams, asyncResp]( 890 const boost::system::error_code& ec2, 891 const dbus::utility::ManagedObjectType& subtree) mutable { 892 if (ec2) 893 { 894 // Not possible in proxy mode 895 BMCWEB_LOG_DEBUG("InsertMedia not " 896 "allowed in proxy mode"); 897 messages::resourceNotFound(asyncResp->res, action, resName); 898 899 return; 900 } 901 for (const auto& object : subtree) 902 { 903 VmMode mode = parseObjectPathAndGetMode(object.first, resName); 904 if (mode == VmMode::Legacy) 905 { 906 validateParams(asyncResp, service, resName, actionParams); 907 908 return; 909 } 910 } 911 BMCWEB_LOG_DEBUG("Parent item not found"); 912 messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName); 913 }); 914 }); 915 } 916 917 inline void handleManagersVirtualMediaActionEject( 918 crow::App& app, const crow::Request& req, 919 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 920 const std::string& managerName, const std::string& resName) 921 { 922 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 923 { 924 return; 925 } 926 927 constexpr std::string_view action = "VirtualMedia.EjectMedia"; 928 if (managerName != "bmc") 929 { 930 messages::resourceNotFound(asyncResp->res, action, resName); 931 932 return; 933 } 934 935 dbus::utility::getDbusObject( 936 "/xyz/openbmc_project/VirtualMedia", {}, 937 [asyncResp, action, 938 resName](const boost::system::error_code& ec2, 939 const dbus::utility::MapperGetObject& getObjectType) { 940 if (ec2) 941 { 942 BMCWEB_LOG_ERROR("ObjectMapper::GetObject call failed: {}", ec2); 943 messages::internalError(asyncResp->res); 944 945 return; 946 } 947 std::string service = getObjectType.begin()->first; 948 BMCWEB_LOG_DEBUG("GetObjectType: {}", service); 949 950 sdbusplus::message::object_path path( 951 "/xyz/openbmc_project/VirtualMedia"); 952 dbus::utility::getManagedObjects( 953 service, path, 954 [resName, service, action, 955 asyncResp](const boost::system::error_code& ec, 956 const dbus::utility::ManagedObjectType& subtree) { 957 if (ec) 958 { 959 BMCWEB_LOG_ERROR("ObjectMapper : No Service found"); 960 messages::resourceNotFound(asyncResp->res, action, resName); 961 return; 962 } 963 964 for (const auto& object : subtree) 965 { 966 VmMode mode = parseObjectPathAndGetMode(object.first, resName); 967 if (mode != VmMode::Invalid) 968 { 969 doEjectAction(asyncResp, service, resName, 970 mode == VmMode::Legacy); 971 return; 972 } 973 } 974 BMCWEB_LOG_DEBUG("Parent item not found"); 975 messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName); 976 }); 977 }); 978 } 979 980 inline void handleManagersVirtualMediaCollectionGet( 981 crow::App& app, const crow::Request& req, 982 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 983 const std::string& name) 984 { 985 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 986 { 987 return; 988 } 989 if (name != "bmc") 990 { 991 messages::resourceNotFound(asyncResp->res, "VirtualMedia", name); 992 993 return; 994 } 995 996 asyncResp->res.jsonValue["@odata.type"] = 997 "#VirtualMediaCollection.VirtualMediaCollection"; 998 asyncResp->res.jsonValue["Name"] = "Virtual Media Services"; 999 asyncResp->res.jsonValue["@odata.id"] = 1000 boost::urls::format("/redfish/v1/Managers/{}/VirtualMedia", name); 1001 1002 dbus::utility::getDbusObject( 1003 "/xyz/openbmc_project/VirtualMedia", {}, 1004 [asyncResp, name](const boost::system::error_code& ec, 1005 const dbus::utility::MapperGetObject& getObjectType) { 1006 if (ec) 1007 { 1008 BMCWEB_LOG_ERROR("ObjectMapper::GetObject call failed: {}", ec); 1009 messages::internalError(asyncResp->res); 1010 1011 return; 1012 } 1013 std::string service = getObjectType.begin()->first; 1014 BMCWEB_LOG_DEBUG("GetObjectType: {}", service); 1015 1016 getVmResourceList(asyncResp, service, name); 1017 }); 1018 } 1019 1020 inline void 1021 handleVirtualMediaGet(crow::App& app, const crow::Request& req, 1022 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1023 const std::string& name, const std::string& resName) 1024 { 1025 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1026 { 1027 return; 1028 } 1029 if (name != "bmc") 1030 { 1031 messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName); 1032 1033 return; 1034 } 1035 1036 dbus::utility::getDbusObject( 1037 "/xyz/openbmc_project/VirtualMedia", {}, 1038 [asyncResp, name, 1039 resName](const boost::system::error_code& ec, 1040 const dbus::utility::MapperGetObject& getObjectType) { 1041 if (ec) 1042 { 1043 BMCWEB_LOG_ERROR("ObjectMapper::GetObject call failed: {}", ec); 1044 messages::internalError(asyncResp->res); 1045 1046 return; 1047 } 1048 std::string service = getObjectType.begin()->first; 1049 BMCWEB_LOG_DEBUG("GetObjectType: {}", service); 1050 1051 getVmData(asyncResp, service, name, resName); 1052 }); 1053 } 1054 1055 inline void requestNBDVirtualMediaRoutes(App& app) 1056 { 1057 BMCWEB_ROUTE( 1058 app, 1059 "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/VirtualMedia.InsertMedia") 1060 .privileges(redfish::privileges::postVirtualMedia) 1061 .methods(boost::beast::http::verb::post)(std::bind_front( 1062 handleManagersVirtualMediaActionInsertPost, std::ref(app))); 1063 1064 BMCWEB_ROUTE( 1065 app, 1066 "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/VirtualMedia.EjectMedia") 1067 .privileges(redfish::privileges::postVirtualMedia) 1068 .methods(boost::beast::http::verb::post)(std::bind_front( 1069 handleManagersVirtualMediaActionEject, std::ref(app))); 1070 1071 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/") 1072 .privileges(redfish::privileges::getVirtualMediaCollection) 1073 .methods(boost::beast::http::verb::get)(std::bind_front( 1074 handleManagersVirtualMediaCollectionGet, std::ref(app))); 1075 1076 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/<str>/") 1077 .privileges(redfish::privileges::getVirtualMedia) 1078 .methods(boost::beast::http::verb::get)( 1079 std::bind_front(handleVirtualMediaGet, std::ref(app))); 1080 } 1081 1082 } // namespace redfish 1083