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 "dbus_utility.hpp"
21 #include "query.hpp"
22 #include "registries/privilege_registry.hpp"
23 #include "utils/json_utils.hpp"
24 
25 #include <boost/process/async_pipe.hpp>
26 #include <boost/type_traits/has_dereference.hpp>
27 #include <boost/url/url_view.hpp>
28 
29 #include <array>
30 #include <string_view>
31 
32 namespace redfish
33 {
34 /**
35  * @brief Function extracts transfer protocol name from URI.
36  */
37 inline std::string getTransferProtocolTypeFromUri(const std::string& imageUri)
38 {
39     boost::urls::result<boost::urls::url_view> url =
40         boost::urls::parse_uri(imageUri);
41     if (!url)
42     {
43         return "None";
44     }
45     std::string_view scheme = url->scheme();
46     if (scheme == "smb")
47     {
48         return "CIFS";
49     }
50     if (scheme == "https")
51     {
52         return "HTTPS";
53     }
54 
55     return "None";
56 }
57 
58 /**
59  * @brief Read all known properties from VM object interfaces
60  */
61 inline void
62     vmParseInterfaceObject(const dbus::utility::DBusInteracesMap& interfaces,
63                            const std::shared_ptr<bmcweb::AsyncResp>& aResp)
64 {
65     for (const auto& [interface, values] : interfaces)
66     {
67         if (interface == "xyz.openbmc_project.VirtualMedia.MountPoint")
68         {
69             for (const auto& [property, value] : values)
70             {
71                 if (property == "EndpointId")
72                 {
73                     const std::string* endpointIdValue =
74                         std::get_if<std::string>(&value);
75                     if (endpointIdValue == nullptr)
76                     {
77                         continue;
78                     }
79                     if (!endpointIdValue->empty())
80                     {
81                         // Proxy mode
82                         aResp->res
83                             .jsonValue["Oem"]["OpenBMC"]["WebSocketEndpoint"] =
84                             *endpointIdValue;
85                         aResp->res.jsonValue["TransferProtocolType"] = "OEM";
86                     }
87                 }
88                 if (property == "ImageURL")
89                 {
90                     const std::string* imageUrlValue =
91                         std::get_if<std::string>(&value);
92                     if (imageUrlValue != nullptr && !imageUrlValue->empty())
93                     {
94                         std::filesystem::path filePath = *imageUrlValue;
95                         if (!filePath.has_filename())
96                         {
97                             // this will handle https share, which not
98                             // necessarily has to have filename given.
99                             aResp->res.jsonValue["ImageName"] = "";
100                         }
101                         else
102                         {
103                             aResp->res.jsonValue["ImageName"] =
104                                 filePath.filename();
105                         }
106 
107                         aResp->res.jsonValue["Image"] = *imageUrlValue;
108                         aResp->res.jsonValue["TransferProtocolType"] =
109                             getTransferProtocolTypeFromUri(*imageUrlValue);
110 
111                         aResp->res.jsonValue["ConnectedVia"] = "URI";
112                     }
113                 }
114                 if (property == "WriteProtected")
115                 {
116                     const bool* writeProtectedValue = std::get_if<bool>(&value);
117                     if (writeProtectedValue != nullptr)
118                     {
119                         aResp->res.jsonValue["WriteProtected"] =
120                             *writeProtectedValue;
121                     }
122                 }
123             }
124         }
125         if (interface == "xyz.openbmc_project.VirtualMedia.Process")
126         {
127             for (const auto& [property, value] : values)
128             {
129                 if (property == "Active")
130                 {
131                     const bool* activeValue = std::get_if<bool>(&value);
132                     if (activeValue == nullptr)
133                     {
134                         BMCWEB_LOG_DEBUG << "Value Active not found";
135                         return;
136                     }
137                     aResp->res.jsonValue["Inserted"] = *activeValue;
138 
139                     if (*activeValue)
140                     {
141                         aResp->res.jsonValue["ConnectedVia"] = "Applet";
142                     }
143                 }
144             }
145         }
146     }
147 }
148 
149 /**
150  * @brief Fill template for Virtual Media Item.
151  */
152 inline nlohmann::json vmItemTemplate(const std::string& name,
153                                      const std::string& resName)
154 {
155     nlohmann::json item;
156     item["@odata.id"] = crow::utility::urlFromPieces(
157         "redfish", "v1", "Managers", name, "VirtualMedia", resName);
158 
159     item["@odata.type"] = "#VirtualMedia.v1_3_0.VirtualMedia";
160     item["Name"] = "Virtual Removable Media";
161     item["Id"] = resName;
162     item["WriteProtected"] = true;
163     item["MediaTypes"] = nlohmann::json::array_t({"CD", "USBStick"});
164     item["TransferMethod"] = "Stream";
165     item["Oem"]["OpenBMC"]["@odata.type"] =
166         "#OemVirtualMedia.v1_0_0.VirtualMedia";
167 
168     return item;
169 }
170 
171 /**
172  *  @brief Fills collection data
173  */
174 inline void getVmResourceList(std::shared_ptr<bmcweb::AsyncResp> aResp,
175                               const std::string& service,
176                               const std::string& name)
177 {
178     BMCWEB_LOG_DEBUG << "Get available Virtual Media resources.";
179     crow::connections::systemBus->async_method_call(
180         [name, aResp{std::move(aResp)}](
181             const boost::system::error_code ec,
182             const dbus::utility::ManagedObjectType& subtree) {
183         if (ec)
184         {
185             BMCWEB_LOG_DEBUG << "DBUS response error";
186             return;
187         }
188         nlohmann::json& members = aResp->res.jsonValue["Members"];
189         members = nlohmann::json::array();
190 
191         for (const auto& object : subtree)
192         {
193             nlohmann::json item;
194             std::string path = object.first.filename();
195             if (path.empty())
196             {
197                 continue;
198             }
199 
200             item["@odata.id"] = crow::utility::urlFromPieces(
201                 "redfish", "v1", "Managers", name, "VirtualMedia", path);
202             members.emplace_back(std::move(item));
203         }
204         aResp->res.jsonValue["Members@odata.count"] = members.size();
205         },
206         service, "/xyz/openbmc_project/VirtualMedia",
207         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
208 }
209 
210 /**
211  *  @brief Fills data for specific resource
212  */
213 inline void getVmData(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
214                       const std::string& service, const std::string& name,
215                       const std::string& resName)
216 {
217     BMCWEB_LOG_DEBUG << "Get Virtual Media resource data.";
218 
219     crow::connections::systemBus->async_method_call(
220         [resName, name,
221          aResp](const boost::system::error_code ec,
222                 const dbus::utility::ManagedObjectType& subtree) {
223         if (ec)
224         {
225             BMCWEB_LOG_DEBUG << "DBUS response error";
226 
227             return;
228         }
229 
230         for (const auto& item : subtree)
231         {
232             std::string thispath = item.first.filename();
233             if (thispath.empty())
234             {
235                 continue;
236             }
237 
238             if (thispath != resName)
239             {
240                 continue;
241             }
242 
243             // "Legacy"/"Proxy"
244             auto mode = item.first.parent_path();
245             // "VirtualMedia"
246             auto type = mode.parent_path();
247             if (mode.filename().empty() || type.filename().empty())
248             {
249                 continue;
250             }
251 
252             if (type.filename() != "VirtualMedia")
253             {
254                 continue;
255             }
256 
257             aResp->res.jsonValue = vmItemTemplate(name, resName);
258 
259             // Check if dbus path is Legacy type
260             if (mode.filename() == "Legacy")
261             {
262                 aResp->res.jsonValue["Actions"]["#VirtualMedia.InsertMedia"]
263                                     ["target"] = crow::utility::urlFromPieces(
264                     "redfish", "v1", "Managers", name, "VirtualMedia", resName,
265                     "Actions", "VirtualMedia.InsertMedia");
266             }
267 
268             vmParseInterfaceObject(item.second, aResp);
269 
270             aResp->res
271                 .jsonValue["Actions"]["#VirtualMedia.EjectMedia"]["target"] =
272                 crow::utility::urlFromPieces("redfish", "v1", "Managers", name,
273                                              "VirtualMedia", resName, "Actions",
274                                              "VirtualMedia.EjectMedia");
275             return;
276         }
277 
278         messages::resourceNotFound(aResp->res, "VirtualMedia", resName);
279         },
280         service, "/xyz/openbmc_project/VirtualMedia",
281         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
282 }
283 
284 /**
285  * @brief Transfer protocols supported for InsertMedia action.
286  *
287  */
288 enum class TransferProtocol
289 {
290     https,
291     smb,
292     invalid
293 };
294 
295 /**
296  * @brief Function extracts transfer protocol type from URI.
297  *
298  */
299 inline std::optional<TransferProtocol>
300     getTransferProtocolFromUri(const boost::urls::url_view& imageUri)
301 {
302     std::string_view scheme = imageUri.scheme();
303     if (scheme == "smb")
304     {
305         return TransferProtocol::smb;
306     }
307     if (scheme == "https")
308     {
309         return TransferProtocol::https;
310     }
311     if (!scheme.empty())
312     {
313         return TransferProtocol::invalid;
314     }
315 
316     return {};
317 }
318 
319 /**
320  * @brief Function convert transfer protocol from string param.
321  *
322  */
323 inline std::optional<TransferProtocol> getTransferProtocolFromParam(
324     const std::optional<std::string>& transferProtocolType)
325 {
326     if (transferProtocolType == std::nullopt)
327     {
328         return {};
329     }
330 
331     if (*transferProtocolType == "CIFS")
332     {
333         return TransferProtocol::smb;
334     }
335 
336     if (*transferProtocolType == "HTTPS")
337     {
338         return TransferProtocol::https;
339     }
340 
341     return TransferProtocol::invalid;
342 }
343 
344 /**
345  * @brief Function extends URI with transfer protocol type.
346  *
347  */
348 inline std::string
349     getUriWithTransferProtocol(const std::string& imageUri,
350                                const TransferProtocol& transferProtocol)
351 {
352     if (transferProtocol == TransferProtocol::smb)
353     {
354         return "smb://" + imageUri;
355     }
356 
357     if (transferProtocol == TransferProtocol::https)
358     {
359         return "https://" + imageUri;
360     }
361 
362     return imageUri;
363 }
364 
365 struct InsertMediaActionParams
366 {
367     std::string imageUrl;
368     std::optional<std::string> userName;
369     std::optional<std::string> password;
370     std::optional<std::string> transferMethod;
371     std::optional<std::string> transferProtocolType;
372     std::optional<bool> writeProtected = true;
373     std::optional<bool> inserted;
374 };
375 
376 /**
377  * @brief Function validate parameters of insert media request.
378  *
379  */
380 inline bool validateParams(crow::Response& res,
381                            InsertMediaActionParams& actionParams)
382 {
383     BMCWEB_LOG_DEBUG << "Validation started";
384     // required param imageUrl must not be empty
385     if (actionParams.imageUrl.empty())
386     {
387         BMCWEB_LOG_ERROR << "Request action parameter Image is empty.";
388 
389         messages::propertyValueFormatError(res, "<empty>", "Image");
390 
391         return false;
392     }
393 
394     // optional param inserted must be true
395     if ((actionParams.inserted != std::nullopt) && !*actionParams.inserted)
396     {
397         BMCWEB_LOG_ERROR
398             << "Request action optional parameter Inserted must be true.";
399 
400         messages::actionParameterNotSupported(res, "Inserted", "InsertMedia");
401 
402         return false;
403     }
404 
405     // optional param transferMethod must be stream
406     if ((actionParams.transferMethod != std::nullopt) &&
407         (*actionParams.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(actionParams.imageUrl));
419     if (!url)
420     {
421         messages::actionParameterValueFormatError(res, actionParams.imageUrl,
422                                                   "Image", "InsertMedia");
423         return false;
424     }
425     std::optional<TransferProtocol> uriTransferProtocolType =
426         getTransferProtocolFromUri(*url);
427 
428     std::optional<TransferProtocol> paramTransferProtocolType =
429         getTransferProtocolFromParam(actionParams.transferProtocolType);
430 
431     // ImageUrl does not contain valid protocol type
432     if (*uriTransferProtocolType == TransferProtocol::invalid)
433     {
434         BMCWEB_LOG_ERROR << "Request action parameter ImageUrl must "
435                             "contain specified protocol type from list: "
436                             "(smb, https).";
437 
438         messages::resourceAtUriInUnknownFormat(res, *url);
439 
440         return false;
441     }
442 
443     // transferProtocolType should contain value from list
444     if (*paramTransferProtocolType == TransferProtocol::invalid)
445     {
446         BMCWEB_LOG_ERROR << "Request action parameter TransferProtocolType "
447                             "must be provided with value from list: "
448                             "(CIFS, HTTPS).";
449 
450         messages::propertyValueNotInList(
451             res, *actionParams.transferProtocolType, "TransferProtocolType");
452         return false;
453     }
454 
455     // valid transfer protocol not provided either with URI nor param
456     if ((uriTransferProtocolType == std::nullopt) &&
457         (paramTransferProtocolType == std::nullopt))
458     {
459         BMCWEB_LOG_ERROR << "Request action parameter ImageUrl must "
460                             "contain specified protocol type or param "
461                             "TransferProtocolType must be provided.";
462 
463         messages::resourceAtUriInUnknownFormat(res, *url);
464 
465         return false;
466     }
467 
468     // valid transfer protocol provided both with URI and param
469     if ((paramTransferProtocolType != std::nullopt) &&
470         (uriTransferProtocolType != std::nullopt))
471     {
472         // check if protocol is the same for URI and param
473         if (*paramTransferProtocolType != *uriTransferProtocolType)
474         {
475             BMCWEB_LOG_ERROR << "Request action parameter "
476                                 "TransferProtocolType must  contain the "
477                                 "same protocol type as protocol type "
478                                 "provided with param imageUrl.";
479 
480             messages::actionParameterValueTypeError(
481                 res, *actionParams.transferProtocolType, "TransferProtocolType",
482                 "InsertMedia");
483 
484             return false;
485         }
486     }
487 
488     // validation passed, add protocol to URI if needed
489     if (uriTransferProtocolType == std::nullopt)
490     {
491         actionParams.imageUrl = getUriWithTransferProtocol(
492             actionParams.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&& bufferIn) :
600         impl(io), buffer{std::move(bufferIn)}
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 inline void handleManagersVirtualMediaActionInsertPost(
767     crow::App& app, const crow::Request& req,
768     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
769     const std::string& name, const std::string& resName)
770 {
771     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
772     {
773         return;
774     }
775     if (name != "bmc")
776     {
777         messages::resourceNotFound(asyncResp->res, "VirtualMedia.InsertMedia",
778                                    resName);
779 
780         return;
781     }
782     InsertMediaActionParams actionParams;
783 
784     // Read obligatory parameters (url of
785     // image)
786     if (!json_util::readJsonAction(
787             req, asyncResp->res, "Image", actionParams.imageUrl,
788             "WriteProtected", actionParams.writeProtected, "UserName",
789             actionParams.userName, "Password", actionParams.password,
790             "Inserted", actionParams.inserted, "TransferMethod",
791             actionParams.transferMethod, "TransferProtocolType",
792             actionParams.transferProtocolType))
793     {
794         BMCWEB_LOG_DEBUG << "Image is not provided";
795         return;
796     }
797 
798     bool paramsValid = validateParams(asyncResp->res, actionParams);
799     if (!paramsValid)
800     {
801         return;
802     }
803 
804     dbus::utility::getDbusObject(
805         "/xyz/openbmc_project/VirtualMedia", {},
806         [asyncResp, actionParams,
807          resName](const boost::system::error_code& ec,
808                   const dbus::utility::MapperGetObject& getObjectType) mutable {
809         if (ec)
810         {
811             BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << ec;
812             messages::internalError(asyncResp->res);
813 
814             return;
815         }
816         std::string service = getObjectType.begin()->first;
817         BMCWEB_LOG_DEBUG << "GetObjectType: " << service;
818 
819         crow::connections::systemBus->async_method_call(
820             [service, resName, actionParams,
821              asyncResp](const boost::system::error_code ec2,
822                         dbus::utility::ManagedObjectType& subtree) mutable {
823             if (ec2)
824             {
825                 BMCWEB_LOG_DEBUG << "DBUS response error";
826                 messages::internalError(asyncResp->res);
827 
828                 return;
829             }
830 
831             for (const auto& object : subtree)
832             {
833                 const std::string& path =
834                     static_cast<const std::string&>(object.first);
835 
836                 std::size_t lastIndex = path.rfind('/');
837                 if (lastIndex == std::string::npos)
838                 {
839                     continue;
840                 }
841 
842                 lastIndex += 1;
843 
844                 if (path.substr(lastIndex) == resName)
845                 {
846                     lastIndex = path.rfind("Proxy");
847                     if (lastIndex != std::string::npos)
848                     {
849                         // Not possible in proxy mode
850                         BMCWEB_LOG_DEBUG << "InsertMedia not "
851                                             "allowed in proxy mode";
852                         messages::resourceNotFound(asyncResp->res,
853                                                    "VirtualMedia.InsertMedia",
854                                                    resName);
855 
856                         return;
857                     }
858 
859                     lastIndex = path.rfind("Legacy");
860                     if (lastIndex == std::string::npos)
861                     {
862                         continue;
863                     }
864 
865                     // manager is irrelevant for
866                     // VirtualMedia dbus calls
867                     doMountVmLegacy(asyncResp, service, resName,
868                                     actionParams.imageUrl,
869                                     !(*actionParams.writeProtected),
870                                     std::move(*actionParams.userName),
871                                     std::move(*actionParams.password));
872 
873                     return;
874                 }
875             }
876             BMCWEB_LOG_DEBUG << "Parent item not found";
877             messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName);
878             },
879             service, "/xyz/openbmc_project/VirtualMedia",
880             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
881         });
882 }
883 
884 inline void handleManagersVirtualMediaActionEject(
885     crow::App& app, const crow::Request& req,
886     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
887     const std::string& managerName, const std::string& resName)
888 {
889     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
890     {
891         return;
892     }
893     if (managerName != "bmc")
894     {
895         messages::resourceNotFound(asyncResp->res, "VirtualMedia.Eject",
896                                    resName);
897 
898         return;
899     }
900 
901     dbus::utility::getDbusObject(
902         "/xyz/openbmc_project/VirtualMedia", {},
903         [asyncResp,
904          resName](const boost::system::error_code& ec2,
905                   const dbus::utility::MapperGetObject& getObjectType) {
906         if (ec2)
907         {
908             BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << ec2;
909             messages::internalError(asyncResp->res);
910 
911             return;
912         }
913         std::string service = getObjectType.begin()->first;
914         BMCWEB_LOG_DEBUG << "GetObjectType: " << service;
915 
916         crow::connections::systemBus->async_method_call(
917             [resName, service, asyncResp{asyncResp}](
918                 const boost::system::error_code ec,
919                 const dbus::utility::ManagedObjectType& subtree) {
920             if (ec)
921             {
922                 BMCWEB_LOG_DEBUG << "DBUS response error";
923                 messages::internalError(asyncResp->res);
924 
925                 return;
926             }
927 
928             for (const auto& object : subtree)
929             {
930                 const std::string& path =
931                     static_cast<const std::string&>(object.first);
932 
933                 std::size_t lastIndex = path.rfind('/');
934                 if (lastIndex == std::string::npos)
935                 {
936                     continue;
937                 }
938 
939                 lastIndex += 1;
940 
941                 if (path.substr(lastIndex) == resName)
942                 {
943                     lastIndex = path.rfind("Proxy");
944                     if (lastIndex != std::string::npos)
945                     {
946                         // Proxy mode
947                         doVmAction(asyncResp, service, resName, false);
948                     }
949 
950                     lastIndex = path.rfind("Legacy");
951                     if (lastIndex != std::string::npos)
952                     {
953                         // Legacy mode
954                         doVmAction(asyncResp, service, resName, true);
955                     }
956 
957                     return;
958                 }
959             }
960             BMCWEB_LOG_DEBUG << "Parent item not found";
961             messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName);
962             },
963             service, "/xyz/openbmc_project/VirtualMedia",
964             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
965         });
966 }
967 
968 inline void handleManagersVirtualMediaCollectionGet(
969     crow::App& app, const crow::Request& req,
970     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
971     const std::string& name)
972 {
973     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
974     {
975         return;
976     }
977     if (name != "bmc")
978     {
979         messages::resourceNotFound(asyncResp->res, "VirtualMedia", name);
980 
981         return;
982     }
983 
984     asyncResp->res.jsonValue["@odata.type"] =
985         "#VirtualMediaCollection.VirtualMediaCollection";
986     asyncResp->res.jsonValue["Name"] = "Virtual Media Services";
987     asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
988         "redfish", "v1", "Managers", name, "VirtualMedia");
989 
990     dbus::utility::getDbusObject(
991         "/xyz/openbmc_project/VirtualMedia", {},
992         [asyncResp, name](const boost::system::error_code& ec,
993                           const dbus::utility::MapperGetObject& getObjectType) {
994         if (ec)
995         {
996             BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << ec;
997             messages::internalError(asyncResp->res);
998 
999             return;
1000         }
1001         std::string service = getObjectType.begin()->first;
1002         BMCWEB_LOG_DEBUG << "GetObjectType: " << service;
1003 
1004         getVmResourceList(asyncResp, service, name);
1005         });
1006 }
1007 
1008 inline void
1009     handleVirtualMediaGet(crow::App& app, const crow::Request& req,
1010                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1011                           const std::string& name, const std::string& resName)
1012 {
1013     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1014     {
1015         return;
1016     }
1017     if (name != "bmc")
1018     {
1019         messages::resourceNotFound(asyncResp->res, "VirtualMedia", resName);
1020 
1021         return;
1022     }
1023 
1024     dbus::utility::getDbusObject(
1025         "/xyz/openbmc_project/VirtualMedia", {},
1026         [asyncResp, name,
1027          resName](const boost::system::error_code& ec,
1028                   const dbus::utility::MapperGetObject& getObjectType) {
1029         if (ec)
1030         {
1031             BMCWEB_LOG_ERROR << "ObjectMapper::GetObject call failed: " << 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         getVmData(asyncResp, service, name, resName);
1040         });
1041 }
1042 
1043 inline void requestNBDVirtualMediaRoutes(App& app)
1044 {
1045     BMCWEB_ROUTE(
1046         app,
1047         "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/VirtualMedia.InsertMedia")
1048         .privileges(redfish::privileges::postVirtualMedia)
1049         .methods(boost::beast::http::verb::post)(std::bind_front(
1050             handleManagersVirtualMediaActionInsertPost, std::ref(app)));
1051 
1052     BMCWEB_ROUTE(
1053         app,
1054         "/redfish/v1/Managers/<str>/VirtualMedia/<str>/Actions/VirtualMedia.EjectMedia")
1055         .privileges(redfish::privileges::postVirtualMedia)
1056         .methods(boost::beast::http::verb::post)(std::bind_front(
1057             handleManagersVirtualMediaActionEject, std::ref(app)));
1058 
1059     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/")
1060         .privileges(redfish::privileges::getVirtualMediaCollection)
1061         .methods(boost::beast::http::verb::get)(std::bind_front(
1062             handleManagersVirtualMediaCollectionGet, std::ref(app)));
1063 
1064     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/VirtualMedia/<str>/")
1065         .privileges(redfish::privileges::getVirtualMedia)
1066         .methods(boost::beast::http::verb::get)(
1067             std::bind_front(handleVirtualMediaGet, std::ref(app)));
1068 }
1069 
1070 } // namespace redfish
1071