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