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