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