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