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