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 <boost/process/async_pipe.hpp>
21 #include <boost/type_traits/has_dereference.hpp>
22 #include <boost/url/url_view.hpp>
23 #include <query.hpp>
24 #include <registries/privilege_registry.hpp>
25 #include <utils/json_utils.hpp>
26 
27 namespace redfish
28 {
29 /**
30  * @brief Function extracts transfer protocol name from URI.
31  */
32 inline std::string getTransferProtocolTypeFromUri(const std::string& imageUri)
33 {
34     boost::urls::result<boost::urls::url_view> url =
35         boost::urls::parse_uri(boost::string_view(imageUri));
36     if (!url)
37     {
38         return "None";
39     }
40     boost::string_view scheme = url->scheme();
41     if (scheme == "smb")
42     {
43         return "CIFS";
44     }
45     if (scheme == "https")
46     {
47         return "HTTPS";
48     }
49 
50     return "None";
51 }
52 
53 /**
54  * @brief Read all known properties from VM object interfaces
55  */
56 inline void
57     vmParseInterfaceObject(const dbus::utility::DBusInteracesMap& interfaces,
58                            const std::shared_ptr<bmcweb::AsyncResp>& aResp)
59 {
60     for (const auto& [interface, values] : interfaces)
61     {
62         if (interface == "xyz.openbmc_project.VirtualMedia.MountPoint")
63         {
64             for (const auto& [property, value] : values)
65             {
66                 if (property == "EndpointId")
67                 {
68                     const std::string* endpointIdValue =
69                         std::get_if<std::string>(&value);
70                     if (endpointIdValue == nullptr)
71                     {
72                         continue;
73                     }
74                     if (!endpointIdValue->empty())
75                     {
76                         // Proxy mode
77                         aResp->res
78                             .jsonValue["Oem"]["OpenBMC"]["WebSocketEndpoint"] =
79                             *endpointIdValue;
80                         aResp->res.jsonValue["TransferProtocolType"] = "OEM";
81                     }
82                 }
83                 if (property == "ImageURL")
84                 {
85                     const std::string* imageUrlValue =
86                         std::get_if<std::string>(&value);
87                     if (imageUrlValue != nullptr && !imageUrlValue->empty())
88                     {
89                         std::filesystem::path filePath = *imageUrlValue;
90                         if (!filePath.has_filename())
91                         {
92                             // this will handle https share, which not
93                             // necessarily has to have filename given.
94                             aResp->res.jsonValue["ImageName"] = "";
95                         }
96                         else
97                         {
98                             aResp->res.jsonValue["ImageName"] =
99                                 filePath.filename();
100                         }
101 
102                         aResp->res.jsonValue["Image"] = *imageUrlValue;
103                         aResp->res.jsonValue["TransferProtocolType"] =
104                             getTransferProtocolTypeFromUri(*imageUrlValue);
105 
106                         aResp->res.jsonValue["ConnectedVia"] = "URI";
107                     }
108                 }
109                 if (property == "WriteProtected")
110                 {
111                     const bool* writeProtectedValue = std::get_if<bool>(&value);
112                     if (writeProtectedValue != nullptr)
113                     {
114                         aResp->res.jsonValue["WriteProtected"] =
115                             *writeProtectedValue;
116                     }
117                 }
118             }
119         }
120         if (interface == "xyz.openbmc_project.VirtualMedia.Process")
121         {
122             for (const auto& [property, value] : values)
123             {
124                 if (property == "Active")
125                 {
126                     const bool* activeValue = std::get_if<bool>(&value);
127                     if (activeValue == nullptr)
128                     {
129                         BMCWEB_LOG_DEBUG << "Value Active not found";
130                         return;
131                     }
132                     aResp->res.jsonValue["Inserted"] = *activeValue;
133 
134                     if (*activeValue)
135                     {
136                         aResp->res.jsonValue["ConnectedVia"] = "Applet";
137                     }
138                 }
139             }
140         }
141     }
142 }
143 
144 /**
145  * @brief Fill template for Virtual Media Item.
146  */
147 inline nlohmann::json vmItemTemplate(const std::string& name,
148                                      const std::string& resName)
149 {
150     nlohmann::json item;
151     item["@odata.id"] = crow::utility::urlFromPieces(
152         "redfish", "v1", "Managers", name, "VirtualMedia", resName);
153 
154     item["@odata.type"] = "#VirtualMedia.v1_3_0.VirtualMedia";
155     item["Name"] = "Virtual Removable Media";
156     item["Id"] = resName;
157     item["WriteProtected"] = true;
158     item["MediaTypes"] = nlohmann::json::array_t({"CD", "USBStick"});
159     item["TransferMethod"] = "Stream";
160     item["Oem"]["OpenBMC"]["@odata.type"] =
161         "#OemVirtualMedia.v1_0_0.VirtualMedia";
162 
163     return item;
164 }
165 
166 /**
167  *  @brief Fills collection data
168  */
169 inline void getVmResourceList(std::shared_ptr<bmcweb::AsyncResp> aResp,
170                               const std::string& service,
171                               const std::string& name)
172 {
173     BMCWEB_LOG_DEBUG << "Get available Virtual Media resources.";
174     crow::connections::systemBus->async_method_call(
175         [name, aResp{std::move(aResp)}](
176             const boost::system::error_code ec,
177             const dbus::utility::ManagedObjectType& subtree) {
178         if (ec)
179         {
180             BMCWEB_LOG_DEBUG << "DBUS response error";
181             return;
182         }
183         nlohmann::json& members = aResp->res.jsonValue["Members"];
184         members = nlohmann::json::array();
185 
186         for (const auto& object : subtree)
187         {
188             nlohmann::json item;
189             std::string path = object.first.filename();
190             if (path.empty())
191             {
192                 continue;
193             }
194 
195             item["@odata.id"] = crow::utility::urlFromPieces(
196                 "redfish", "v1", "Managers", name, "VirtualMedia", path);
197             members.emplace_back(std::move(item));
198         }
199         aResp->res.jsonValue["Members@odata.count"] = members.size();
200         },
201         service, "/xyz/openbmc_project/VirtualMedia",
202         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
203 }
204 
205 /**
206  *  @brief Fills data for specific resource
207  */
208 inline void getVmData(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
209                       const std::string& service, const std::string& name,
210                       const std::string& resName)
211 {
212     BMCWEB_LOG_DEBUG << "Get Virtual Media resource data.";
213 
214     crow::connections::systemBus->async_method_call(
215         [resName, name,
216          aResp](const boost::system::error_code ec,
217                 const dbus::utility::ManagedObjectType& subtree) {
218         if (ec)
219         {
220             BMCWEB_LOG_DEBUG << "DBUS response error";
221 
222             return;
223         }
224 
225         for (const auto& item : subtree)
226         {
227             std::string thispath = item.first.filename();
228             if (thispath.empty())
229             {
230                 continue;
231             }
232 
233             if (thispath != resName)
234             {
235                 continue;
236             }
237 
238             // "Legacy"/"Proxy"
239             auto mode = item.first.parent_path();
240             // "VirtualMedia"
241             auto type = mode.parent_path();
242             if (mode.filename().empty() || type.filename().empty())
243             {
244                 continue;
245             }
246 
247             if (type.filename() != "VirtualMedia")
248             {
249                 continue;
250             }
251 
252             aResp->res.jsonValue = vmItemTemplate(name, resName);
253 
254             // Check if dbus path is Legacy type
255             if (mode.filename() == "Legacy")
256             {
257                 aResp->res.jsonValue["Actions"]["#VirtualMedia.InsertMedia"]
258                                     ["target"] = crow::utility::urlFromPieces(
259                     "redfish", "v1", "Managers", name, "VirtualMedia", resName,
260                     "Actions", "VirtualMedia.InsertMedia");
261             }
262 
263             vmParseInterfaceObject(item.second, aResp);
264 
265             aResp->res
266                 .jsonValue["Actions"]["#VirtualMedia.EjectMedia"]["target"] =
267                 crow::utility::urlFromPieces("redfish", "v1", "Managers", name,
268                                              "VirtualMedia", resName, "Actions",
269                                              "VirtualMedia.EjectMedia");
270             return;
271         }
272 
273         messages::resourceNotFound(aResp->res, "VirtualMedia", resName);
274         },
275         service, "/xyz/openbmc_project/VirtualMedia",
276         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
277 }
278 
279 /**
280  * @brief Transfer protocols supported for InsertMedia action.
281  *
282  */
283 enum class TransferProtocol
284 {
285     https,
286     smb,
287     invalid
288 };
289 
290 /**
291  * @brief Function extracts transfer protocol type from URI.
292  *
293  */
294 inline std::optional<TransferProtocol>
295     getTransferProtocolFromUri(const boost::urls::url_view& imageUri)
296 {
297     boost::string_view scheme = imageUri.scheme();
298     if (scheme == "smb")
299     {
300         return TransferProtocol::smb;
301     }
302     if (scheme == "https")
303     {
304         return TransferProtocol::https;
305     }
306     if (!scheme.empty())
307     {
308         return TransferProtocol::invalid;
309     }
310 
311     return {};
312 }
313 
314 /**
315  * @brief Function convert transfer protocol from string param.
316  *
317  */
318 inline std::optional<TransferProtocol> getTransferProtocolFromParam(
319     const std::optional<std::string>& transferProtocolType)
320 {
321     if (transferProtocolType == std::nullopt)
322     {
323         return {};
324     }
325 
326     if (*transferProtocolType == "CIFS")
327     {
328         return TransferProtocol::smb;
329     }
330 
331     if (*transferProtocolType == "HTTPS")
332     {
333         return TransferProtocol::https;
334     }
335 
336     return TransferProtocol::invalid;
337 }
338 
339 /**
340  * @brief Function extends URI with transfer protocol type.
341  *
342  */
343 inline std::string
344     getUriWithTransferProtocol(const std::string& imageUri,
345                                const TransferProtocol& transferProtocol)
346 {
347     if (transferProtocol == TransferProtocol::smb)
348     {
349         return "smb://" + imageUri;
350     }
351 
352     if (transferProtocol == TransferProtocol::https)
353     {
354         return "https://" + imageUri;
355     }
356 
357     return imageUri;
358 }
359 
360 /**
361  * @brief Function validate parameters of insert media request.
362  *
363  */
364 inline bool
365     validateParams(crow::Response& res, std::string& imageUrl,
366                    const std::optional<bool>& inserted,
367                    const std::optional<std::string>& transferMethod,
368                    const std::optional<std::string>& transferProtocolType)
369 {
370     BMCWEB_LOG_DEBUG << "Validation started";
371     // required param imageUrl must not be empty
372     if (imageUrl.empty())
373     {
374         BMCWEB_LOG_ERROR << "Request action parameter Image is empty.";
375 
376         messages::propertyValueFormatError(res, "<empty>", "Image");
377 
378         return false;
379     }
380 
381     // optional param inserted must be true
382     if ((inserted != std::nullopt) && !*inserted)
383     {
384         BMCWEB_LOG_ERROR
385             << "Request action optional parameter Inserted must be true.";
386 
387         messages::actionParameterNotSupported(res, "Inserted", "InsertMedia");
388 
389         return false;
390     }
391 
392     // optional param transferMethod must be stream
393     if ((transferMethod != std::nullopt) && (*transferMethod != "Stream"))
394     {
395         BMCWEB_LOG_ERROR << "Request action optional parameter "
396                             "TransferMethod must be Stream.";
397 
398         messages::actionParameterNotSupported(res, "TransferMethod",
399                                               "InsertMedia");
400 
401         return false;
402     }
403     boost::urls::result<boost::urls::url_view> url =
404         boost::urls::parse_uri(boost::string_view(imageUrl));
405     if (!url)
406     {
407         messages::actionParameterValueFormatError(res, imageUrl, "Image",
408                                                   "InsertMedia");
409         return false;
410     }
411     std::optional<TransferProtocol> uriTransferProtocolType =
412         getTransferProtocolFromUri(*url);
413 
414     std::optional<TransferProtocol> paramTransferProtocolType =
415         getTransferProtocolFromParam(transferProtocolType);
416 
417     // ImageUrl does not contain valid protocol type
418     if (*uriTransferProtocolType == TransferProtocol::invalid)
419     {
420         BMCWEB_LOG_ERROR << "Request action parameter ImageUrl must "
421                             "contain specified protocol type from list: "
422                             "(smb, https).";
423 
424         messages::resourceAtUriInUnknownFormat(res, *url);
425 
426         return false;
427     }
428 
429     // transferProtocolType should contain value from list
430     if (*paramTransferProtocolType == TransferProtocol::invalid)
431     {
432         BMCWEB_LOG_ERROR << "Request action parameter TransferProtocolType "
433                             "must be provided with value from list: "
434                             "(CIFS, HTTPS).";
435 
436         messages::propertyValueNotInList(res, *transferProtocolType,
437                                          "TransferProtocolType");
438         return false;
439     }
440 
441     // valid transfer protocol not provided either with URI nor param
442     if ((uriTransferProtocolType == std::nullopt) &&
443         (paramTransferProtocolType == std::nullopt))
444     {
445         BMCWEB_LOG_ERROR << "Request action parameter ImageUrl must "
446                             "contain specified protocol type or param "
447                             "TransferProtocolType must be provided.";
448 
449         messages::resourceAtUriInUnknownFormat(res, *url);
450 
451         return false;
452     }
453 
454     // valid transfer protocol provided both with URI and param
455     if ((paramTransferProtocolType != std::nullopt) &&
456         (uriTransferProtocolType != std::nullopt))
457     {
458         // check if protocol is the same for URI and param
459         if (*paramTransferProtocolType != *uriTransferProtocolType)
460         {
461             BMCWEB_LOG_ERROR << "Request action parameter "
462                                 "TransferProtocolType must  contain the "
463                                 "same protocol type as protocol type "
464                                 "provided with param imageUrl.";
465 
466             messages::actionParameterValueTypeError(res, *transferProtocolType,
467                                                     "TransferProtocolType",
468                                                     "InsertMedia");
469 
470             return false;
471         }
472     }
473 
474     // validation passed
475     // add protocol to URI if needed
476     if (uriTransferProtocolType == std::nullopt)
477     {
478         imageUrl =
479             getUriWithTransferProtocol(imageUrl, *paramTransferProtocolType);
480     }
481 
482     return true;
483 }
484 
485 template <typename T>
486 static void secureCleanup(T& value)
487 {
488     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
489     auto raw = const_cast<typename T::value_type*>(value.data());
490     explicit_bzero(raw, value.size() * sizeof(*raw));
491 }
492 
493 class Credentials
494 {
495   public:
496     Credentials(std::string&& user, std::string&& password) :
497         userBuf(std::move(user)), passBuf(std::move(password))
498     {}
499 
500     ~Credentials()
501     {
502         secureCleanup(userBuf);
503         secureCleanup(passBuf);
504     }
505 
506     const std::string& user()
507     {
508         return userBuf;
509     }
510 
511     const std::string& password()
512     {
513         return passBuf;
514     }
515 
516     Credentials() = delete;
517     Credentials(const Credentials&) = delete;
518     Credentials& operator=(const Credentials&) = delete;
519     Credentials(Credentials&&) = delete;
520     Credentials& operator=(Credentials&&) = delete;
521 
522   private:
523     std::string userBuf;
524     std::string passBuf;
525 };
526 
527 class CredentialsProvider
528 {
529   public:
530     template <typename T>
531     struct Deleter
532     {
533         void operator()(T* buff) const
534         {
535             if (buff)
536             {
537                 secureCleanup(*buff);
538                 delete buff;
539             }
540         }
541     };
542 
543     using Buffer = std::vector<char>;
544     using SecureBuffer = std::unique_ptr<Buffer, Deleter<Buffer>>;
545     // Using explicit definition instead of std::function to avoid implicit
546     // conversions eg. stack copy instead of reference
547     using FormatterFunc = void(const std::string& username,
548                                const std::string& password, Buffer& dest);
549 
550     CredentialsProvider(std::string&& user, std::string&& password) :
551         credentials(std::move(user), std::move(password))
552     {}
553 
554     const std::string& user()
555     {
556         return credentials.user();
557     }
558 
559     const std::string& password()
560     {
561         return credentials.password();
562     }
563 
564     SecureBuffer pack(FormatterFunc formatter)
565     {
566         SecureBuffer packed{new Buffer{}};
567         if (formatter != nullptr)
568         {
569             formatter(credentials.user(), credentials.password(), *packed);
570         }
571 
572         return packed;
573     }
574 
575   private:
576     Credentials credentials;
577 };
578 
579 // Wrapper for boost::async_pipe ensuring proper pipe cleanup
580 template <typename Buffer>
581 class Pipe
582 {
583   public:
584     using unix_fd = sdbusplus::message::unix_fd;
585 
586     Pipe(boost::asio::io_context& io, Buffer&& bufferIn) :
587         impl(io), buffer{std::move(bufferIn)}
588     {}
589 
590     ~Pipe()
591     {
592         // Named pipe needs to be explicitly removed
593         impl.close();
594     }
595 
596     Pipe(const Pipe&) = delete;
597     Pipe(Pipe&&) = delete;
598     Pipe& operator=(const Pipe&) = delete;
599     Pipe& operator=(Pipe&&) = delete;
600 
601     unix_fd fd()
602     {
603         return unix_fd{impl.native_source()};
604     }
605 
606     template <typename WriteHandler>
607     void asyncWrite(WriteHandler&& handler)
608     {
609         impl.async_write_some(data(), std::forward<WriteHandler>(handler));
610     }
611 
612   private:
613     // Specialization for pointer types
614     template <typename B = Buffer>
615     typename std::enable_if<boost::has_dereference<B>::value,
616                             boost::asio::const_buffer>::type
617         data()
618     {
619         return boost::asio::buffer(*buffer);
620     }
621 
622     template <typename B = Buffer>
623     typename std::enable_if<!boost::has_dereference<B>::value,
624                             boost::asio::const_buffer>::type
625         data()
626     {
627         return boost::asio::buffer(buffer);
628     }
629 
630     const std::string name;
631     boost::process::async_pipe impl;
632     Buffer buffer;
633 };
634 
635 /**
636  * @brief Function transceives data with dbus directly.
637  *
638  * All BMC state properties will be retrieved before sending reset request.
639  */
640 inline void doMountVmLegacy(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
641                             const std::string& service, const std::string& name,
642                             const std::string& imageUrl, const bool rw,
643                             std::string&& userName, std::string&& password)
644 {
645     using SecurePipe = Pipe<CredentialsProvider::SecureBuffer>;
646     constexpr const size_t secretLimit = 1024;
647 
648     std::shared_ptr<SecurePipe> secretPipe;
649     dbus::utility::DbusVariantType unixFd = -1;
650 
651     if (!userName.empty() || !password.empty())
652     {
653         // Encapsulate in safe buffer
654         CredentialsProvider credentials(std::move(userName),
655                                         std::move(password));
656 
657         // Payload must contain data + NULL delimiters
658         if (credentials.user().size() + credentials.password().size() + 2 >
659             secretLimit)
660         {
661             BMCWEB_LOG_ERROR << "Credentials too long to handle";
662             messages::unrecognizedRequestBody(asyncResp->res);
663             return;
664         }
665 
666         // Pack secret
667         auto secret = credentials.pack(
668             [](const auto& user, const auto& pass, auto& buff) {
669             std::copy(user.begin(), user.end(), std::back_inserter(buff));
670             buff.push_back('\0');
671             std::copy(pass.begin(), pass.end(), std::back_inserter(buff));
672             buff.push_back('\0');
673         });
674 
675         // Open pipe
676         secretPipe = std::make_shared<SecurePipe>(
677             crow::connections::systemBus->get_io_context(), std::move(secret));
678         unixFd = secretPipe->fd();
679 
680         // Pass secret over pipe
681         secretPipe->asyncWrite(
682             [asyncResp](const boost::system::error_code& ec, std::size_t) {
683             if (ec)
684             {
685                 BMCWEB_LOG_ERROR << "Failed to pass secret: " << ec;
686                 messages::internalError(asyncResp->res);
687             }
688         });
689     }
690 
691     crow::connections::systemBus->async_method_call(
692         [asyncResp, secretPipe](const boost::system::error_code ec,
693                                 bool success) {
694         if (ec)
695         {
696             BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec;
697             messages::internalError(asyncResp->res);
698         }
699         else if (!success)
700         {
701             BMCWEB_LOG_ERROR << "Service responded with error";
702             messages::generalError(asyncResp->res);
703         }
704         },
705         service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name,
706         "xyz.openbmc_project.VirtualMedia.Legacy", "Mount", imageUrl, rw,
707         unixFd);
708 }
709 
710 /**
711  * @brief Function transceives data with dbus directly.
712  *
713  * All BMC state properties will be retrieved before sending reset request.
714  */
715 inline void doVmAction(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
716                        const std::string& service, const std::string& name,
717                        bool legacy)
718 {
719 
720     // Legacy mount requires parameter with image
721     if (legacy)
722     {
723         crow::connections::systemBus->async_method_call(
724             [asyncResp](const boost::system::error_code ec) {
725             if (ec)
726             {
727                 BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec;
728 
729                 messages::internalError(asyncResp->res);
730                 return;
731             }
732             },
733             service, "/xyz/openbmc_project/VirtualMedia/Legacy/" + name,
734             "xyz.openbmc_project.VirtualMedia.Legacy", "Unmount");
735     }
736     else // proxy
737     {
738         crow::connections::systemBus->async_method_call(
739             [asyncResp](const boost::system::error_code ec) {
740             if (ec)
741             {
742                 BMCWEB_LOG_ERROR << "Bad D-Bus request error: " << ec;
743 
744                 messages::internalError(asyncResp->res);
745                 return;
746             }
747             },
748             service, "/xyz/openbmc_project/VirtualMedia/Proxy/" + name,
749             "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount");
750     }
751 }
752 
753 struct InsertMediaActionParams
754 {
755     std::string imageUrl;
756     std::optional<std::string> userName;
757     std::optional<std::string> password;
758     std::optional<std::string> transferMethod;
759     std::optional<std::string> transferProtocolType;
760     std::optional<bool> writeProtected = true;
761     std::optional<bool> inserted;
762 };
763 
764 inline void handleManagersVirtualMediaActionInsertPost(
765     crow::App& app, const crow::Request& req,
766     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
767     const std::string& name, const std::string& resName)
768 {
769     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
770     {
771         return;
772     }
773     if (name != "bmc")
774     {
775         messages::resourceNotFound(asyncResp->res, "VirtualMedia.Insert",
776                                    resName);
777 
778         return;
779     }
780     InsertMediaActionParams actionParams;
781 
782     // Read obligatory parameters (url of
783     // image)
784     if (!json_util::readJsonAction(
785             req, asyncResp->res, "Image", actionParams.imageUrl,
786             "WriteProtected", actionParams.writeProtected, "UserName",
787             actionParams.userName, "Password", actionParams.password,
788             "Inserted", actionParams.inserted, "TransferMethod",
789             actionParams.transferMethod, "TransferProtocolType",
790             actionParams.transferProtocolType))
791     {
792         BMCWEB_LOG_DEBUG << "Image is not provided";
793         return;
794     }
795 
796     bool paramsValid = validateParams(
797         asyncResp->res, actionParams.imageUrl, actionParams.inserted,
798         actionParams.transferMethod, actionParams.transferProtocolType);
799 
800     if (!paramsValid)
801     {
802         return;
803     }
804 
805     crow::connections::systemBus->async_method_call(
806         [asyncResp, actionParams,
807          resName](const boost::system::error_code ec,
808                   const dbus::utility::MapperGetObject& getObjectType) mutable {
809         if (ec)
810         {
811             BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << ec;
812             messages::internalError(asyncResp->res);
813 
814             return;
815         }
816         std::string service = getObjectType.begin()->first;
817         BMCWEB_LOG_DEBUG << "GetObjectType: " << service;
818 
819         crow::connections::systemBus->async_method_call(
820             [service, resName, actionParams,
821              asyncResp](const boost::system::error_code ec2,
822                         dbus::utility::ManagedObjectType& subtree) mutable {
823             if (ec2)
824             {
825                 BMCWEB_LOG_DEBUG << "DBUS response error";
826 
827                 return;
828             }
829 
830             for (const auto& object : subtree)
831             {
832                 const std::string& path =
833                     static_cast<const std::string&>(object.first);
834 
835                 std::size_t lastIndex = path.rfind('/');
836                 if (lastIndex == std::string::npos)
837                 {
838                     continue;
839                 }
840 
841                 lastIndex += 1;
842 
843                 if (path.substr(lastIndex) == resName)
844                 {
845                     lastIndex = path.rfind("Proxy");
846                     if (lastIndex != std::string::npos)
847                     {
848                         // Not possible in proxy mode
849                         BMCWEB_LOG_DEBUG << "InsertMedia not "
850                                             "allowed in proxy mode";
851                         messages::resourceNotFound(asyncResp->res,
852                                                    "VirtualMedia.InsertMedia",
853                                                    resName);
854 
855                         return;
856                     }
857 
858                     lastIndex = path.rfind("Legacy");
859                     if (lastIndex == std::string::npos)
860                     {
861                         continue;
862                     }
863 
864                     // manager is irrelevant for
865                     // VirtualMedia dbus calls
866                     doMountVmLegacy(asyncResp, service, resName,
867                                     actionParams.imageUrl,
868                                     !(*actionParams.writeProtected),
869                                     std::move(*actionParams.userName),
870                                     std::move(*actionParams.password));
871 
872                     return;
873                 }
874             }
875             BMCWEB_LOG_DEBUG << "Parent item not found";
876             messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName);
877             },
878             service, "/xyz/openbmc_project/VirtualMedia",
879             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
880         },
881         "xyz.openbmc_project.ObjectMapper",
882         "/xyz/openbmc_project/object_mapper",
883         "xyz.openbmc_project.ObjectMapper", "GetObject",
884         "/xyz/openbmc_project/VirtualMedia", std::array<const char*, 0>());
885 }
886 
887 inline void handleManagersVirtualMediaActionEject(
888     crow::App& app, const crow::Request& req,
889     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
890     const std::string& managerName, const std::string& resName)
891 {
892     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
893     {
894         return;
895     }
896     if (managerName != "bmc")
897     {
898         messages::resourceNotFound(asyncResp->res, "VirtualMedia.Eject",
899                                    resName);
900 
901         return;
902     }
903 
904     crow::connections::systemBus->async_method_call(
905         [asyncResp,
906          resName](const boost::system::error_code ec2,
907                   const dbus::utility::MapperGetObject& getObjectType) {
908         if (ec2)
909         {
910             BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << ec2;
911             messages::internalError(asyncResp->res);
912 
913             return;
914         }
915         std::string service = getObjectType.begin()->first;
916         BMCWEB_LOG_DEBUG << "GetObjectType: " << service;
917 
918         crow::connections::systemBus->async_method_call(
919             [resName, service, asyncResp{asyncResp}](
920                 const boost::system::error_code ec,
921                 const dbus::utility::ManagedObjectType& subtree) {
922             if (ec)
923             {
924                 BMCWEB_LOG_DEBUG << "DBUS response error";
925 
926                 return;
927             }
928 
929             for (const auto& object : subtree)
930             {
931                 const std::string& path =
932                     static_cast<const std::string&>(object.first);
933 
934                 std::size_t lastIndex = path.rfind('/');
935                 if (lastIndex == std::string::npos)
936                 {
937                     continue;
938                 }
939 
940                 lastIndex += 1;
941 
942                 if (path.substr(lastIndex) == resName)
943                 {
944                     lastIndex = path.rfind("Proxy");
945                     if (lastIndex != std::string::npos)
946                     {
947                         // Proxy mode
948                         doVmAction(asyncResp, service, resName, false);
949                     }
950 
951                     lastIndex = path.rfind("Legacy");
952                     if (lastIndex != std::string::npos)
953                     {
954                         // Legacy mode
955                         doVmAction(asyncResp, service, resName, true);
956                     }
957 
958                     return;
959                 }
960             }
961             BMCWEB_LOG_DEBUG << "Parent item not found";
962             messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName);
963             },
964             service, "/xyz/openbmc_project/VirtualMedia",
965             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
966         },
967         "xyz.openbmc_project.ObjectMapper",
968         "/xyz/openbmc_project/object_mapper",
969         "xyz.openbmc_project.ObjectMapper", "GetObject",
970         "/xyz/openbmc_project/VirtualMedia", std::array<const char*, 0>());
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"] = crow::utility::urlFromPieces(
993         "redfish", "v1", "Managers", name, "VirtualMedia");
994 
995     crow::connections::systemBus->async_method_call(
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         "xyz.openbmc_project.ObjectMapper",
1011         "/xyz/openbmc_project/object_mapper",
1012         "xyz.openbmc_project.ObjectMapper", "GetObject",
1013         "/xyz/openbmc_project/VirtualMedia", std::array<const char*, 0>());
1014 }
1015 
1016 inline void
1017     handleVirtualMediaGet(crow::App& app, const crow::Request& req,
1018                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1019                           const std::string& name, const std::string& resName)
1020 {
1021     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1022     {
1023         return;
1024     }
1025     if (name != "bmc")
1026     {
1027         messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName);
1028 
1029         return;
1030     }
1031 
1032     crow::connections::systemBus->async_method_call(
1033         [asyncResp, name,
1034          resName](const boost::system::error_code ec,
1035                   const dbus::utility::MapperGetObject& getObjectType) {
1036         if (ec)
1037         {
1038             BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << ec;
1039             messages::internalError(asyncResp->res);
1040 
1041             return;
1042         }
1043         std::string service = getObjectType.begin()->first;
1044         BMCWEB_LOG_DEBUG << "GetObjectType: " << service;
1045 
1046         getVmData(asyncResp, service, name, resName);
1047         },
1048         "xyz.openbmc_project.ObjectMapper",
1049         "/xyz/openbmc_project/object_mapper",
1050         "xyz.openbmc_project.ObjectMapper", "GetObject",
1051         "/xyz/openbmc_project/VirtualMedia", std::array<const char*, 0>());
1052 }
1053 
1054 inline void requestNBDVirtualMediaRoutes(App& app)
1055 {
1056     BMCWEB_ROUTE(
1057         app,
1058         "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/VirtualMedia.InsertMedia")
1059         .privileges(redfish::privileges::postVirtualMedia)
1060         .methods(boost::beast::http::verb::post)(std::bind_front(
1061             handleManagersVirtualMediaActionInsertPost, std::ref(app)));
1062 
1063     BMCWEB_ROUTE(
1064         app,
1065         "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/VirtualMedia.EjectMedia")
1066         .privileges(redfish::privileges::postVirtualMedia)
1067         .methods(boost::beast::http::verb::post)(std::bind_front(
1068             handleManagersVirtualMediaActionEject, std::ref(app)));
1069 
1070     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/")
1071         .privileges(redfish::privileges::getVirtualMediaCollection)
1072         .methods(boost::beast::http::verb::get)(std::bind_front(
1073             handleManagersVirtualMediaCollectionGet, std::ref(app)));
1074 
1075     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/<str>/")
1076         .privileges(redfish::privileges::getVirtualMedia)
1077         .methods(boost::beast::http::verb::get)(
1078             std::bind_front(handleVirtualMediaGet, std::ref(app)));
1079 }
1080 
1081 } // namespace redfish
1082