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, 799 std::size_t size) { 800 if (ec) 801 { 802 BMCWEB_LOG_ERROR << "Failed to pass secret: " << ec; 803 messages::internalError(asyncResp->res); 804 } 805 }); 806 } 807 808 crow::connections::systemBus->async_method_call( 809 [asyncResp, secretPipe](const boost::system::error_code ec, 810 bool success) { 811 if (ec) 812 { 813 BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; 814 messages::internalError(asyncResp->res); 815 } 816 else if (!success) 817 { 818 BMCWEB_LOG_ERROR << "Service responded with error"; 819 messages::generalError(asyncResp->res); 820 } 821 }, 822 service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name, 823 "xyz.openbmc_project.VirtualMedia.Legacy", "Mount", imageUrl, rw, 824 unixFd); 825 } 826 }; 827 828 /** 829 @brief EjectMedia action class 830 */ 831 class VirtualMediaActionEjectMedia : public Node 832 { 833 public: 834 VirtualMediaActionEjectMedia(App& app) : 835 Node(app, 836 "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/" 837 "VirtualMedia.EjectMedia", 838 std::string(), std::string()) 839 { 840 entityPrivileges = { 841 {boost::beast::http::verb::get, {{"Login"}}}, 842 {boost::beast::http::verb::head, {{"Login"}}}, 843 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 844 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 845 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 846 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 847 } 848 849 private: 850 /** 851 * @brief Function handles POST method request. 852 * 853 * Analyzes POST body message before sends Reset request data to dbus. 854 */ 855 void doPost(crow::Response& res, const crow::Request& req, 856 const std::vector<std::string>& params) override 857 { 858 auto aResp = std::make_shared<AsyncResp>(res); 859 860 if (params.size() != 2) 861 { 862 messages::internalError(res); 863 return; 864 } 865 866 // take resource name from URL 867 const std::string& resName = params[1]; 868 869 if (params[0] != "bmc") 870 { 871 messages::resourceNotFound(res, "VirtualMedia.Eject", resName); 872 873 return; 874 } 875 876 crow::connections::systemBus->async_method_call( 877 [this, aResp{std::move(aResp)}, req, 878 resName](const boost::system::error_code ec, 879 const GetObjectType& getObjectType) { 880 if (ec) 881 { 882 BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " 883 << ec; 884 messages::internalError(aResp->res); 885 886 return; 887 } 888 std::string service = getObjectType.begin()->first; 889 BMCWEB_LOG_DEBUG << "GetObjectType: " << service; 890 891 crow::connections::systemBus->async_method_call( 892 [this, resName, service, req, aResp{std::move(aResp)}]( 893 const boost::system::error_code ec, 894 ManagedObjectType& subtree) { 895 if (ec) 896 { 897 BMCWEB_LOG_DEBUG << "DBUS response error"; 898 899 return; 900 } 901 902 for (const auto& object : subtree) 903 { 904 const std::string& path = 905 static_cast<const std::string&>(object.first); 906 907 std::size_t lastIndex = path.rfind("/"); 908 if (lastIndex == std::string::npos) 909 { 910 continue; 911 } 912 913 lastIndex += 1; 914 915 if (path.substr(lastIndex) == resName) 916 { 917 lastIndex = path.rfind("Proxy"); 918 if (lastIndex != std::string::npos) 919 { 920 // Proxy mode 921 doVmAction(std::move(aResp), service, 922 resName, false); 923 } 924 925 lastIndex = path.rfind("Legacy"); 926 if (lastIndex != std::string::npos) 927 { 928 // Legacy mode 929 doVmAction(std::move(aResp), service, 930 resName, true); 931 } 932 933 return; 934 } 935 } 936 BMCWEB_LOG_DEBUG << "Parent item not found"; 937 messages::resourceNotFound(aResp->res, "VirtualMedia", 938 resName); 939 }, 940 service, "/xyz/openbmc_project/VirtualMedia", 941 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 942 }, 943 "xyz.openbmc_project.ObjectMapper", 944 "/xyz/openbmc_project/object_mapper", 945 "xyz.openbmc_project.ObjectMapper", "GetObject", 946 "/xyz/openbmc_project/VirtualMedia", std::array<const char*, 0>()); 947 } 948 949 /** 950 * @brief Function transceives data with dbus directly. 951 * 952 * All BMC state properties will be retrieved before sending reset request. 953 */ 954 void doVmAction(std::shared_ptr<AsyncResp> asyncResp, 955 const std::string& service, const std::string& name, 956 bool legacy) 957 { 958 959 // Legacy mount requires parameter with image 960 if (legacy) 961 { 962 crow::connections::systemBus->async_method_call( 963 [asyncResp](const boost::system::error_code ec) { 964 if (ec) 965 { 966 BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; 967 968 messages::internalError(asyncResp->res); 969 return; 970 } 971 }, 972 service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name, 973 "xyz.openbmc_project.VirtualMedia.Legacy", "Unmount"); 974 } 975 else // proxy 976 { 977 crow::connections::systemBus->async_method_call( 978 [asyncResp](const boost::system::error_code ec) { 979 if (ec) 980 { 981 BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec; 982 983 messages::internalError(asyncResp->res); 984 return; 985 } 986 }, 987 service, "/xyz/openbmc_project/VirtualMedia/Proxy/" + name, 988 "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount"); 989 } 990 } 991 }; 992 993 class VirtualMediaCollection : public Node 994 { 995 public: 996 /* 997 * Default Constructor 998 */ 999 VirtualMediaCollection(App& app) : 1000 Node(app, "/redfish/v1/Managers/<str>/VirtualMedia/", std::string()) 1001 { 1002 entityPrivileges = { 1003 {boost::beast::http::verb::get, {{"Login"}}}, 1004 {boost::beast::http::verb::head, {{"Login"}}}, 1005 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1006 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1007 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1008 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1009 } 1010 1011 private: 1012 /** 1013 * Functions triggers appropriate requests on DBus 1014 */ 1015 void doGet(crow::Response& res, const crow::Request& req, 1016 const std::vector<std::string>& params) override 1017 { 1018 auto asyncResp = std::make_shared<AsyncResp>(res); 1019 1020 // Check if there is required param, truly entering this shall be 1021 // impossible 1022 if (params.size() != 1) 1023 { 1024 messages::internalError(res); 1025 1026 return; 1027 } 1028 1029 const std::string& name = params[0]; 1030 1031 if (name != "bmc") 1032 { 1033 messages::resourceNotFound(asyncResp->res, "VirtualMedia", name); 1034 1035 return; 1036 } 1037 1038 res.jsonValue["@odata.type"] = 1039 "#VirtualMediaCollection.VirtualMediaCollection"; 1040 res.jsonValue["Name"] = "Virtual Media Services"; 1041 res.jsonValue["@odata.id"] = 1042 "/redfish/v1/Managers/" + name + "/VirtualMedia"; 1043 1044 crow::connections::systemBus->async_method_call( 1045 [asyncResp, name](const boost::system::error_code ec, 1046 const GetObjectType& getObjectType) { 1047 if (ec) 1048 { 1049 BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " 1050 << ec; 1051 messages::internalError(asyncResp->res); 1052 1053 return; 1054 } 1055 std::string service = getObjectType.begin()->first; 1056 BMCWEB_LOG_DEBUG << "GetObjectType: " << service; 1057 1058 getVmResourceList(asyncResp, service, name); 1059 }, 1060 "xyz.openbmc_project.ObjectMapper", 1061 "/xyz/openbmc_project/object_mapper", 1062 "xyz.openbmc_project.ObjectMapper", "GetObject", 1063 "/xyz/openbmc_project/VirtualMedia", std::array<const char*, 0>()); 1064 } 1065 }; 1066 1067 class VirtualMedia : public Node 1068 { 1069 public: 1070 /* 1071 * Default Constructor 1072 */ 1073 VirtualMedia(App& app) : 1074 Node(app, "/redfish/v1/Managers/<str>/VirtualMedia/<str>/", 1075 std::string(), std::string()) 1076 { 1077 entityPrivileges = { 1078 {boost::beast::http::verb::get, {{"Login"}}}, 1079 {boost::beast::http::verb::head, {{"Login"}}}, 1080 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1081 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1082 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1083 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1084 } 1085 1086 private: 1087 /** 1088 * Functions triggers appropriate requests on DBus 1089 */ 1090 void doGet(crow::Response& res, const crow::Request& req, 1091 const std::vector<std::string>& params) override 1092 { 1093 // Check if there is required param, truly entering this shall be 1094 // impossible 1095 if (params.size() != 2) 1096 { 1097 messages::internalError(res); 1098 1099 res.end(); 1100 return; 1101 } 1102 const std::string& name = params[0]; 1103 const std::string& resName = params[1]; 1104 1105 auto asyncResp = std::make_shared<AsyncResp>(res); 1106 1107 if (name != "bmc") 1108 { 1109 messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName); 1110 1111 return; 1112 } 1113 1114 crow::connections::systemBus->async_method_call( 1115 [asyncResp, name, resName](const boost::system::error_code ec, 1116 const GetObjectType& getObjectType) { 1117 if (ec) 1118 { 1119 BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " 1120 << ec; 1121 messages::internalError(asyncResp->res); 1122 1123 return; 1124 } 1125 std::string service = getObjectType.begin()->first; 1126 BMCWEB_LOG_DEBUG << "GetObjectType: " << service; 1127 1128 getVmData(asyncResp, service, name, resName); 1129 }, 1130 "xyz.openbmc_project.ObjectMapper", 1131 "/xyz/openbmc_project/object_mapper", 1132 "xyz.openbmc_project.ObjectMapper", "GetObject", 1133 "/xyz/openbmc_project/VirtualMedia", std::array<const char*, 0>()); 1134 } 1135 }; 1136 1137 } // namespace redfish 1138