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: " << itemPath.str 50 << ", ThisPath: " << thisPath; 51 52 if (thisPath.empty()) 53 { 54 return VmMode::Invalid; 55 } 56 57 if (thisPath != resName) 58 { 59 return VmMode::Invalid; 60 } 61 62 auto mode = itemPath.parent_path(); 63 auto type = mode.parent_path(); 64 65 if (mode.filename().empty() || type.filename().empty()) 66 { 67 return VmMode::Invalid; 68 } 69 70 if (type.filename() != "VirtualMedia") 71 { 72 return VmMode::Invalid; 73 } 74 std::string modeStr = mode.filename(); 75 if (modeStr == "Legacy") 76 { 77 return VmMode::Legacy; 78 } 79 if (modeStr == "Proxy") 80 { 81 return VmMode::Proxy; 82 } 83 return VmMode::Invalid; 84 } 85 86 using CheckItemHandler = 87 std::function<void(const std::string& service, const std::string& resName, 88 const std::shared_ptr<bmcweb::AsyncResp>&, 89 const std::pair<sdbusplus::message::object_path, 90 dbus::utility::DBusInteracesMap>&)>; 91 92 inline void findAndParseObject(const std::string& service, 93 const std::string& resName, 94 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 95 CheckItemHandler&& handler) 96 { 97 crow::connections::systemBus->async_method_call( 98 [service, resName, aResp, 99 handler](const boost::system::error_code& ec, 100 const dbus::utility::ManagedObjectType& subtree) { 101 if (ec) 102 { 103 BMCWEB_LOG_DEBUG << "DBUS response error"; 104 105 return; 106 } 107 108 for (const auto& item : subtree) 109 { 110 VmMode mode = parseObjectPathAndGetMode(item.first, resName); 111 if (mode != VmMode::Invalid) 112 { 113 handler(service, resName, aResp, item); 114 return; 115 } 116 } 117 118 BMCWEB_LOG_DEBUG << "Parent item not found"; 119 aResp->res.result(boost::beast::http::status::not_found); 120 }, 121 service, "/xyz/openbmc_project/VirtualMedia", 122 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 123 } 124 125 /** 126 * @brief Function extracts transfer protocol name from URI. 127 */ 128 inline std::string getTransferProtocolTypeFromUri(const std::string& imageUri) 129 { 130 boost::urls::result<boost::urls::url_view> url = 131 boost::urls::parse_uri(imageUri); 132 if (!url) 133 { 134 return "None"; 135 } 136 std::string_view scheme = url->scheme(); 137 if (scheme == "smb") 138 { 139 return "CIFS"; 140 } 141 if (scheme == "https") 142 { 143 return "HTTPS"; 144 } 145 146 return "None"; 147 } 148 149 /** 150 * @brief Read all known properties from VM object interfaces 151 */ 152 inline void 153 vmParseInterfaceObject(const dbus::utility::DBusInteracesMap& interfaces, 154 const std::shared_ptr<bmcweb::AsyncResp>& aResp) 155 { 156 for (const auto& [interface, values] : interfaces) 157 { 158 if (interface == "xyz.openbmc_project.VirtualMedia.MountPoint") 159 { 160 for (const auto& [property, value] : values) 161 { 162 if (property == "EndpointId") 163 { 164 const std::string* endpointIdValue = 165 std::get_if<std::string>(&value); 166 if (endpointIdValue == nullptr) 167 { 168 continue; 169 } 170 if (!endpointIdValue->empty()) 171 { 172 // Proxy mode 173 aResp->res 174 .jsonValue["Oem"]["OpenBMC"]["WebSocketEndpoint"] = 175 *endpointIdValue; 176 aResp->res.jsonValue["TransferProtocolType"] = "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 aResp->res.jsonValue["ImageName"] = ""; 191 } 192 else 193 { 194 aResp->res.jsonValue["ImageName"] = 195 filePath.filename(); 196 } 197 198 aResp->res.jsonValue["Image"] = *imageUrlValue; 199 aResp->res.jsonValue["TransferProtocolType"] = 200 getTransferProtocolTypeFromUri(*imageUrlValue); 201 202 aResp->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 aResp->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 aResp->res.jsonValue["Inserted"] = *activeValue; 230 231 if (*activeValue) 232 { 233 aResp->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 262 return item; 263 } 264 265 /** 266 * @brief Fills collection data 267 */ 268 inline void getVmResourceList(std::shared_ptr<bmcweb::AsyncResp> aResp, 269 const std::string& service, 270 const std::string& name) 271 { 272 BMCWEB_LOG_DEBUG << "Get available Virtual Media resources."; 273 crow::connections::systemBus->async_method_call( 274 [name, aResp{std::move(aResp)}]( 275 const boost::system::error_code& ec, 276 const dbus::utility::ManagedObjectType& subtree) { 277 if (ec) 278 { 279 BMCWEB_LOG_DEBUG << "DBUS response error"; 280 return; 281 } 282 nlohmann::json& members = aResp->res.jsonValue["Members"]; 283 members = nlohmann::json::array(); 284 285 for (const auto& object : subtree) 286 { 287 nlohmann::json item; 288 std::string path = object.first.filename(); 289 if (path.empty()) 290 { 291 continue; 292 } 293 294 item["@odata.id"] = boost::urls::format( 295 "/redfish/v1/Managers/{}/VirtualMedia/{}", name, path); 296 members.emplace_back(std::move(item)); 297 } 298 aResp->res.jsonValue["Members@odata.count"] = members.size(); 299 }, 300 service, "/xyz/openbmc_project/VirtualMedia", 301 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 302 } 303 304 inline void 305 afterGetVmData(const std::string& name, const std::string& /*service*/, 306 const std::string& resName, 307 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 308 const std::pair<sdbusplus::message::object_path, 309 dbus::utility::DBusInteracesMap>& item) 310 { 311 VmMode mode = parseObjectPathAndGetMode(item.first, resName); 312 if (mode == VmMode::Invalid) 313 { 314 return; 315 } 316 317 asyncResp->res.jsonValue = vmItemTemplate(name, resName); 318 319 // Check if dbus path is Legacy type 320 if (mode == VmMode::Legacy) 321 { 322 asyncResp->res.jsonValue["Actions"]["#VirtualMedia.InsertMedia"] 323 ["target"] = boost::urls::format( 324 "/redfish/v1/Managers/{}/VirtualMedia/{}/Actions/VirtualMedia.InsertMedia", 325 name, resName); 326 } 327 328 vmParseInterfaceObject(item.second, asyncResp); 329 330 asyncResp->res.jsonValue["Actions"]["#VirtualMedia.EjectMedia"] 331 ["target"] = boost::urls::format( 332 "/redfish/v1/Managers/{}/VirtualMedia/{}/Actions/VirtualMedia.EjectMedia", 333 name, resName); 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"] = 990 boost::urls::format("/redfish/v1/Managers/{}/VirtualMedia", name); 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