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