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