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