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