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