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