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