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/url_view.hpp>
29 
30 #include <array>
31 #include <string_view>
32 
33 namespace redfish
34 {
35 
36 enum class VmMode
37 {
38     Invalid,
39     Legacy,
40     Proxy
41 };
42 
43 inline VmMode
44     parseObjectPathAndGetMode(const sdbusplus::message::object_path& itemPath,
45                               const std::string& resName)
46 {
47     std::string thisPath = itemPath.filename();
48     BMCWEB_LOG_DEBUG << "Filename: " << itemPath.str
49                      << ", ThisPath: " << thisPath;
50 
51     if (thisPath.empty())
52     {
53         return VmMode::Invalid;
54     }
55 
56     if (thisPath != resName)
57     {
58         return VmMode::Invalid;
59     }
60 
61     auto mode = itemPath.parent_path();
62     auto type = mode.parent_path();
63 
64     if (mode.filename().empty() || type.filename().empty())
65     {
66         return VmMode::Invalid;
67     }
68 
69     if (type.filename() != "VirtualMedia")
70     {
71         return VmMode::Invalid;
72     }
73     std::string modeStr = mode.filename();
74     if (modeStr == "Legacy")
75     {
76         return VmMode::Legacy;
77     }
78     if (modeStr == "Proxy")
79     {
80         return VmMode::Proxy;
81     }
82     return VmMode::Invalid;
83 }
84 
85 using CheckItemHandler =
86     std::function<void(const std::string& service, const std::string& resName,
87                        const std::shared_ptr<bmcweb::AsyncResp>&,
88                        const std::pair<sdbusplus::message::object_path,
89                                        dbus::utility::DBusInteracesMap>&)>;
90 
91 inline void findAndParseObject(const std::string& service,
92                                const std::string& resName,
93                                const std::shared_ptr<bmcweb::AsyncResp>& aResp,
94                                CheckItemHandler&& handler)
95 {
96     crow::connections::systemBus->async_method_call(
97         [service, resName, aResp,
98          handler](const boost::system::error_code& ec,
99                   const dbus::utility::ManagedObjectType& subtree) {
100         if (ec)
101         {
102             BMCWEB_LOG_DEBUG << "DBUS response error";
103 
104             return;
105         }
106 
107         for (const auto& item : subtree)
108         {
109             VmMode mode = parseObjectPathAndGetMode(item.first, resName);
110             if (mode != VmMode::Invalid)
111             {
112                 handler(service, resName, aResp, item);
113                 return;
114             }
115         }
116 
117         BMCWEB_LOG_DEBUG << "Parent item not found";
118         aResp->res.result(boost::beast::http::status::not_found);
119         },
120         service, "/xyz/openbmc_project/VirtualMedia",
121         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
122 }
123 
124 /**
125  * @brief Function extracts transfer protocol name from URI.
126  */
127 inline std::string getTransferProtocolTypeFromUri(const std::string& imageUri)
128 {
129     boost::urls::result<boost::urls::url_view> url =
130         boost::urls::parse_uri(imageUri);
131     if (!url)
132     {
133         return "None";
134     }
135     std::string_view scheme = url->scheme();
136     if (scheme == "smb")
137     {
138         return "CIFS";
139     }
140     if (scheme == "https")
141     {
142         return "HTTPS";
143     }
144 
145     return "None";
146 }
147 
148 /**
149  * @brief Read all known properties from VM object interfaces
150  */
151 inline void
152     vmParseInterfaceObject(const dbus::utility::DBusInteracesMap& interfaces,
153                            const std::shared_ptr<bmcweb::AsyncResp>& aResp)
154 {
155     for (const auto& [interface, values] : interfaces)
156     {
157         if (interface == "xyz.openbmc_project.VirtualMedia.MountPoint")
158         {
159             for (const auto& [property, value] : values)
160             {
161                 if (property == "EndpointId")
162                 {
163                     const std::string* endpointIdValue =
164                         std::get_if<std::string>(&value);
165                     if (endpointIdValue == nullptr)
166                     {
167                         continue;
168                     }
169                     if (!endpointIdValue->empty())
170                     {
171                         // Proxy mode
172                         aResp->res
173                             .jsonValue["Oem"]["OpenBMC"]["WebSocketEndpoint"] =
174                             *endpointIdValue;
175                         aResp->res.jsonValue["TransferProtocolType"] = "OEM";
176                     }
177                 }
178                 if (property == "ImageURL")
179                 {
180                     const std::string* imageUrlValue =
181                         std::get_if<std::string>(&value);
182                     if (imageUrlValue != nullptr && !imageUrlValue->empty())
183                     {
184                         std::filesystem::path filePath = *imageUrlValue;
185                         if (!filePath.has_filename())
186                         {
187                             // this will handle https share, which not
188                             // necessarily has to have filename given.
189                             aResp->res.jsonValue["ImageName"] = "";
190                         }
191                         else
192                         {
193                             aResp->res.jsonValue["ImageName"] =
194                                 filePath.filename();
195                         }
196 
197                         aResp->res.jsonValue["Image"] = *imageUrlValue;
198                         aResp->res.jsonValue["TransferProtocolType"] =
199                             getTransferProtocolTypeFromUri(*imageUrlValue);
200 
201                         aResp->res.jsonValue["ConnectedVia"] =
202                             virtual_media::ConnectedVia::URI;
203                     }
204                 }
205                 if (property == "WriteProtected")
206                 {
207                     const bool* writeProtectedValue = std::get_if<bool>(&value);
208                     if (writeProtectedValue != nullptr)
209                     {
210                         aResp->res.jsonValue["WriteProtected"] =
211                             *writeProtectedValue;
212                     }
213                 }
214             }
215         }
216         if (interface == "xyz.openbmc_project.VirtualMedia.Process")
217         {
218             for (const auto& [property, value] : values)
219             {
220                 if (property == "Active")
221                 {
222                     const bool* activeValue = std::get_if<bool>(&value);
223                     if (activeValue == nullptr)
224                     {
225                         BMCWEB_LOG_DEBUG << "Value Active not found";
226                         return;
227                     }
228                     aResp->res.jsonValue["Inserted"] = *activeValue;
229 
230                     if (*activeValue)
231                     {
232                         aResp->res.jsonValue["ConnectedVia"] =
233                             virtual_media::ConnectedVia::Applet;
234                     }
235                 }
236             }
237         }
238     }
239 }
240 
241 /**
242  * @brief Fill template for Virtual Media Item.
243  */
244 inline nlohmann::json vmItemTemplate(const std::string& name,
245                                      const std::string& resName)
246 {
247     nlohmann::json item;
248     item["@odata.id"] = crow::utility::urlFromPieces(
249         "redfish", "v1", "Managers", name, "VirtualMedia", resName);
250 
251     item["@odata.type"] = "#VirtualMedia.v1_3_0.VirtualMedia";
252     item["Name"] = "Virtual Removable Media";
253     item["Id"] = resName;
254     item["WriteProtected"] = true;
255     item["ConnectedVia"] = virtual_media::ConnectedVia::NotConnected;
256     item["MediaTypes"] = nlohmann::json::array_t({"CD", "USBStick"});
257     item["TransferMethod"] = "Stream";
258     item["Oem"]["OpenBMC"]["@odata.type"] =
259         "#OemVirtualMedia.v1_0_0.VirtualMedia";
260 
261     return item;
262 }
263 
264 /**
265  *  @brief Fills collection data
266  */
267 inline void getVmResourceList(std::shared_ptr<bmcweb::AsyncResp> aResp,
268                               const std::string& service,
269                               const std::string& name)
270 {
271     BMCWEB_LOG_DEBUG << "Get available Virtual Media resources.";
272     crow::connections::systemBus->async_method_call(
273         [name, aResp{std::move(aResp)}](
274             const boost::system::error_code& ec,
275             const dbus::utility::ManagedObjectType& subtree) {
276         if (ec)
277         {
278             BMCWEB_LOG_DEBUG << "DBUS response error";
279             return;
280         }
281         nlohmann::json& members = aResp->res.jsonValue["Members"];
282         members = nlohmann::json::array();
283 
284         for (const auto& object : subtree)
285         {
286             nlohmann::json item;
287             std::string path = object.first.filename();
288             if (path.empty())
289             {
290                 continue;
291             }
292 
293             item["@odata.id"] = crow::utility::urlFromPieces(
294                 "redfish", "v1", "Managers", name, "VirtualMedia", path);
295             members.emplace_back(std::move(item));
296         }
297         aResp->res.jsonValue["Members@odata.count"] = members.size();
298         },
299         service, "/xyz/openbmc_project/VirtualMedia",
300         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
301 }
302 
303 inline void
304     afterGetVmData(const std::string& name, const std::string& /*service*/,
305                    const std::string& resName,
306                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
307                    const std::pair<sdbusplus::message::object_path,
308                                    dbus::utility::DBusInteracesMap>& item)
309 {
310     VmMode mode = parseObjectPathAndGetMode(item.first, resName);
311     if (mode == VmMode::Invalid)
312     {
313         return;
314     }
315 
316     asyncResp->res.jsonValue = vmItemTemplate(name, resName);
317 
318     // Check if dbus path is Legacy type
319     if (mode == VmMode::Legacy)
320     {
321         asyncResp->res
322             .jsonValue["Actions"]["#VirtualMedia.InsertMedia"]["target"] =
323             crow::utility::urlFromPieces("redfish", "v1", "Managers", name,
324                                          "VirtualMedia", resName, "Actions",
325                                          "VirtualMedia.InsertMedia");
326     }
327 
328     vmParseInterfaceObject(item.second, asyncResp);
329 
330     asyncResp->res.jsonValue["Actions"]["#VirtualMedia.EjectMedia"]["target"] =
331         crow::utility::urlFromPieces("redfish", "v1", "Managers", name,
332                                      "VirtualMedia", resName, "Actions",
333                                      "VirtualMedia.EjectMedia");
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     doMountVmLegacy(asyncResp, service, resName, *actionParams.imageUrl,
773                     !(*actionParams.writeProtected),
774                     std::move(*actionParams.userName),
775                     std::move(*actionParams.password));
776 }
777 
778 /**
779  * @brief Function transceives data with dbus directly.
780  *
781  * All BMC state properties will be retrieved before sending reset request.
782  */
783 inline void doEjectAction(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
784                           const std::string& service, const std::string& name,
785                           bool legacy)
786 {
787 
788     // Legacy mount requires parameter with image
789     if (legacy)
790     {
791         crow::connections::systemBus->async_method_call(
792             [asyncResp](const boost::system::error_code& ec) {
793             if (ec)
794             {
795                 BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec;
796 
797                 messages::internalError(asyncResp->res);
798                 return;
799             }
800             },
801             service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name,
802             "xyz.openbmc_project.VirtualMedia.Legacy", "Unmount");
803     }
804     else // proxy
805     {
806         crow::connections::systemBus->async_method_call(
807             [asyncResp](const boost::system::error_code& ec) {
808             if (ec)
809             {
810                 BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec;
811 
812                 messages::internalError(asyncResp->res);
813                 return;
814             }
815             },
816             service, "/xyz/openbmc_project/VirtualMedia/Proxy/" + name,
817             "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount");
818     }
819 }
820 
821 inline void handleManagersVirtualMediaActionInsertPost(
822     crow::App& app, const crow::Request& req,
823     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
824     const std::string& name, const std::string& resName)
825 {
826     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
827     {
828         return;
829     }
830 
831     constexpr std::string_view action = "VirtualMedia.InsertMedia";
832     if (name != "bmc")
833     {
834         messages::resourceNotFound(asyncResp->res, action, resName);
835 
836         return;
837     }
838     InsertMediaActionParams actionParams;
839 
840     // Read obligatory parameters (url of image)
841     if (!json_util::readJsonAction(
842             req, asyncResp->res, "Image", actionParams.imageUrl,
843             "WriteProtected", actionParams.writeProtected, "UserName",
844             actionParams.userName, "Password", actionParams.password,
845             "Inserted", actionParams.inserted, "TransferMethod",
846             actionParams.transferMethod, "TransferProtocolType",
847             actionParams.transferProtocolType))
848     {
849         return;
850     }
851 
852     dbus::utility::getDbusObject(
853         "/xyz/openbmc_project/VirtualMedia", {},
854         [asyncResp, action, actionParams,
855          resName](const boost::system::error_code& ec,
856                   const dbus::utility::MapperGetObject& getObjectType) mutable {
857         if (ec)
858         {
859             BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << ec;
860             messages::resourceNotFound(asyncResp->res, action, resName);
861 
862             return;
863         }
864 
865         std::string service = getObjectType.begin()->first;
866         BMCWEB_LOG_DEBUG << "GetObjectType: " << service;
867 
868         crow::connections::systemBus->async_method_call(
869             [service, resName, action, actionParams,
870              asyncResp](const boost::system::error_code& ec2,
871                         dbus::utility::ManagedObjectType& subtree) mutable {
872             if (ec2)
873             {
874                 // Not possible in proxy mode
875                 BMCWEB_LOG_DEBUG << "InsertMedia not "
876                                     "allowed in proxy mode";
877                 messages::resourceNotFound(asyncResp->res, action, resName);
878 
879                 return;
880             }
881             for (const auto& object : subtree)
882             {
883                 VmMode mode = parseObjectPathAndGetMode(object.first, resName);
884                 if (mode == VmMode::Proxy)
885                 {
886                     validateParams(asyncResp, service, resName, actionParams);
887 
888                     return;
889                 }
890             }
891             BMCWEB_LOG_DEBUG << "Parent item not found";
892             messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName);
893             },
894             service, "/xyz/openbmc_project/VirtualMedia",
895             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
896         });
897 }
898 
899 inline void handleManagersVirtualMediaActionEject(
900     crow::App& app, const crow::Request& req,
901     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
902     const std::string& managerName, const std::string& resName)
903 {
904     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
905     {
906         return;
907     }
908 
909     constexpr std::string_view action = "VirtualMedia.EjectMedia";
910     if (managerName != "bmc")
911     {
912         messages::resourceNotFound(asyncResp->res, action, resName);
913 
914         return;
915     }
916 
917     dbus::utility::getDbusObject(
918         "/xyz/openbmc_project/VirtualMedia", {},
919         [asyncResp, action,
920          resName](const boost::system::error_code& ec2,
921                   const dbus::utility::MapperGetObject& getObjectType) {
922         if (ec2)
923         {
924             BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << ec2;
925             messages::internalError(asyncResp->res);
926 
927             return;
928         }
929         std::string service = getObjectType.begin()->first;
930         BMCWEB_LOG_DEBUG << "GetObjectType: " << service;
931 
932         crow::connections::systemBus->async_method_call(
933             [resName, service, action,
934              asyncResp](const boost::system::error_code& ec,
935                         const dbus::utility::ManagedObjectType& subtree) {
936             if (ec)
937             {
938                 BMCWEB_LOG_ERROR << "ObjectMapper : No Service found";
939                 messages::resourceNotFound(asyncResp->res, action, resName);
940                 return;
941             }
942 
943             for (const auto& object : subtree)
944             {
945 
946                 VmMode mode = parseObjectPathAndGetMode(object.first, resName);
947                 if (mode != VmMode::Invalid)
948                 {
949                     doEjectAction(asyncResp, service, resName,
950                                   mode == VmMode::Legacy);
951                 }
952             }
953             BMCWEB_LOG_DEBUG << "Parent item not found";
954             messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName);
955             },
956             service, "/xyz/openbmc_project/VirtualMedia",
957             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
958         });
959 }
960 
961 inline void handleManagersVirtualMediaCollectionGet(
962     crow::App& app, const crow::Request& req,
963     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
964     const std::string& name)
965 {
966     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
967     {
968         return;
969     }
970     if (name != "bmc")
971     {
972         messages::resourceNotFound(asyncResp->res, "VirtualMedia", name);
973 
974         return;
975     }
976 
977     asyncResp->res.jsonValue["@odata.type"] =
978         "#VirtualMediaCollection.VirtualMediaCollection";
979     asyncResp->res.jsonValue["Name"] = "Virtual Media Services";
980     asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
981         "redfish", "v1", "Managers", name, "VirtualMedia");
982 
983     dbus::utility::getDbusObject(
984         "/xyz/openbmc_project/VirtualMedia", {},
985         [asyncResp, name](const boost::system::error_code& ec,
986                           const dbus::utility::MapperGetObject& getObjectType) {
987         if (ec)
988         {
989             BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << ec;
990             messages::internalError(asyncResp->res);
991 
992             return;
993         }
994         std::string service = getObjectType.begin()->first;
995         BMCWEB_LOG_DEBUG << "GetObjectType: " << service;
996 
997         getVmResourceList(asyncResp, service, name);
998         });
999 }
1000 
1001 inline void
1002     handleVirtualMediaGet(crow::App& app, const crow::Request& req,
1003                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1004                           const std::string& name, const std::string& resName)
1005 {
1006     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1007     {
1008         return;
1009     }
1010     if (name != "bmc")
1011     {
1012         messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName);
1013 
1014         return;
1015     }
1016 
1017     dbus::utility::getDbusObject(
1018         "/xyz/openbmc_project/VirtualMedia", {},
1019         [asyncResp, name,
1020          resName](const boost::system::error_code& ec,
1021                   const dbus::utility::MapperGetObject& getObjectType) {
1022         if (ec)
1023         {
1024             BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << ec;
1025             messages::internalError(asyncResp->res);
1026 
1027             return;
1028         }
1029         std::string service = getObjectType.begin()->first;
1030         BMCWEB_LOG_DEBUG << "GetObjectType: " << service;
1031 
1032         getVmData(asyncResp, service, name, resName);
1033         });
1034 }
1035 
1036 inline void requestNBDVirtualMediaRoutes(App& app)
1037 {
1038     BMCWEB_ROUTE(
1039         app,
1040         "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/VirtualMedia.InsertMedia")
1041         .privileges(redfish::privileges::postVirtualMedia)
1042         .methods(boost::beast::http::verb::post)(std::bind_front(
1043             handleManagersVirtualMediaActionInsertPost, std::ref(app)));
1044 
1045     BMCWEB_ROUTE(
1046         app,
1047         "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/VirtualMedia.EjectMedia")
1048         .privileges(redfish::privileges::postVirtualMedia)
1049         .methods(boost::beast::http::verb::post)(std::bind_front(
1050             handleManagersVirtualMediaActionEject, std::ref(app)));
1051 
1052     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/")
1053         .privileges(redfish::privileges::getVirtualMediaCollection)
1054         .methods(boost::beast::http::verb::get)(std::bind_front(
1055             handleManagersVirtualMediaCollectionGet, std::ref(app)));
1056 
1057     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/<str>/")
1058         .privileges(redfish::privileges::getVirtualMedia)
1059         .methods(boost::beast::http::verb::get)(
1060             std::bind_front(handleVirtualMediaGet, std::ref(app)));
1061 }
1062 
1063 } // namespace redfish
1064