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