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