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 "dbus_utility.hpp" 21 #include "query.hpp" 22 #include "registries/privilege_registry.hpp" 23 #include "utils/json_utils.hpp" 24 25 #include <boost/process/async_pipe.hpp> 26 #include <boost/type_traits/has_dereference.hpp> 27 #include <boost/url/url_view.hpp> 28 29 #include <array> 30 #include <string_view> 31 32 namespace redfish 33 { 34 /** 35 * @brief Function extracts transfer protocol name from URI. 36 */ 37 inline std::string getTransferProtocolTypeFromUri(const std::string& imageUri) 38 { 39 boost::urls::result<boost::urls::url_view> url = 40 boost::urls::parse_uri(imageUri); 41 if (!url) 42 { 43 return "None"; 44 } 45 std::string_view scheme = url->scheme(); 46 if (scheme == "smb") 47 { 48 return "CIFS"; 49 } 50 if (scheme == "https") 51 { 52 return "HTTPS"; 53 } 54 55 return "None"; 56 } 57 58 /** 59 * @brief Read all known properties from VM object interfaces 60 */ 61 inline void 62 vmParseInterfaceObject(const dbus::utility::DBusInteracesMap& interfaces, 63 const std::shared_ptr<bmcweb::AsyncResp>& aResp) 64 { 65 for (const auto& [interface, values] : interfaces) 66 { 67 if (interface == "xyz.openbmc_project.VirtualMedia.MountPoint") 68 { 69 for (const auto& [property, value] : values) 70 { 71 if (property == "EndpointId") 72 { 73 const std::string* endpointIdValue = 74 std::get_if<std::string>(&value); 75 if (endpointIdValue == nullptr) 76 { 77 continue; 78 } 79 if (!endpointIdValue->empty()) 80 { 81 // Proxy mode 82 aResp->res 83 .jsonValue["Oem"]["OpenBMC"]["WebSocketEndpoint"] = 84 *endpointIdValue; 85 aResp->res.jsonValue["TransferProtocolType"] = "OEM"; 86 } 87 } 88 if (property == "ImageURL") 89 { 90 const std::string* imageUrlValue = 91 std::get_if<std::string>(&value); 92 if (imageUrlValue != nullptr && !imageUrlValue->empty()) 93 { 94 std::filesystem::path filePath = *imageUrlValue; 95 if (!filePath.has_filename()) 96 { 97 // this will handle https share, which not 98 // necessarily has to have filename given. 99 aResp->res.jsonValue["ImageName"] = ""; 100 } 101 else 102 { 103 aResp->res.jsonValue["ImageName"] = 104 filePath.filename(); 105 } 106 107 aResp->res.jsonValue["Image"] = *imageUrlValue; 108 aResp->res.jsonValue["TransferProtocolType"] = 109 getTransferProtocolTypeFromUri(*imageUrlValue); 110 111 aResp->res.jsonValue["ConnectedVia"] = "URI"; 112 } 113 } 114 if (property == "WriteProtected") 115 { 116 const bool* writeProtectedValue = std::get_if<bool>(&value); 117 if (writeProtectedValue != nullptr) 118 { 119 aResp->res.jsonValue["WriteProtected"] = 120 *writeProtectedValue; 121 } 122 } 123 } 124 } 125 if (interface == "xyz.openbmc_project.VirtualMedia.Process") 126 { 127 for (const auto& [property, value] : values) 128 { 129 if (property == "Active") 130 { 131 const bool* activeValue = std::get_if<bool>(&value); 132 if (activeValue == nullptr) 133 { 134 BMCWEB_LOG_DEBUG << "Value Active not found"; 135 return; 136 } 137 aResp->res.jsonValue["Inserted"] = *activeValue; 138 139 if (*activeValue) 140 { 141 aResp->res.jsonValue["ConnectedVia"] = "Applet"; 142 } 143 } 144 } 145 } 146 } 147 } 148 149 /** 150 * @brief Fill template for Virtual Media Item. 151 */ 152 inline nlohmann::json vmItemTemplate(const std::string& name, 153 const std::string& resName) 154 { 155 nlohmann::json item; 156 item["@odata.id"] = crow::utility::urlFromPieces( 157 "redfish", "v1", "Managers", name, "VirtualMedia", resName); 158 159 item["@odata.type"] = "#VirtualMedia.v1_3_0.VirtualMedia"; 160 item["Name"] = "Virtual Removable Media"; 161 item["Id"] = resName; 162 item["WriteProtected"] = true; 163 item["MediaTypes"] = nlohmann::json::array_t({"CD", "USBStick"}); 164 item["TransferMethod"] = "Stream"; 165 item["Oem"]["OpenBMC"]["@odata.type"] = 166 "#OemVirtualMedia.v1_0_0.VirtualMedia"; 167 168 return item; 169 } 170 171 /** 172 * @brief Fills collection data 173 */ 174 inline void getVmResourceList(std::shared_ptr<bmcweb::AsyncResp> aResp, 175 const std::string& service, 176 const std::string& name) 177 { 178 BMCWEB_LOG_DEBUG << "Get available Virtual Media resources."; 179 crow::connections::systemBus->async_method_call( 180 [name, aResp{std::move(aResp)}]( 181 const boost::system::error_code ec, 182 const dbus::utility::ManagedObjectType& subtree) { 183 if (ec) 184 { 185 BMCWEB_LOG_DEBUG << "DBUS response error"; 186 return; 187 } 188 nlohmann::json& members = aResp->res.jsonValue["Members"]; 189 members = nlohmann::json::array(); 190 191 for (const auto& object : subtree) 192 { 193 nlohmann::json item; 194 std::string path = object.first.filename(); 195 if (path.empty()) 196 { 197 continue; 198 } 199 200 item["@odata.id"] = crow::utility::urlFromPieces( 201 "redfish", "v1", "Managers", name, "VirtualMedia", path); 202 members.emplace_back(std::move(item)); 203 } 204 aResp->res.jsonValue["Members@odata.count"] = members.size(); 205 }, 206 service, "/xyz/openbmc_project/VirtualMedia", 207 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 208 } 209 210 /** 211 * @brief Fills data for specific resource 212 */ 213 inline void getVmData(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 214 const std::string& service, const std::string& name, 215 const std::string& resName) 216 { 217 BMCWEB_LOG_DEBUG << "Get Virtual Media resource data."; 218 219 crow::connections::systemBus->async_method_call( 220 [resName, name, 221 aResp](const boost::system::error_code ec, 222 const dbus::utility::ManagedObjectType& subtree) { 223 if (ec) 224 { 225 BMCWEB_LOG_DEBUG << "DBUS response error"; 226 227 return; 228 } 229 230 for (const auto& item : subtree) 231 { 232 std::string thispath = item.first.filename(); 233 if (thispath.empty()) 234 { 235 continue; 236 } 237 238 if (thispath != resName) 239 { 240 continue; 241 } 242 243 // "Legacy"/"Proxy" 244 auto mode = item.first.parent_path(); 245 // "VirtualMedia" 246 auto type = mode.parent_path(); 247 if (mode.filename().empty() || type.filename().empty()) 248 { 249 continue; 250 } 251 252 if (type.filename() != "VirtualMedia") 253 { 254 continue; 255 } 256 257 aResp->res.jsonValue = vmItemTemplate(name, resName); 258 259 // Check if dbus path is Legacy type 260 if (mode.filename() == "Legacy") 261 { 262 aResp->res.jsonValue["Actions"]["#VirtualMedia.InsertMedia"] 263 ["target"] = crow::utility::urlFromPieces( 264 "redfish", "v1", "Managers", name, "VirtualMedia", resName, 265 "Actions", "VirtualMedia.InsertMedia"); 266 } 267 268 vmParseInterfaceObject(item.second, aResp); 269 270 aResp->res 271 .jsonValue["Actions"]["#VirtualMedia.EjectMedia"]["target"] = 272 crow::utility::urlFromPieces("redfish", "v1", "Managers", name, 273 "VirtualMedia", resName, "Actions", 274 "VirtualMedia.EjectMedia"); 275 return; 276 } 277 278 messages::resourceNotFound(aResp->res, "VirtualMedia", resName); 279 }, 280 service, "/xyz/openbmc_project/VirtualMedia", 281 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 282 } 283 284 /** 285 * @brief Transfer protocols supported for InsertMedia action. 286 * 287 */ 288 enum class TransferProtocol 289 { 290 https, 291 smb, 292 invalid 293 }; 294 295 /** 296 * @brief Function extracts transfer protocol type from URI. 297 * 298 */ 299 inline std::optional<TransferProtocol> 300 getTransferProtocolFromUri(const boost::urls::url_view& imageUri) 301 { 302 std::string_view scheme = imageUri.scheme(); 303 if (scheme == "smb") 304 { 305 return TransferProtocol::smb; 306 } 307 if (scheme == "https") 308 { 309 return TransferProtocol::https; 310 } 311 if (!scheme.empty()) 312 { 313 return TransferProtocol::invalid; 314 } 315 316 return {}; 317 } 318 319 /** 320 * @brief Function convert transfer protocol from string param. 321 * 322 */ 323 inline std::optional<TransferProtocol> getTransferProtocolFromParam( 324 const std::optional<std::string>& transferProtocolType) 325 { 326 if (transferProtocolType == std::nullopt) 327 { 328 return {}; 329 } 330 331 if (*transferProtocolType == "CIFS") 332 { 333 return TransferProtocol::smb; 334 } 335 336 if (*transferProtocolType == "HTTPS") 337 { 338 return TransferProtocol::https; 339 } 340 341 return TransferProtocol::invalid; 342 } 343 344 /** 345 * @brief Function extends URI with transfer protocol type. 346 * 347 */ 348 inline std::string 349 getUriWithTransferProtocol(const std::string& imageUri, 350 const TransferProtocol& transferProtocol) 351 { 352 if (transferProtocol == TransferProtocol::smb) 353 { 354 return "smb://" + imageUri; 355 } 356 357 if (transferProtocol == TransferProtocol::https) 358 { 359 return "https://" + imageUri; 360 } 361 362 return imageUri; 363 } 364 365 struct InsertMediaActionParams 366 { 367 std::string imageUrl; 368 std::optional<std::string> userName; 369 std::optional<std::string> password; 370 std::optional<std::string> transferMethod; 371 std::optional<std::string> transferProtocolType; 372 std::optional<bool> writeProtected = true; 373 std::optional<bool> inserted; 374 }; 375 376 /** 377 * @brief Function validate parameters of insert media request. 378 * 379 */ 380 inline bool validateParams(crow::Response& res, 381 InsertMediaActionParams& actionParams) 382 { 383 BMCWEB_LOG_DEBUG << "Validation started"; 384 // required param imageUrl must not be empty 385 if (actionParams.imageUrl.empty()) 386 { 387 BMCWEB_LOG_ERROR << "Request action parameter Image is empty."; 388 389 messages::propertyValueFormatError(res, "<empty>", "Image"); 390 391 return false; 392 } 393 394 // optional param inserted must be true 395 if ((actionParams.inserted != std::nullopt) && !*actionParams.inserted) 396 { 397 BMCWEB_LOG_ERROR 398 << "Request action optional parameter Inserted must be true."; 399 400 messages::actionParameterNotSupported(res, "Inserted", "InsertMedia"); 401 402 return false; 403 } 404 405 // optional param transferMethod must be stream 406 if ((actionParams.transferMethod != std::nullopt) && 407 (*actionParams.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(actionParams.imageUrl)); 419 if (!url) 420 { 421 messages::actionParameterValueFormatError(res, actionParams.imageUrl, 422 "Image", "InsertMedia"); 423 return false; 424 } 425 std::optional<TransferProtocol> uriTransferProtocolType = 426 getTransferProtocolFromUri(*url); 427 428 std::optional<TransferProtocol> paramTransferProtocolType = 429 getTransferProtocolFromParam(actionParams.transferProtocolType); 430 431 // ImageUrl does not contain valid protocol type 432 if (*uriTransferProtocolType == TransferProtocol::invalid) 433 { 434 BMCWEB_LOG_ERROR << "Request action parameter ImageUrl must " 435 "contain specified protocol type from list: " 436 "(smb, https)."; 437 438 messages::resourceAtUriInUnknownFormat(res, *url); 439 440 return false; 441 } 442 443 // transferProtocolType should contain value from list 444 if (*paramTransferProtocolType == TransferProtocol::invalid) 445 { 446 BMCWEB_LOG_ERROR << "Request action parameter TransferProtocolType " 447 "must be provided with value from list: " 448 "(CIFS, HTTPS)."; 449 450 messages::propertyValueNotInList( 451 res, *actionParams.transferProtocolType, "TransferProtocolType"); 452 return false; 453 } 454 455 // valid transfer protocol not provided either with URI nor param 456 if ((uriTransferProtocolType == std::nullopt) && 457 (paramTransferProtocolType == std::nullopt)) 458 { 459 BMCWEB_LOG_ERROR << "Request action parameter ImageUrl must " 460 "contain specified protocol type or param " 461 "TransferProtocolType must be provided."; 462 463 messages::resourceAtUriInUnknownFormat(res, *url); 464 465 return false; 466 } 467 468 // valid transfer protocol provided both with URI and param 469 if ((paramTransferProtocolType != std::nullopt) && 470 (uriTransferProtocolType != std::nullopt)) 471 { 472 // check if protocol is the same for URI and param 473 if (*paramTransferProtocolType != *uriTransferProtocolType) 474 { 475 BMCWEB_LOG_ERROR << "Request action parameter " 476 "TransferProtocolType must contain the " 477 "same protocol type as protocol type " 478 "provided with param imageUrl."; 479 480 messages::actionParameterValueTypeError( 481 res, *actionParams.transferProtocolType, "TransferProtocolType", 482 "InsertMedia"); 483 484 return false; 485 } 486 } 487 488 // validation passed, add protocol to URI if needed 489 if (uriTransferProtocolType == std::nullopt) 490 { 491 actionParams.imageUrl = getUriWithTransferProtocol( 492 actionParams.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&& bufferIn) : 600 impl(io), buffer{std::move(bufferIn)} 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 inline void handleManagersVirtualMediaActionInsertPost( 767 crow::App& app, const crow::Request& req, 768 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 769 const std::string& name, const std::string& resName) 770 { 771 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 772 { 773 return; 774 } 775 if (name != "bmc") 776 { 777 messages::resourceNotFound(asyncResp->res, "VirtualMedia.InsertMedia", 778 resName); 779 780 return; 781 } 782 InsertMediaActionParams actionParams; 783 784 // Read obligatory parameters (url of 785 // image) 786 if (!json_util::readJsonAction( 787 req, asyncResp->res, "Image", actionParams.imageUrl, 788 "WriteProtected", actionParams.writeProtected, "UserName", 789 actionParams.userName, "Password", actionParams.password, 790 "Inserted", actionParams.inserted, "TransferMethod", 791 actionParams.transferMethod, "TransferProtocolType", 792 actionParams.transferProtocolType)) 793 { 794 BMCWEB_LOG_DEBUG << "Image is not provided"; 795 return; 796 } 797 798 bool paramsValid = validateParams(asyncResp->res, actionParams); 799 if (!paramsValid) 800 { 801 return; 802 } 803 804 dbus::utility::getDbusObject( 805 "/xyz/openbmc_project/VirtualMedia", {}, 806 [asyncResp, actionParams, 807 resName](const boost::system::error_code& ec, 808 const dbus::utility::MapperGetObject& getObjectType) mutable { 809 if (ec) 810 { 811 BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << ec; 812 messages::internalError(asyncResp->res); 813 814 return; 815 } 816 std::string service = getObjectType.begin()->first; 817 BMCWEB_LOG_DEBUG << "GetObjectType: " << service; 818 819 crow::connections::systemBus->async_method_call( 820 [service, resName, actionParams, 821 asyncResp](const boost::system::error_code ec2, 822 dbus::utility::ManagedObjectType& subtree) mutable { 823 if (ec2) 824 { 825 BMCWEB_LOG_DEBUG << "DBUS response error"; 826 messages::internalError(asyncResp->res); 827 828 return; 829 } 830 831 for (const auto& object : subtree) 832 { 833 const std::string& path = 834 static_cast<const std::string&>(object.first); 835 836 std::size_t lastIndex = path.rfind('/'); 837 if (lastIndex == std::string::npos) 838 { 839 continue; 840 } 841 842 lastIndex += 1; 843 844 if (path.substr(lastIndex) == resName) 845 { 846 lastIndex = path.rfind("Proxy"); 847 if (lastIndex != std::string::npos) 848 { 849 // Not possible in proxy mode 850 BMCWEB_LOG_DEBUG << "InsertMedia not " 851 "allowed in proxy mode"; 852 messages::resourceNotFound(asyncResp->res, 853 "VirtualMedia.InsertMedia", 854 resName); 855 856 return; 857 } 858 859 lastIndex = path.rfind("Legacy"); 860 if (lastIndex == std::string::npos) 861 { 862 continue; 863 } 864 865 // manager is irrelevant for 866 // VirtualMedia dbus calls 867 doMountVmLegacy(asyncResp, service, resName, 868 actionParams.imageUrl, 869 !(*actionParams.writeProtected), 870 std::move(*actionParams.userName), 871 std::move(*actionParams.password)); 872 873 return; 874 } 875 } 876 BMCWEB_LOG_DEBUG << "Parent item not found"; 877 messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName); 878 }, 879 service, "/xyz/openbmc_project/VirtualMedia", 880 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 881 }); 882 } 883 884 inline void handleManagersVirtualMediaActionEject( 885 crow::App& app, const crow::Request& req, 886 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 887 const std::string& managerName, const std::string& resName) 888 { 889 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 890 { 891 return; 892 } 893 if (managerName != "bmc") 894 { 895 messages::resourceNotFound(asyncResp->res, "VirtualMedia.Eject", 896 resName); 897 898 return; 899 } 900 901 dbus::utility::getDbusObject( 902 "/xyz/openbmc_project/VirtualMedia", {}, 903 [asyncResp, 904 resName](const boost::system::error_code& ec2, 905 const dbus::utility::MapperGetObject& getObjectType) { 906 if (ec2) 907 { 908 BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << ec2; 909 messages::internalError(asyncResp->res); 910 911 return; 912 } 913 std::string service = getObjectType.begin()->first; 914 BMCWEB_LOG_DEBUG << "GetObjectType: " << service; 915 916 crow::connections::systemBus->async_method_call( 917 [resName, service, asyncResp{asyncResp}]( 918 const boost::system::error_code ec, 919 const dbus::utility::ManagedObjectType& subtree) { 920 if (ec) 921 { 922 BMCWEB_LOG_DEBUG << "DBUS response error"; 923 messages::internalError(asyncResp->res); 924 925 return; 926 } 927 928 for (const auto& object : subtree) 929 { 930 const std::string& path = 931 static_cast<const std::string&>(object.first); 932 933 std::size_t lastIndex = path.rfind('/'); 934 if (lastIndex == std::string::npos) 935 { 936 continue; 937 } 938 939 lastIndex += 1; 940 941 if (path.substr(lastIndex) == resName) 942 { 943 lastIndex = path.rfind("Proxy"); 944 if (lastIndex != std::string::npos) 945 { 946 // Proxy mode 947 doVmAction(asyncResp, service, resName, false); 948 } 949 950 lastIndex = path.rfind("Legacy"); 951 if (lastIndex != std::string::npos) 952 { 953 // Legacy mode 954 doVmAction(asyncResp, service, resName, true); 955 } 956 957 return; 958 } 959 } 960 BMCWEB_LOG_DEBUG << "Parent item not found"; 961 messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName); 962 }, 963 service, "/xyz/openbmc_project/VirtualMedia", 964 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 965 }); 966 } 967 968 inline void handleManagersVirtualMediaCollectionGet( 969 crow::App& app, const crow::Request& req, 970 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 971 const std::string& name) 972 { 973 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 974 { 975 return; 976 } 977 if (name != "bmc") 978 { 979 messages::resourceNotFound(asyncResp->res, "VirtualMedia", name); 980 981 return; 982 } 983 984 asyncResp->res.jsonValue["@odata.type"] = 985 "#VirtualMediaCollection.VirtualMediaCollection"; 986 asyncResp->res.jsonValue["Name"] = "Virtual Media Services"; 987 asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( 988 "redfish", "v1", "Managers", name, "VirtualMedia"); 989 990 dbus::utility::getDbusObject( 991 "/xyz/openbmc_project/VirtualMedia", {}, 992 [asyncResp, name](const boost::system::error_code& ec, 993 const dbus::utility::MapperGetObject& getObjectType) { 994 if (ec) 995 { 996 BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << ec; 997 messages::internalError(asyncResp->res); 998 999 return; 1000 } 1001 std::string service = getObjectType.begin()->first; 1002 BMCWEB_LOG_DEBUG << "GetObjectType: " << service; 1003 1004 getVmResourceList(asyncResp, service, name); 1005 }); 1006 } 1007 1008 inline void 1009 handleVirtualMediaGet(crow::App& app, const crow::Request& req, 1010 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1011 const std::string& name, const std::string& resName) 1012 { 1013 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1014 { 1015 return; 1016 } 1017 if (name != "bmc") 1018 { 1019 messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName); 1020 1021 return; 1022 } 1023 1024 dbus::utility::getDbusObject( 1025 "/xyz/openbmc_project/VirtualMedia", {}, 1026 [asyncResp, name, 1027 resName](const boost::system::error_code& ec, 1028 const dbus::utility::MapperGetObject& getObjectType) { 1029 if (ec) 1030 { 1031 BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << 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 getVmData(asyncResp, service, name, resName); 1040 }); 1041 } 1042 1043 inline void requestNBDVirtualMediaRoutes(App& app) 1044 { 1045 BMCWEB_ROUTE( 1046 app, 1047 "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/VirtualMedia.InsertMedia") 1048 .privileges(redfish::privileges::postVirtualMedia) 1049 .methods(boost::beast::http::verb::post)(std::bind_front( 1050 handleManagersVirtualMediaActionInsertPost, std::ref(app))); 1051 1052 BMCWEB_ROUTE( 1053 app, 1054 "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/VirtualMedia.EjectMedia") 1055 .privileges(redfish::privileges::postVirtualMedia) 1056 .methods(boost::beast::http::verb::post)(std::bind_front( 1057 handleManagersVirtualMediaActionEject, std::ref(app))); 1058 1059 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/") 1060 .privileges(redfish::privileges::getVirtualMediaCollection) 1061 .methods(boost::beast::http::verb::get)(std::bind_front( 1062 handleManagersVirtualMediaCollectionGet, std::ref(app))); 1063 1064 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/<str>/") 1065 .privileges(redfish::privileges::getVirtualMedia) 1066 .methods(boost::beast::http::verb::get)( 1067 std::bind_front(handleVirtualMediaGet, std::ref(app))); 1068 } 1069 1070 } // namespace redfish 1071