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