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::DBusInterfacesMap>&)>;
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::DBusInterfacesMap& 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::DBusInterfacesMap>& 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 
769     // validation passed, add protocol to URI if needed
770     if (!uriTransferProtocolType && paramTransferProtocolType)
771     {
772         actionParams.imageUrl = getUriWithTransferProtocol(
773             *actionParams.imageUrl, *paramTransferProtocolType);
774     }
775 
776     if (!actionParams.userName)
777     {
778         actionParams.userName = "";
779     }
780 
781     if (!actionParams.password)
782     {
783         actionParams.password = "";
784     }
785 
786     doMountVmLegacy(asyncResp, service, resName, *actionParams.imageUrl,
787                     !(actionParams.writeProtected.value_or(false)),
788                     std::move(*actionParams.userName),
789                     std::move(*actionParams.password));
790 }
791 
792 /**
793  * @brief Function transceives data with dbus directly.
794  *
795  * All BMC state properties will be retrieved before sending reset request.
796  */
797 inline void doEjectAction(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
798                           const std::string& service, const std::string& name,
799                           bool legacy)
800 {
801     // Legacy mount requires parameter with image
802     if (legacy)
803     {
804         crow::connections::systemBus->async_method_call(
805             [asyncResp](const boost::system::error_code& ec) {
806             if (ec)
807             {
808                 BMCWEB_LOG_ERROR("Bad D-Bus request error: {}", ec);
809 
810                 messages::internalError(asyncResp->res);
811                 return;
812             }
813             },
814             service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name,
815             "xyz.openbmc_project.VirtualMedia.Legacy", "Unmount");
816     }
817     else // proxy
818     {
819         crow::connections::systemBus->async_method_call(
820             [asyncResp](const boost::system::error_code& ec) {
821             if (ec)
822             {
823                 BMCWEB_LOG_ERROR("Bad D-Bus request error: {}", ec);
824 
825                 messages::internalError(asyncResp->res);
826                 return;
827             }
828             },
829             service, "/xyz/openbmc_project/VirtualMedia/Proxy/" + name,
830             "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount");
831     }
832 }
833 
834 inline void handleManagersVirtualMediaActionInsertPost(
835     crow::App& app, const crow::Request& req,
836     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
837     const std::string& name, const std::string& resName)
838 {
839     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
840     {
841         return;
842     }
843 
844     constexpr std::string_view action = "VirtualMedia.InsertMedia";
845     if (name != "bmc")
846     {
847         messages::resourceNotFound(asyncResp->res, action, resName);
848 
849         return;
850     }
851     InsertMediaActionParams actionParams;
852 
853     // Read obligatory parameters (url of image)
854     if (!json_util::readJsonAction(
855             req, asyncResp->res, "Image", actionParams.imageUrl,
856             "WriteProtected", actionParams.writeProtected, "UserName",
857             actionParams.userName, "Password", actionParams.password,
858             "Inserted", actionParams.inserted, "TransferMethod",
859             actionParams.transferMethod, "TransferProtocolType",
860             actionParams.transferProtocolType))
861     {
862         return;
863     }
864 
865     dbus::utility::getDbusObject(
866         "/xyz/openbmc_project/VirtualMedia", {},
867         [asyncResp, action, actionParams,
868          resName](const boost::system::error_code& ec,
869                   const dbus::utility::MapperGetObject& getObjectType) mutable {
870         if (ec)
871         {
872             BMCWEB_LOG_ERROR("ObjectMapper::GetObject call failed: {}", ec);
873             messages::resourceNotFound(asyncResp->res, action, resName);
874 
875             return;
876         }
877 
878         std::string service = getObjectType.begin()->first;
879         BMCWEB_LOG_DEBUG("GetObjectType: {}", service);
880 
881         sdbusplus::message::object_path path(
882             "/xyz/openbmc_project/VirtualMedia");
883         dbus::utility::getManagedObjects(
884             service, path,
885             [service, resName, action, actionParams, asyncResp](
886                 const boost::system::error_code& ec2,
887                 const dbus::utility::ManagedObjectType& subtree) mutable {
888             if (ec2)
889             {
890                 // Not possible in proxy mode
891                 BMCWEB_LOG_DEBUG("InsertMedia not "
892                                  "allowed in proxy mode");
893                 messages::resourceNotFound(asyncResp->res, action, resName);
894 
895                 return;
896             }
897             for (const auto& object : subtree)
898             {
899                 VmMode mode = parseObjectPathAndGetMode(object.first, resName);
900                 if (mode == VmMode::Legacy)
901                 {
902                     validateParams(asyncResp, service, resName, actionParams);
903 
904                     return;
905                 }
906             }
907             BMCWEB_LOG_DEBUG("Parent item not found");
908             messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName);
909             });
910         });
911 }
912 
913 inline void handleManagersVirtualMediaActionEject(
914     crow::App& app, const crow::Request& req,
915     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
916     const std::string& managerName, const std::string& resName)
917 {
918     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
919     {
920         return;
921     }
922 
923     constexpr std::string_view action = "VirtualMedia.EjectMedia";
924     if (managerName != "bmc")
925     {
926         messages::resourceNotFound(asyncResp->res, action, resName);
927 
928         return;
929     }
930 
931     dbus::utility::getDbusObject(
932         "/xyz/openbmc_project/VirtualMedia", {},
933         [asyncResp, action,
934          resName](const boost::system::error_code& ec2,
935                   const dbus::utility::MapperGetObject& getObjectType) {
936         if (ec2)
937         {
938             BMCWEB_LOG_ERROR("ObjectMapper::GetObject call failed: {}", ec2);
939             messages::internalError(asyncResp->res);
940 
941             return;
942         }
943         std::string service = getObjectType.begin()->first;
944         BMCWEB_LOG_DEBUG("GetObjectType: {}", service);
945 
946         sdbusplus::message::object_path path(
947             "/xyz/openbmc_project/VirtualMedia");
948         dbus::utility::getManagedObjects(
949             service, path,
950             [resName, service, action,
951              asyncResp](const boost::system::error_code& ec,
952                         const dbus::utility::ManagedObjectType& subtree) {
953             if (ec)
954             {
955                 BMCWEB_LOG_ERROR("ObjectMapper : No Service found");
956                 messages::resourceNotFound(asyncResp->res, action, resName);
957                 return;
958             }
959 
960             for (const auto& object : subtree)
961             {
962                 VmMode mode = parseObjectPathAndGetMode(object.first, resName);
963                 if (mode != VmMode::Invalid)
964                 {
965                     doEjectAction(asyncResp, service, resName,
966                                   mode == VmMode::Legacy);
967                     return;
968                 }
969             }
970             BMCWEB_LOG_DEBUG("Parent item not found");
971             messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName);
972             });
973         });
974 }
975 
976 inline void handleManagersVirtualMediaCollectionGet(
977     crow::App& app, const crow::Request& req,
978     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
979     const std::string& name)
980 {
981     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
982     {
983         return;
984     }
985     if (name != "bmc")
986     {
987         messages::resourceNotFound(asyncResp->res, "VirtualMedia", name);
988 
989         return;
990     }
991 
992     asyncResp->res.jsonValue["@odata.type"] =
993         "#VirtualMediaCollection.VirtualMediaCollection";
994     asyncResp->res.jsonValue["Name"] = "Virtual Media Services";
995     asyncResp->res.jsonValue["@odata.id"] =
996         boost::urls::format("/redfish/v1/Managers/{}/VirtualMedia", name);
997 
998     dbus::utility::getDbusObject(
999         "/xyz/openbmc_project/VirtualMedia", {},
1000         [asyncResp, name](const boost::system::error_code& ec,
1001                           const dbus::utility::MapperGetObject& getObjectType) {
1002         if (ec)
1003         {
1004             BMCWEB_LOG_ERROR("ObjectMapper::GetObject call failed: {}", ec);
1005             messages::internalError(asyncResp->res);
1006 
1007             return;
1008         }
1009         std::string service = getObjectType.begin()->first;
1010         BMCWEB_LOG_DEBUG("GetObjectType: {}", service);
1011 
1012         getVmResourceList(asyncResp, service, name);
1013         });
1014 }
1015 
1016 inline void
1017     handleVirtualMediaGet(crow::App& app, const crow::Request& req,
1018                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1019                           const std::string& name, const std::string& resName)
1020 {
1021     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1022     {
1023         return;
1024     }
1025     if (name != "bmc")
1026     {
1027         messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName);
1028 
1029         return;
1030     }
1031 
1032     dbus::utility::getDbusObject(
1033         "/xyz/openbmc_project/VirtualMedia", {},
1034         [asyncResp, name,
1035          resName](const boost::system::error_code& ec,
1036                   const dbus::utility::MapperGetObject& getObjectType) {
1037         if (ec)
1038         {
1039             BMCWEB_LOG_ERROR("ObjectMapper::GetObject call failed: {}", ec);
1040             messages::internalError(asyncResp->res);
1041 
1042             return;
1043         }
1044         std::string service = getObjectType.begin()->first;
1045         BMCWEB_LOG_DEBUG("GetObjectType: {}", service);
1046 
1047         getVmData(asyncResp, service, name, resName);
1048         });
1049 }
1050 
1051 inline void requestNBDVirtualMediaRoutes(App& app)
1052 {
1053     BMCWEB_ROUTE(
1054         app,
1055         "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/VirtualMedia.InsertMedia")
1056         .privileges(redfish::privileges::postVirtualMedia)
1057         .methods(boost::beast::http::verb::post)(std::bind_front(
1058             handleManagersVirtualMediaActionInsertPost, std::ref(app)));
1059 
1060     BMCWEB_ROUTE(
1061         app,
1062         "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/VirtualMedia.EjectMedia")
1063         .privileges(redfish::privileges::postVirtualMedia)
1064         .methods(boost::beast::http::verb::post)(std::bind_front(
1065             handleManagersVirtualMediaActionEject, std::ref(app)));
1066 
1067     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/")
1068         .privileges(redfish::privileges::getVirtualMediaCollection)
1069         .methods(boost::beast::http::verb::get)(std::bind_front(
1070             handleManagersVirtualMediaCollectionGet, std::ref(app)));
1071 
1072     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/<str>/")
1073         .privileges(redfish::privileges::getVirtualMedia)
1074         .methods(boost::beast::http::verb::get)(
1075             std::bind_front(handleVirtualMediaGet, std::ref(app)));
1076 }
1077 
1078 } // namespace redfish
1079