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& 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"] = {"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, 176 aResp{std::move(aResp)}](const boost::system::error_code ec, 177 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( 274 aResp->res, "#VirtualMedia.v1_3_0.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 boost::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 /** 362 * @brief Function validate parameters of insert media request. 363 * 364 */ 365 inline bool 366 validateParams(crow::Response& res, std::string& imageUrl, 367 const std::optional<bool>& inserted, 368 const std::optional<std::string>& transferMethod, 369 const std::optional<std::string>& transferProtocolType) 370 { 371 BMCWEB_LOG_DEBUG << "Validation started"; 372 // required param imageUrl must not be empty 373 if (imageUrl.empty()) 374 { 375 BMCWEB_LOG_ERROR << "Request action parameter Image is empty."; 376 377 messages::propertyValueFormatError(res, "<empty>", "Image"); 378 379 return false; 380 } 381 382 // optional param inserted must be true 383 if ((inserted != std::nullopt) && !*inserted) 384 { 385 BMCWEB_LOG_ERROR 386 << "Request action optional parameter Inserted must be true."; 387 388 messages::actionParameterNotSupported(res, "Inserted", "InsertMedia"); 389 390 return false; 391 } 392 393 // optional param transferMethod must be stream 394 if ((transferMethod != std::nullopt) && (*transferMethod != "Stream")) 395 { 396 BMCWEB_LOG_ERROR << "Request action optional parameter " 397 "TransferMethod must be Stream."; 398 399 messages::actionParameterNotSupported(res, "TransferMethod", 400 "InsertMedia"); 401 402 return false; 403 } 404 boost::urls::result<boost::urls::url_view> url = 405 boost::urls::parse_uri(boost::string_view(imageUrl)); 406 if (!url) 407 { 408 messages::actionParameterValueFormatError(res, imageUrl, "Image", 409 "InsertMedia"); 410 return false; 411 } 412 std::optional<TransferProtocol> uriTransferProtocolType = 413 getTransferProtocolFromUri(*url); 414 415 std::optional<TransferProtocol> paramTransferProtocolType = 416 getTransferProtocolFromParam(transferProtocolType); 417 418 // ImageUrl does not contain valid protocol type 419 if (*uriTransferProtocolType == TransferProtocol::invalid) 420 { 421 BMCWEB_LOG_ERROR << "Request action parameter ImageUrl must " 422 "contain specified protocol type from list: " 423 "(smb, https)."; 424 425 messages::resourceAtUriInUnknownFormat(res, *url); 426 427 return false; 428 } 429 430 // transferProtocolType should contain value from list 431 if (*paramTransferProtocolType == TransferProtocol::invalid) 432 { 433 BMCWEB_LOG_ERROR << "Request action parameter TransferProtocolType " 434 "must be provided with value from list: " 435 "(CIFS, HTTPS)."; 436 437 messages::propertyValueNotInList(res, *transferProtocolType, 438 "TransferProtocolType"); 439 return false; 440 } 441 442 // valid transfer protocol not provided either with URI nor param 443 if ((uriTransferProtocolType == std::nullopt) && 444 (paramTransferProtocolType == std::nullopt)) 445 { 446 BMCWEB_LOG_ERROR << "Request action parameter ImageUrl must " 447 "contain specified protocol type or param " 448 "TransferProtocolType must be provided."; 449 450 messages::resourceAtUriInUnknownFormat(res, *url); 451 452 return false; 453 } 454 455 // valid transfer protocol provided both with URI and param 456 if ((paramTransferProtocolType != std::nullopt) && 457 (uriTransferProtocolType != std::nullopt)) 458 { 459 // check if protocol is the same for URI and param 460 if (*paramTransferProtocolType != *uriTransferProtocolType) 461 { 462 BMCWEB_LOG_ERROR << "Request action parameter " 463 "TransferProtocolType must contain the " 464 "same protocol type as protocol type " 465 "provided with param imageUrl."; 466 467 messages::actionParameterValueTypeError(res, *transferProtocolType, 468 "TransferProtocolType", 469 "InsertMedia"); 470 471 return false; 472 } 473 } 474 475 // validation passed 476 // add protocol to URI if needed 477 if (uriTransferProtocolType == std::nullopt) 478 { 479 imageUrl = 480 getUriWithTransferProtocol(imageUrl, *paramTransferProtocolType); 481 } 482 483 return true; 484 } 485 486 template <typename T> 487 static void secureCleanup(T& value) 488 { 489 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) 490 auto raw = const_cast<typename T::value_type*>(value.data()); 491 explicit_bzero(raw, value.size() * sizeof(*raw)); 492 } 493 494 class Credentials 495 { 496 public: 497 Credentials(std::string&& user, std::string&& password) : 498 userBuf(std::move(user)), passBuf(std::move(password)) 499 {} 500 501 ~Credentials() 502 { 503 secureCleanup(userBuf); 504 secureCleanup(passBuf); 505 } 506 507 const std::string& user() 508 { 509 return userBuf; 510 } 511 512 const std::string& password() 513 { 514 return passBuf; 515 } 516 517 Credentials() = delete; 518 Credentials(const Credentials&) = delete; 519 Credentials& operator=(const Credentials&) = delete; 520 Credentials(Credentials&&) = delete; 521 Credentials& operator=(Credentials&&) = delete; 522 523 private: 524 std::string userBuf; 525 std::string passBuf; 526 }; 527 528 class CredentialsProvider 529 { 530 public: 531 template <typename T> 532 struct Deleter 533 { 534 void operator()(T* buff) const 535 { 536 if (buff) 537 { 538 secureCleanup(*buff); 539 delete buff; 540 } 541 } 542 }; 543 544 using Buffer = std::vector<char>; 545 using SecureBuffer = std::unique_ptr<Buffer, Deleter<Buffer>>; 546 // Using explicit definition instead of std::function to avoid implicit 547 // conversions eg. stack copy instead of reference 548 using FormatterFunc = void(const std::string& username, 549 const std::string& password, Buffer& dest); 550 551 CredentialsProvider(std::string&& user, std::string&& password) : 552 credentials(std::move(user), std::move(password)) 553 {} 554 555 const std::string& user() 556 { 557 return credentials.user(); 558 } 559 560 const std::string& password() 561 { 562 return credentials.password(); 563 } 564 565 SecureBuffer pack(FormatterFunc formatter) 566 { 567 SecureBuffer packed{new Buffer{}}; 568 if (formatter != nullptr) 569 { 570 formatter(credentials.user(), credentials.password(), *packed); 571 } 572 573 return packed; 574 } 575 576 private: 577 Credentials credentials; 578 }; 579 580 // Wrapper for boost::async_pipe ensuring proper pipe cleanup 581 template <typename Buffer> 582 class Pipe 583 { 584 public: 585 using unix_fd = sdbusplus::message::unix_fd; 586 587 Pipe(boost::asio::io_context& io, Buffer&& bufferIn) : 588 impl(io), buffer{std::move(bufferIn)} 589 {} 590 591 ~Pipe() 592 { 593 // Named pipe needs to be explicitly removed 594 impl.close(); 595 } 596 597 Pipe(const Pipe&) = delete; 598 Pipe(Pipe&&) = delete; 599 Pipe& operator=(const Pipe&) = delete; 600 Pipe& operator=(Pipe&&) = delete; 601 602 unix_fd fd() 603 { 604 return unix_fd{impl.native_source()}; 605 } 606 607 template <typename WriteHandler> 608 void asyncWrite(WriteHandler&& handler) 609 { 610 impl.async_write_some(data(), std::forward<WriteHandler>(handler)); 611 } 612 613 private: 614 // Specialization for pointer types 615 template <typename B = Buffer> 616 typename std::enable_if<boost::has_dereference<B>::value, 617 boost::asio::const_buffer>::type 618 data() 619 { 620 return boost::asio::buffer(*buffer); 621 } 622 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 const std::string name; 632 boost::process::async_pipe impl; 633 Buffer buffer; 634 }; 635 636 /** 637 * @brief Function transceives data with dbus directly. 638 * 639 * All BMC state properties will be retrieved before sending reset request. 640 */ 641 inline void doMountVmLegacy(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 642 const std::string& service, const std::string& name, 643 const std::string& imageUrl, const bool rw, 644 std::string&& userName, std::string&& password) 645 { 646 using SecurePipe = Pipe<CredentialsProvider::SecureBuffer>; 647 constexpr const size_t secretLimit = 1024; 648 649 std::shared_ptr<SecurePipe> secretPipe; 650 dbus::utility::DbusVariantType unixFd = -1; 651 652 if (!userName.empty() || !password.empty()) 653 { 654 // Encapsulate in safe buffer 655 CredentialsProvider credentials(std::move(userName), 656 std::move(password)); 657 658 // Payload must contain data + NULL delimiters 659 if (credentials.user().size() + credentials.password().size() + 2 > 660 secretLimit) 661 { 662 BMCWEB_LOG_ERROR << "Credentials too long to handle"; 663 messages::unrecognizedRequestBody(asyncResp->res); 664 return; 665 } 666 667 // Pack secret 668 auto secret = credentials.pack( 669 [](const auto& user, const auto& pass, auto& buff) { 670 std::copy(user.begin(), user.end(), std::back_inserter(buff)); 671 buff.push_back('\0'); 672 std::copy(pass.begin(), pass.end(), std::back_inserter(buff)); 673 buff.push_back('\0'); 674 }); 675 676 // Open pipe 677 secretPipe = std::make_shared<SecurePipe>( 678 crow::connections::systemBus->get_io_context(), std::move(secret)); 679 unixFd = secretPipe->fd(); 680 681 // Pass secret over pipe 682 secretPipe->asyncWrite( 683 [asyncResp](const boost::system::error_code& ec, std::size_t) { 684 if (ec) 685 { 686 BMCWEB_LOG_ERROR << "Failed to pass secret: " << ec; 687 messages::internalError(asyncResp->res); 688 } 689 }); 690 } 691 692 crow::connections::systemBus->async_method_call( 693 [asyncResp, secretPipe](const boost::system::error_code ec, 694 bool success) { 695 if (ec) 696 { 697 BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; 698 messages::internalError(asyncResp->res); 699 } 700 else if (!success) 701 { 702 BMCWEB_LOG_ERROR << "Service responded with error"; 703 messages::generalError(asyncResp->res); 704 } 705 }, 706 service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name, 707 "xyz.openbmc_project.VirtualMedia.Legacy", "Mount", imageUrl, rw, 708 unixFd); 709 } 710 711 /** 712 * @brief Function transceives data with dbus directly. 713 * 714 * All BMC state properties will be retrieved before sending reset request. 715 */ 716 inline void doVmAction(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 717 const std::string& service, const std::string& name, 718 bool legacy) 719 { 720 721 // Legacy mount requires parameter with image 722 if (legacy) 723 { 724 crow::connections::systemBus->async_method_call( 725 [asyncResp](const boost::system::error_code ec) { 726 if (ec) 727 { 728 BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; 729 730 messages::internalError(asyncResp->res); 731 return; 732 } 733 }, 734 service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name, 735 "xyz.openbmc_project.VirtualMedia.Legacy", "Unmount"); 736 } 737 else // proxy 738 { 739 crow::connections::systemBus->async_method_call( 740 [asyncResp](const boost::system::error_code ec) { 741 if (ec) 742 { 743 BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; 744 745 messages::internalError(asyncResp->res); 746 return; 747 } 748 }, 749 service, "/xyz/openbmc_project/VirtualMedia/Proxy/" + name, 750 "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount"); 751 } 752 } 753 754 struct InsertMediaActionParams 755 { 756 std::string imageUrl; 757 std::optional<std::string> userName; 758 std::optional<std::string> password; 759 std::optional<std::string> transferMethod; 760 std::optional<std::string> transferProtocolType; 761 std::optional<bool> writeProtected = true; 762 std::optional<bool> inserted; 763 }; 764 765 inline void handleManagersVirtualMediaActionInsertPost( 766 crow::App& app, const crow::Request& req, 767 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 768 const std::string& name, const std::string& resName) 769 { 770 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 771 { 772 return; 773 } 774 if (name != "bmc") 775 { 776 messages::resourceNotFound(asyncResp->res, "VirtualMedia.Insert", 777 resName); 778 779 return; 780 } 781 InsertMediaActionParams actionParams; 782 783 // Read obligatory parameters (url of 784 // image) 785 if (!json_util::readJsonAction( 786 req, asyncResp->res, "Image", actionParams.imageUrl, 787 "WriteProtected", actionParams.writeProtected, "UserName", 788 actionParams.userName, "Password", actionParams.password, 789 "Inserted", actionParams.inserted, "TransferMethod", 790 actionParams.transferMethod, "TransferProtocolType", 791 actionParams.transferProtocolType)) 792 { 793 BMCWEB_LOG_DEBUG << "Image is not provided"; 794 return; 795 } 796 797 bool paramsValid = validateParams( 798 asyncResp->res, actionParams.imageUrl, actionParams.inserted, 799 actionParams.transferMethod, actionParams.transferProtocolType); 800 801 if (!paramsValid) 802 { 803 return; 804 } 805 806 crow::connections::systemBus->async_method_call( 807 [asyncResp, actionParams, 808 resName](const boost::system::error_code ec, 809 const dbus::utility::MapperGetObject& getObjectType) mutable { 810 if (ec) 811 { 812 BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << ec; 813 messages::internalError(asyncResp->res); 814 815 return; 816 } 817 std::string service = getObjectType.begin()->first; 818 BMCWEB_LOG_DEBUG << "GetObjectType: " << service; 819 820 crow::connections::systemBus->async_method_call( 821 [service, resName, actionParams, 822 asyncResp](const boost::system::error_code ec2, 823 dbus::utility::ManagedObjectType& subtree) mutable { 824 if (ec2) 825 { 826 BMCWEB_LOG_DEBUG << "DBUS response error"; 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 "xyz.openbmc_project.ObjectMapper", 883 "/xyz/openbmc_project/object_mapper", 884 "xyz.openbmc_project.ObjectMapper", "GetObject", 885 "/xyz/openbmc_project/VirtualMedia", std::array<const char*, 0>()); 886 } 887 888 inline void handleManagersVirtualMediaActionEject( 889 crow::App& app, const crow::Request& req, 890 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 891 const std::string& managerName, const std::string& resName) 892 { 893 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 894 { 895 return; 896 } 897 if (managerName != "bmc") 898 { 899 messages::resourceNotFound(asyncResp->res, "VirtualMedia.Eject", 900 resName); 901 902 return; 903 } 904 905 crow::connections::systemBus->async_method_call( 906 [asyncResp, 907 resName](const boost::system::error_code ec2, 908 const dbus::utility::MapperGetObject& getObjectType) { 909 if (ec2) 910 { 911 BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << ec2; 912 messages::internalError(asyncResp->res); 913 914 return; 915 } 916 std::string service = getObjectType.begin()->first; 917 BMCWEB_LOG_DEBUG << "GetObjectType: " << service; 918 919 crow::connections::systemBus->async_method_call( 920 [resName, service, 921 asyncResp{asyncResp}](const boost::system::error_code ec, 922 dbus::utility::ManagedObjectType& subtree) { 923 if (ec) 924 { 925 BMCWEB_LOG_DEBUG << "DBUS response error"; 926 927 return; 928 } 929 930 for (const auto& object : subtree) 931 { 932 const std::string& path = 933 static_cast<const std::string&>(object.first); 934 935 std::size_t lastIndex = path.rfind('/'); 936 if (lastIndex == std::string::npos) 937 { 938 continue; 939 } 940 941 lastIndex += 1; 942 943 if (path.substr(lastIndex) == resName) 944 { 945 lastIndex = path.rfind("Proxy"); 946 if (lastIndex != std::string::npos) 947 { 948 // Proxy mode 949 doVmAction(asyncResp, service, resName, false); 950 } 951 952 lastIndex = path.rfind("Legacy"); 953 if (lastIndex != std::string::npos) 954 { 955 // Legacy mode 956 doVmAction(asyncResp, service, resName, true); 957 } 958 959 return; 960 } 961 } 962 BMCWEB_LOG_DEBUG << "Parent item not found"; 963 messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName); 964 }, 965 service, "/xyz/openbmc_project/VirtualMedia", 966 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 967 }, 968 "xyz.openbmc_project.ObjectMapper", 969 "/xyz/openbmc_project/object_mapper", 970 "xyz.openbmc_project.ObjectMapper", "GetObject", 971 "/xyz/openbmc_project/VirtualMedia", std::array<const char*, 0>()); 972 } 973 974 inline void handleManagersVirtualMediaCollectionGet( 975 crow::App& app, const crow::Request& req, 976 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 977 const std::string& name) 978 { 979 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 980 { 981 return; 982 } 983 if (name != "bmc") 984 { 985 messages::resourceNotFound(asyncResp->res, "VirtualMedia", name); 986 987 return; 988 } 989 990 asyncResp->res.jsonValue["@odata.type"] = 991 "#VirtualMediaCollection.VirtualMediaCollection"; 992 asyncResp->res.jsonValue["Name"] = "Virtual Media Services"; 993 asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( 994 "redfish", "v1", "Managers", name, "VirtualMedia"); 995 996 crow::connections::systemBus->async_method_call( 997 [asyncResp, name](const boost::system::error_code ec, 998 const dbus::utility::MapperGetObject& getObjectType) { 999 if (ec) 1000 { 1001 BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << ec; 1002 messages::internalError(asyncResp->res); 1003 1004 return; 1005 } 1006 std::string service = getObjectType.begin()->first; 1007 BMCWEB_LOG_DEBUG << "GetObjectType: " << service; 1008 1009 getVmResourceList(asyncResp, service, name); 1010 }, 1011 "xyz.openbmc_project.ObjectMapper", 1012 "/xyz/openbmc_project/object_mapper", 1013 "xyz.openbmc_project.ObjectMapper", "GetObject", 1014 "/xyz/openbmc_project/VirtualMedia", std::array<const char*, 0>()); 1015 } 1016 1017 inline void 1018 handleVirtualMediaGet(crow::App& app, const crow::Request& req, 1019 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1020 const std::string& name, const std::string& resName) 1021 { 1022 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1023 { 1024 return; 1025 } 1026 if (name != "bmc") 1027 { 1028 messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName); 1029 1030 return; 1031 } 1032 1033 crow::connections::systemBus->async_method_call( 1034 [asyncResp, name, 1035 resName](const boost::system::error_code ec, 1036 const dbus::utility::MapperGetObject& getObjectType) { 1037 if (ec) 1038 { 1039 BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << ec; 1040 messages::internalError(asyncResp->res); 1041 1042 return; 1043 } 1044 std::string service = getObjectType.begin()->first; 1045 BMCWEB_LOG_DEBUG << "GetObjectType: " << service; 1046 1047 getVmData(asyncResp, service, name, resName); 1048 }, 1049 "xyz.openbmc_project.ObjectMapper", 1050 "/xyz/openbmc_project/object_mapper", 1051 "xyz.openbmc_project.ObjectMapper", "GetObject", 1052 "/xyz/openbmc_project/VirtualMedia", std::array<const char*, 0>()); 1053 } 1054 1055 inline void requestNBDVirtualMediaRoutes(App& app) 1056 { 1057 BMCWEB_ROUTE( 1058 app, 1059 "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/VirtualMedia.InsertMedia") 1060 .privileges(redfish::privileges::postVirtualMedia) 1061 .methods(boost::beast::http::verb::post)(std::bind_front( 1062 handleManagersVirtualMediaActionInsertPost, std::ref(app))); 1063 1064 BMCWEB_ROUTE( 1065 app, 1066 "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/VirtualMedia.EjectMedia") 1067 .privileges(redfish::privileges::postVirtualMedia) 1068 .methods(boost::beast::http::verb::post)(std::bind_front( 1069 handleManagersVirtualMediaActionEject, std::ref(app))); 1070 1071 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/") 1072 .privileges(redfish::privileges::getVirtualMediaCollection) 1073 .methods(boost::beast::http::verb::get)(std::bind_front( 1074 handleManagersVirtualMediaCollectionGet, std::ref(app))); 1075 1076 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/<str>/") 1077 .privileges(redfish::privileges::getVirtualMedia) 1078 .methods(boost::beast::http::verb::get)( 1079 std::bind_front(handleVirtualMediaGet, std::ref(app))); 1080 } 1081 1082 } // namespace redfish 1083