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