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