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