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