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 278 .jsonValue["Actions"]["#VirtualMedia.EjectMedia"]["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, "VirtualMedia.Insert", 792 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, "UserName", 803 actionParams.userName, "Password", actionParams.password, 804 "Inserted", actionParams.inserted, "TransferMethod", 805 actionParams.transferMethod, "TransferProtocolType", 806 actionParams.transferProtocolType)) 807 { 808 BMCWEB_LOG_DEBUG << "Image is not provided"; 809 return; 810 } 811 812 bool paramsValid = validateParams( 813 asyncResp->res, actionParams.imageUrl, actionParams.inserted, 814 actionParams.transferMethod, actionParams.transferProtocolType); 815 816 if (!paramsValid) 817 { 818 return; 819 } 820 821 crow::connections::systemBus->async_method_call( 822 [asyncResp, actionParams, resName]( 823 const boost::system::error_code ec, 824 const dbus::utility::MapperGetObject& getObjectType) mutable { 825 if (ec) 826 { 827 BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " 828 << ec; 829 messages::internalError(asyncResp->res); 830 831 return; 832 } 833 std::string service = getObjectType.begin()->first; 834 BMCWEB_LOG_DEBUG << "GetObjectType: " << service; 835 836 crow::connections::systemBus->async_method_call( 837 [service, resName, actionParams, 838 asyncResp](const boost::system::error_code ec, 839 dbus::utility::ManagedObjectType& 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&>(object.first); 851 852 std::size_t lastIndex = path.rfind('/'); 853 if (lastIndex == std::string::npos) 854 { 855 continue; 856 } 857 858 lastIndex += 1; 859 860 if (path.substr(lastIndex) == resName) 861 { 862 lastIndex = path.rfind("Proxy"); 863 if (lastIndex != std::string::npos) 864 { 865 // Not possible in proxy mode 866 BMCWEB_LOG_DEBUG << "InsertMedia not " 867 "allowed in proxy mode"; 868 messages::resourceNotFound( 869 asyncResp->res, "VirtualMedia.InsertMedia", 870 resName); 871 872 return; 873 } 874 875 lastIndex = path.rfind("Legacy"); 876 if (lastIndex == std::string::npos) 877 { 878 continue; 879 } 880 881 // manager is irrelevant for 882 // VirtualMedia dbus calls 883 doMountVmLegacy(asyncResp, service, resName, 884 actionParams.imageUrl, 885 !(*actionParams.writeProtected), 886 std::move(*actionParams.userName), 887 std::move(*actionParams.password)); 888 889 return; 890 } 891 } 892 BMCWEB_LOG_DEBUG << "Parent item not found"; 893 messages::resourceNotFound(asyncResp->res, "VirtualMedia", 894 resName); 895 }, 896 service, "/xyz/openbmc_project/VirtualMedia", 897 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 898 }, 899 "xyz.openbmc_project.ObjectMapper", 900 "/xyz/openbmc_project/object_mapper", 901 "xyz.openbmc_project.ObjectMapper", "GetObject", 902 "/xyz/openbmc_project/VirtualMedia", std::array<const char*, 0>()); 903 }); 904 905 BMCWEB_ROUTE( 906 app, 907 "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/VirtualMedia.EjectMedia") 908 .privileges(redfish::privileges::postVirtualMedia) 909 .methods(boost::beast::http::verb::post)( 910 [&app](const crow::Request& req, 911 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 912 const std::string& name, const std::string& resName) { 913 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 914 { 915 return; 916 } 917 if (name != "bmc") 918 { 919 messages::resourceNotFound(asyncResp->res, "VirtualMedia.Eject", 920 resName); 921 922 return; 923 } 924 925 crow::connections::systemBus->async_method_call( 926 [asyncResp, 927 resName](const boost::system::error_code ec, 928 const dbus::utility::MapperGetObject& getObjectType) { 929 if (ec) 930 { 931 BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " 932 << ec; 933 messages::internalError(asyncResp->res); 934 935 return; 936 } 937 std::string service = getObjectType.begin()->first; 938 BMCWEB_LOG_DEBUG << "GetObjectType: " << service; 939 940 crow::connections::systemBus->async_method_call( 941 [resName, service, asyncResp{asyncResp}]( 942 const boost::system::error_code ec, 943 dbus::utility::ManagedObjectType& subtree) { 944 if (ec) 945 { 946 BMCWEB_LOG_DEBUG << "DBUS response error"; 947 948 return; 949 } 950 951 for (const auto& object : subtree) 952 { 953 const std::string& path = 954 static_cast<const std::string&>(object.first); 955 956 std::size_t lastIndex = path.rfind('/'); 957 if (lastIndex == std::string::npos) 958 { 959 continue; 960 } 961 962 lastIndex += 1; 963 964 if (path.substr(lastIndex) == resName) 965 { 966 lastIndex = path.rfind("Proxy"); 967 if (lastIndex != std::string::npos) 968 { 969 // Proxy mode 970 doVmAction(asyncResp, service, resName, false); 971 } 972 973 lastIndex = path.rfind("Legacy"); 974 if (lastIndex != std::string::npos) 975 { 976 // Legacy mode 977 doVmAction(asyncResp, service, resName, true); 978 } 979 980 return; 981 } 982 } 983 BMCWEB_LOG_DEBUG << "Parent item not found"; 984 messages::resourceNotFound(asyncResp->res, "VirtualMedia", 985 resName); 986 }, 987 service, "/xyz/openbmc_project/VirtualMedia", 988 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 989 }, 990 "xyz.openbmc_project.ObjectMapper", 991 "/xyz/openbmc_project/object_mapper", 992 "xyz.openbmc_project.ObjectMapper", "GetObject", 993 "/xyz/openbmc_project/VirtualMedia", std::array<const char*, 0>()); 994 }); 995 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/") 996 .privileges(redfish::privileges::getVirtualMediaCollection) 997 .methods(boost::beast::http::verb::get)( 998 [&app](const crow::Request& req, 999 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1000 const std::string& name) { 1001 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 1002 { 1003 return; 1004 } 1005 if (name != "bmc") 1006 { 1007 messages::resourceNotFound(asyncResp->res, "VirtualMedia", name); 1008 1009 return; 1010 } 1011 1012 asyncResp->res.jsonValue["@odata.type"] = 1013 "#VirtualMediaCollection.VirtualMediaCollection"; 1014 asyncResp->res.jsonValue["Name"] = "Virtual Media Services"; 1015 asyncResp->res.jsonValue["@odata.id"] = 1016 "/redfish/v1/Managers/" + name + "/VirtualMedia"; 1017 1018 crow::connections::systemBus->async_method_call( 1019 [asyncResp, 1020 name](const boost::system::error_code ec, 1021 const dbus::utility::MapperGetObject& getObjectType) { 1022 if (ec) 1023 { 1024 BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " 1025 << ec; 1026 messages::internalError(asyncResp->res); 1027 1028 return; 1029 } 1030 std::string service = getObjectType.begin()->first; 1031 BMCWEB_LOG_DEBUG << "GetObjectType: " << service; 1032 1033 getVmResourceList(asyncResp, service, name); 1034 }, 1035 "xyz.openbmc_project.ObjectMapper", 1036 "/xyz/openbmc_project/object_mapper", 1037 "xyz.openbmc_project.ObjectMapper", "GetObject", 1038 "/xyz/openbmc_project/VirtualMedia", std::array<const char*, 0>()); 1039 }); 1040 1041 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/<str>/") 1042 .privileges(redfish::privileges::getVirtualMedia) 1043 .methods(boost::beast::http::verb::get)( 1044 [&app](const crow::Request& req, 1045 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1046 const std::string& name, const std::string& resName) { 1047 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 1048 { 1049 return; 1050 } 1051 if (name != "bmc") 1052 { 1053 messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName); 1054 1055 return; 1056 } 1057 1058 crow::connections::systemBus->async_method_call( 1059 [asyncResp, name, 1060 resName](const boost::system::error_code ec, 1061 const dbus::utility::MapperGetObject& getObjectType) { 1062 if (ec) 1063 { 1064 BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " 1065 << ec; 1066 messages::internalError(asyncResp->res); 1067 1068 return; 1069 } 1070 std::string service = getObjectType.begin()->first; 1071 BMCWEB_LOG_DEBUG << "GetObjectType: " << service; 1072 1073 getVmData(asyncResp, service, name, resName); 1074 }, 1075 "xyz.openbmc_project.ObjectMapper", 1076 "/xyz/openbmc_project/object_mapper", 1077 "xyz.openbmc_project.ObjectMapper", "GetObject", 1078 "/xyz/openbmc_project/VirtualMedia", std::array<const char*, 0>()); 1079 }); 1080 } 1081 1082 } // namespace redfish 1083