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