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