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