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