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