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