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