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