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