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