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