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