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