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