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