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