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