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