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