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