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