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 "bmcweb_config.h"
19 
20 #include "app.hpp"
21 #include "dbus_utility.hpp"
22 #include "error_messages.hpp"
23 #include "generated/enums/update_service.hpp"
24 #include "multipart_parser.hpp"
25 #include "ossl_random.hpp"
26 #include "query.hpp"
27 #include "registries/privilege_registry.hpp"
28 #include "task.hpp"
29 #include "task_messages.hpp"
30 #include "utils/collection.hpp"
31 #include "utils/dbus_utils.hpp"
32 #include "utils/json_utils.hpp"
33 #include "utils/sw_utils.hpp"
34 
35 #include <boost/system/error_code.hpp>
36 #include <boost/url/format.hpp>
37 #include <sdbusplus/asio/property.hpp>
38 #include <sdbusplus/bus/match.hpp>
39 #include <sdbusplus/unpack_properties.hpp>
40 
41 #include <array>
42 #include <filesystem>
43 #include <memory>
44 #include <optional>
45 #include <string>
46 #include <string_view>
47 #include <vector>
48 
49 namespace redfish
50 {
51 
52 // Match signals added on software path
53 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
54 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateMatcher;
55 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
56 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateErrorMatcher;
57 // Only allow one update at a time
58 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
59 static bool fwUpdateInProgress = false;
60 // Timer for software available
61 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
62 static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer;
63 
64 inline void cleanUp()
65 {
66     fwUpdateInProgress = false;
67     fwUpdateMatcher = nullptr;
68     fwUpdateErrorMatcher = nullptr;
69 }
70 
71 inline void activateImage(const std::string& objPath,
72                           const std::string& service)
73 {
74     BMCWEB_LOG_DEBUG("Activate image for {} {}", objPath, service);
75     sdbusplus::asio::setProperty(
76         *crow::connections::systemBus, service, objPath,
77         "xyz.openbmc_project.Software.Activation", "RequestedActivation",
78         "xyz.openbmc_project.Software.Activation.RequestedActivations.Active",
79         [](const boost::system::error_code& ec) {
80         if (ec)
81         {
82             BMCWEB_LOG_DEBUG("error_code = {}", ec);
83             BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
84         }
85     });
86 }
87 
88 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
89 // then no asyncResp updates will occur
90 static void
91     softwareInterfaceAdded(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
92                            sdbusplus::message_t& m, task::Payload&& payload)
93 {
94     dbus::utility::DBusInterfacesMap interfacesProperties;
95 
96     sdbusplus::message::object_path objPath;
97 
98     m.read(objPath, interfacesProperties);
99 
100     BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
101     for (const auto& interface : interfacesProperties)
102     {
103         BMCWEB_LOG_DEBUG("interface = {}", interface.first);
104 
105         if (interface.first == "xyz.openbmc_project.Software.Activation")
106         {
107             // Retrieve service and activate
108             constexpr std::array<std::string_view, 1> interfaces = {
109                 "xyz.openbmc_project.Software.Activation"};
110             dbus::utility::getDbusObject(
111                 objPath.str, interfaces,
112                 [objPath, asyncResp, payload(std::move(payload))](
113                     const boost::system::error_code& ec,
114                     const std::vector<
115                         std::pair<std::string, std::vector<std::string>>>&
116                         objInfo) mutable {
117                 if (ec)
118                 {
119                     BMCWEB_LOG_DEBUG("error_code = {}", ec);
120                     BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
121                     if (asyncResp)
122                     {
123                         messages::internalError(asyncResp->res);
124                     }
125                     cleanUp();
126                     return;
127                 }
128                 // Ensure we only got one service back
129                 if (objInfo.size() != 1)
130                 {
131                     BMCWEB_LOG_ERROR("Invalid Object Size {}", objInfo.size());
132                     if (asyncResp)
133                     {
134                         messages::internalError(asyncResp->res);
135                     }
136                     cleanUp();
137                     return;
138                 }
139                 // cancel timer only when
140                 // xyz.openbmc_project.Software.Activation interface
141                 // is added
142                 fwAvailableTimer = nullptr;
143 
144                 activateImage(objPath.str, objInfo[0].first);
145                 if (asyncResp)
146                 {
147                     std::shared_ptr<task::TaskData> task =
148                         task::TaskData::createTask(
149                             [](const boost::system::error_code& ec2,
150                                sdbusplus::message_t& msg,
151                                const std::shared_ptr<task::TaskData>&
152                                    taskData) {
153                         if (ec2)
154                         {
155                             return task::completed;
156                         }
157 
158                         std::string iface;
159                         dbus::utility::DBusPropertiesMap values;
160 
161                         std::string index = std::to_string(taskData->index);
162                         msg.read(iface, values);
163 
164                         if (iface == "xyz.openbmc_project.Software.Activation")
165                         {
166                             const std::string* state = nullptr;
167                             for (const auto& property : values)
168                             {
169                                 if (property.first == "Activation")
170                                 {
171                                     state = std::get_if<std::string>(
172                                         &property.second);
173                                     if (state == nullptr)
174                                     {
175                                         taskData->messages.emplace_back(
176                                             messages::internalError());
177                                         return task::completed;
178                                     }
179                                 }
180                             }
181 
182                             if (state == nullptr)
183                             {
184                                 return !task::completed;
185                             }
186 
187                             if (state->ends_with("Invalid") ||
188                                 state->ends_with("Failed"))
189                             {
190                                 taskData->state = "Exception";
191                                 taskData->status = "Warning";
192                                 taskData->messages.emplace_back(
193                                     messages::taskAborted(index));
194                                 return task::completed;
195                             }
196 
197                             if (state->ends_with("Staged"))
198                             {
199                                 taskData->state = "Stopping";
200                                 taskData->messages.emplace_back(
201                                     messages::taskPaused(index));
202 
203                                 // its staged, set a long timer to
204                                 // allow them time to complete the
205                                 // update (probably cycle the
206                                 // system) if this expires then
207                                 // task will be canceled
208                                 taskData->extendTimer(std::chrono::hours(5));
209                                 return !task::completed;
210                             }
211 
212                             if (state->ends_with("Active"))
213                             {
214                                 taskData->messages.emplace_back(
215                                     messages::taskCompletedOK(index));
216                                 taskData->state = "Completed";
217                                 return task::completed;
218                             }
219                         }
220                         else if (
221                             iface ==
222                             "xyz.openbmc_project.Software.ActivationProgress")
223                         {
224                             const uint8_t* progress = nullptr;
225                             for (const auto& property : values)
226                             {
227                                 if (property.first == "Progress")
228                                 {
229                                     progress =
230                                         std::get_if<uint8_t>(&property.second);
231                                     if (progress == nullptr)
232                                     {
233                                         taskData->messages.emplace_back(
234                                             messages::internalError());
235                                         return task::completed;
236                                     }
237                                 }
238                             }
239 
240                             if (progress == nullptr)
241                             {
242                                 return !task::completed;
243                             }
244                             taskData->percentComplete = *progress;
245                             taskData->messages.emplace_back(
246                                 messages::taskProgressChanged(index,
247                                                               *progress));
248 
249                             // if we're getting status updates it's
250                             // still alive, update timer
251                             taskData->extendTimer(std::chrono::minutes(5));
252                         }
253 
254                         // as firmware update often results in a
255                         // reboot, the task  may never "complete"
256                         // unless it is an error
257 
258                         return !task::completed;
259                     },
260                             "type='signal',interface='org.freedesktop.DBus.Properties',"
261                             "member='PropertiesChanged',path='" +
262                                 objPath.str + "'");
263                     task->startTimer(std::chrono::minutes(5));
264                     task->populateResp(asyncResp->res);
265                     task->payload.emplace(std::move(payload));
266                 }
267                 fwUpdateInProgress = false;
268             });
269 
270             break;
271         }
272     }
273 }
274 
275 inline void afterAvailbleTimerAsyncWait(
276     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
277     const boost::system::error_code& ec)
278 {
279     cleanUp();
280     if (ec == boost::asio::error::operation_aborted)
281     {
282         // expected, we were canceled before the timer completed.
283         return;
284     }
285     BMCWEB_LOG_ERROR("Timed out waiting for firmware object being created");
286     BMCWEB_LOG_ERROR("FW image may has already been uploaded to server");
287     if (ec)
288     {
289         BMCWEB_LOG_ERROR("Async_wait failed{}", ec);
290         return;
291     }
292     if (asyncResp)
293     {
294         redfish::messages::internalError(asyncResp->res);
295     }
296 }
297 
298 inline void
299     handleUpdateErrorType(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
300                           const std::string& url, const std::string& type)
301 {
302     if (type == "xyz.openbmc_project.Software.Image.Error.UnTarFailure")
303     {
304         redfish::messages::invalidUpload(asyncResp->res, url,
305                                          "Invalid archive");
306     }
307     else if (type ==
308              "xyz.openbmc_project.Software.Image.Error.ManifestFileFailure")
309     {
310         redfish::messages::invalidUpload(asyncResp->res, url,
311                                          "Invalid manifest");
312     }
313     else if (type == "xyz.openbmc_project.Software.Image.Error.ImageFailure")
314     {
315         redfish::messages::invalidUpload(asyncResp->res, url,
316                                          "Invalid image format");
317     }
318     else if (type == "xyz.openbmc_project.Software.Version.Error.AlreadyExists")
319     {
320         redfish::messages::invalidUpload(asyncResp->res, url,
321                                          "Image version already exists");
322 
323         redfish::messages::resourceAlreadyExists(
324             asyncResp->res, "UpdateService", "Version", "uploaded version");
325     }
326     else if (type == "xyz.openbmc_project.Software.Image.Error.BusyFailure")
327     {
328         redfish::messages::resourceExhaustion(asyncResp->res, url);
329     }
330     else if (type == "xyz.openbmc_project.Software.Version.Error.Incompatible")
331     {
332         redfish::messages::invalidUpload(asyncResp->res, url,
333                                          "Incompatible image version");
334     }
335     else if (type ==
336              "xyz.openbmc_project.Software.Version.Error.ExpiredAccessKey")
337     {
338         redfish::messages::invalidUpload(asyncResp->res, url,
339                                          "Update Access Key Expired");
340     }
341     else if (type ==
342              "xyz.openbmc_project.Software.Version.Error.InvalidSignature")
343     {
344         redfish::messages::invalidUpload(asyncResp->res, url,
345                                          "Invalid image signature");
346     }
347     else if (type ==
348                  "xyz.openbmc_project.Software.Image.Error.InternalFailure" ||
349              type == "xyz.openbmc_project.Software.Version.Error.HostFile")
350     {
351         BMCWEB_LOG_ERROR("Software Image Error type={}", type);
352         redfish::messages::internalError(asyncResp->res);
353     }
354     else
355     {
356         // Unrelated error types. Ignored
357         BMCWEB_LOG_INFO("Non-Software-related Error type={}. Ignored", type);
358         return;
359     }
360     // Clear the timer
361     fwAvailableTimer = nullptr;
362 }
363 
364 inline void
365     afterUpdateErrorMatcher(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
366                             const std::string& url, sdbusplus::message_t& m)
367 {
368     dbus::utility::DBusInterfacesMap interfacesProperties;
369     sdbusplus::message::object_path objPath;
370     m.read(objPath, interfacesProperties);
371     BMCWEB_LOG_DEBUG("obj path = {}", objPath.str);
372     for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>&
373              interface : interfacesProperties)
374     {
375         if (interface.first == "xyz.openbmc_project.Logging.Entry")
376         {
377             for (const std::pair<std::string, dbus::utility::DbusVariantType>&
378                      value : interface.second)
379             {
380                 if (value.first != "Message")
381                 {
382                     continue;
383                 }
384                 const std::string* type =
385                     std::get_if<std::string>(&value.second);
386                 if (type == nullptr)
387                 {
388                     // if this was our message, timeout will cover it
389                     return;
390                 }
391                 handleUpdateErrorType(asyncResp, url, *type);
392             }
393         }
394     }
395 }
396 
397 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr
398 // then no asyncResp updates will occur
399 inline void monitorForSoftwareAvailable(
400     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
401     const crow::Request& req, const std::string& url,
402     int timeoutTimeSeconds = 25)
403 {
404     // Only allow one FW update at a time
405     if (fwUpdateInProgress)
406     {
407         if (asyncResp)
408         {
409             messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
410         }
411         return;
412     }
413 
414     if (req.ioService == nullptr)
415     {
416         messages::internalError(asyncResp->res);
417         return;
418     }
419 
420     fwAvailableTimer =
421         std::make_unique<boost::asio::steady_timer>(*req.ioService);
422 
423     fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds));
424 
425     fwAvailableTimer->async_wait(
426         std::bind_front(afterAvailbleTimerAsyncWait, asyncResp));
427 
428     task::Payload payload(req);
429     auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable {
430         BMCWEB_LOG_DEBUG("Match fired");
431         softwareInterfaceAdded(asyncResp, m, std::move(payload));
432     };
433 
434     fwUpdateInProgress = true;
435 
436     fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>(
437         *crow::connections::systemBus,
438         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
439         "member='InterfacesAdded',path='/xyz/openbmc_project/software'",
440         callback);
441 
442     fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>(
443         *crow::connections::systemBus,
444         "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
445         "member='InterfacesAdded',"
446         "path='/xyz/openbmc_project/logging'",
447         std::bind_front(afterUpdateErrorMatcher, asyncResp, url));
448 }
449 
450 inline std::optional<boost::urls::url>
451     parseSimpleUpdateUrl(std::string imageURI,
452                          std::optional<std::string> transferProtocol,
453                          crow::Response& res)
454 {
455     if (imageURI.find("://") == std::string::npos)
456     {
457         if (imageURI.starts_with("/"))
458         {
459             messages::actionParameterValueTypeError(
460                 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
461             return std::nullopt;
462         }
463         if (!transferProtocol)
464         {
465             messages::actionParameterValueTypeError(
466                 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate");
467             return std::nullopt;
468         }
469         // OpenBMC currently only supports TFTP or HTTPS
470         if (*transferProtocol == "TFTP")
471         {
472             imageURI = "tftp://" + imageURI;
473         }
474         else if (*transferProtocol == "HTTPS")
475         {
476             imageURI = "https://" + imageURI;
477         }
478         else
479         {
480             messages::actionParameterNotSupported(res, "TransferProtocol",
481                                                   *transferProtocol);
482             BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}",
483                              *transferProtocol);
484             return std::nullopt;
485         }
486     }
487 
488     boost::system::result<boost::urls::url> url =
489         boost::urls::parse_absolute_uri(imageURI);
490     if (!url)
491     {
492         messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
493                                                 "UpdateService.SimpleUpdate");
494 
495         return std::nullopt;
496     }
497     url->normalize();
498 
499     if (url->scheme() == "tftp")
500     {
501         if (url->encoded_path().size() < 2)
502         {
503             messages::actionParameterNotSupported(res, "ImageURI",
504                                                   url->buffer());
505             return std::nullopt;
506         }
507     }
508     else if (url->scheme() == "https")
509     {
510         // Empty paths default to "/"
511         if (url->encoded_path().empty())
512         {
513             url->set_encoded_path("/");
514         }
515     }
516     else
517     {
518         messages::actionParameterNotSupported(res, "ImageURI", imageURI);
519         return std::nullopt;
520     }
521 
522     if (url->encoded_path().empty())
523     {
524         messages::actionParameterValueTypeError(res, imageURI, "ImageURI",
525                                                 "UpdateService.SimpleUpdate");
526         return std::nullopt;
527     }
528 
529     return *url;
530 }
531 
532 inline void doHttpsUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
533                           const boost::urls::url_view_base& url)
534 {
535     messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
536                                           url.buffer());
537 }
538 
539 inline void doTftpUpdate(const crow::Request& req,
540                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
541                          const boost::urls::url_view_base& url)
542 {
543     if (!BMCWEB_INSECURE_TFTP_UPDATE)
544     {
545         messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
546                                               url.buffer());
547         return;
548     }
549 
550     std::string path(url.encoded_path());
551     if (path.size() < 2)
552     {
553         messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
554                                               url.buffer());
555         return;
556     }
557     // TFTP expects a path without a /
558     path.erase(0, 1);
559     std::string host(url.encoded_host_and_port());
560     BMCWEB_LOG_DEBUG("Server: {} File: {}", host, path);
561 
562     // Setup callback for when new software detected
563     // Give TFTP 10 minutes to complete
564     monitorForSoftwareAvailable(
565         asyncResp, req,
566         "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate", 600);
567 
568     // TFTP can take up to 10 minutes depending on image size and
569     // connection speed. Return to caller as soon as the TFTP operation
570     // has been started. The callback above will ensure the activate
571     // is started once the download has completed
572     redfish::messages::success(asyncResp->res);
573 
574     // Call TFTP service
575     crow::connections::systemBus->async_method_call(
576         [](const boost::system::error_code& ec) {
577         if (ec)
578         {
579             // messages::internalError(asyncResp->res);
580             cleanUp();
581             BMCWEB_LOG_DEBUG("error_code = {}", ec);
582             BMCWEB_LOG_DEBUG("error msg = {}", ec.message());
583         }
584         else
585         {
586             BMCWEB_LOG_DEBUG("Call to DownloaViaTFTP Success");
587         }
588     },
589         "xyz.openbmc_project.Software.Download",
590         "/xyz/openbmc_project/software", "xyz.openbmc_project.Common.TFTP",
591         "DownloadViaTFTP", path, host);
592 }
593 
594 inline void handleUpdateServiceSimpleUpdateAction(
595     crow::App& app, const crow::Request& req,
596     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
597 {
598     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
599     {
600         return;
601     }
602 
603     std::optional<std::string> transferProtocol;
604     std::string imageURI;
605 
606     BMCWEB_LOG_DEBUG("Enter UpdateService.SimpleUpdate doPost");
607 
608     // User can pass in both TransferProtocol and ImageURI parameters or
609     // they can pass in just the ImageURI with the transfer protocol
610     // embedded within it.
611     // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin
612     // 2) ImageURI:tftp://1.1.1.1/myfile.bin
613 
614     if (!json_util::readJsonAction(req, asyncResp->res, "TransferProtocol",
615                                    transferProtocol, "ImageURI", imageURI))
616     {
617         BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter");
618         return;
619     }
620 
621     std::optional<boost::urls::url> url =
622         parseSimpleUpdateUrl(imageURI, transferProtocol, asyncResp->res);
623     if (!url)
624     {
625         return;
626     }
627     if (url->scheme() == "tftp")
628     {
629         doTftpUpdate(req, asyncResp, *url);
630     }
631     else if (url->scheme() == "https")
632     {
633         doHttpsUpdate(asyncResp, *url);
634     }
635     else
636     {
637         messages::actionParameterNotSupported(asyncResp->res, "ImageURI",
638                                               url->buffer());
639         return;
640     }
641 
642     BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost");
643 }
644 
645 inline void uploadImageFile(crow::Response& res, std::string_view body)
646 {
647     std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID());
648 
649     BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string());
650     std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary |
651                                     std::ofstream::trunc);
652     // set the permission of the file to 640
653     std::filesystem::perms permission = std::filesystem::perms::owner_read |
654                                         std::filesystem::perms::group_read;
655     std::filesystem::permissions(filepath, permission);
656     out << body;
657 
658     if (out.bad())
659     {
660         messages::internalError(res);
661         cleanUp();
662     }
663 }
664 
665 inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
666                          const std::string& applyTime)
667 {
668     std::string applyTimeNewVal;
669     if (applyTime == "Immediate")
670     {
671         applyTimeNewVal =
672             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
673     }
674     else if (applyTime == "OnReset")
675     {
676         applyTimeNewVal =
677             "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
678     }
679     else
680     {
681         BMCWEB_LOG_INFO(
682             "ApplyTime value is not in the list of acceptable values");
683         messages::propertyValueNotInList(asyncResp->res, applyTime,
684                                          "ApplyTime");
685         return;
686     }
687 
688     setDbusProperty(asyncResp, "xyz.openbmc_project.Settings",
689                     sdbusplus::message::object_path(
690                         "/xyz/openbmc_project/software/apply_time"),
691                     "xyz.openbmc_project.Software.ApplyTime",
692                     "RequestedApplyTime", "ApplyTime", applyTimeNewVal);
693 }
694 
695 struct MultiPartUpdateParameters
696 {
697     std::optional<std::string> applyTime;
698     std::string uploadData;
699     std::vector<boost::urls::url> targets;
700 };
701 
702 inline std::optional<MultiPartUpdateParameters>
703     extractMultipartUpdateParameters(
704         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
705         MultipartParser parser)
706 {
707     MultiPartUpdateParameters multiRet;
708     for (FormPart& formpart : parser.mime_fields)
709     {
710         boost::beast::http::fields::const_iterator it =
711             formpart.fields.find("Content-Disposition");
712         if (it == formpart.fields.end())
713         {
714             BMCWEB_LOG_ERROR("Couldn't find Content-Disposition");
715             return std::nullopt;
716         }
717         BMCWEB_LOG_INFO("Parsing value {}", it->value());
718 
719         // The construction parameters of param_list must start with `;`
720         size_t index = it->value().find(';');
721         if (index == std::string::npos)
722         {
723             continue;
724         }
725 
726         for (const auto& param :
727              boost::beast::http::param_list{it->value().substr(index)})
728         {
729             if (param.first != "name" || param.second.empty())
730             {
731                 continue;
732             }
733 
734             if (param.second == "UpdateParameters")
735             {
736                 std::vector<std::string> tempTargets;
737                 nlohmann::json content =
738                     nlohmann::json::parse(formpart.content);
739                 nlohmann::json::object_t* obj =
740                     content.get_ptr<nlohmann::json::object_t*>();
741                 if (obj == nullptr)
742                 {
743                     messages::propertyValueTypeError(
744                         asyncResp->res, formpart.content, "UpdateParameters");
745                     return std::nullopt;
746                 }
747 
748                 if (!json_util::readJsonObject(
749                         *obj, asyncResp->res, "Targets", tempTargets,
750                         "@Redfish.OperationApplyTime", multiRet.applyTime))
751                 {
752                     return std::nullopt;
753                 }
754 
755                 for (size_t urlIndex = 0; urlIndex < tempTargets.size();
756                      urlIndex++)
757                 {
758                     const std::string& target = tempTargets[urlIndex];
759                     boost::system::result<boost::urls::url_view> url =
760                         boost::urls::parse_origin_form(target);
761                     if (!url)
762                     {
763                         messages::propertyValueFormatError(
764                             asyncResp->res, target,
765                             std::format("Targets/{}", urlIndex));
766                         return std::nullopt;
767                     }
768                     multiRet.targets.emplace_back(*url);
769                 }
770                 if (multiRet.targets.size() != 1)
771                 {
772                     messages::propertyValueFormatError(
773                         asyncResp->res, multiRet.targets, "Targets");
774                     return std::nullopt;
775                 }
776                 if (multiRet.targets[0].path() != "/redfish/v1/Managers/bmc")
777                 {
778                     messages::propertyValueNotInList(
779                         asyncResp->res, multiRet.targets[0], "Targets/0");
780                     return std::nullopt;
781                 }
782             }
783             else if (param.second == "UpdateFile")
784             {
785                 multiRet.uploadData = std::move(formpart.content);
786             }
787         }
788     }
789 
790     if (multiRet.uploadData.empty())
791     {
792         BMCWEB_LOG_ERROR("Upload data is NULL");
793         messages::propertyMissing(asyncResp->res, "UpdateFile");
794         return std::nullopt;
795     }
796     if (multiRet.targets.empty())
797     {
798         messages::propertyMissing(asyncResp->res, "Targets");
799         return std::nullopt;
800     }
801     return multiRet;
802 }
803 
804 inline void
805     updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
806                            const crow::Request& req, MultipartParser&& parser)
807 {
808     std::optional<MultiPartUpdateParameters> multipart =
809         extractMultipartUpdateParameters(asyncResp, std::move(parser));
810     if (!multipart)
811     {
812         return;
813     }
814     if (!multipart->applyTime)
815     {
816         multipart->applyTime = "OnReset";
817     }
818 
819     setApplyTime(asyncResp, *multipart->applyTime);
820 
821     // Setup callback for when new software detected
822     monitorForSoftwareAvailable(asyncResp, req, "/redfish/v1/UpdateService");
823 
824     uploadImageFile(asyncResp->res, multipart->uploadData);
825 }
826 
827 inline void
828     handleUpdateServicePost(App& app, const crow::Request& req,
829                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
830 {
831     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
832     {
833         return;
834     }
835     std::string_view contentType = req.getHeaderValue("Content-Type");
836 
837     BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType);
838 
839     // Make sure that content type is application/octet-stream or
840     // multipart/form-data
841     if (bmcweb::asciiIEquals(contentType, "application/octet-stream"))
842     {
843         // Setup callback for when new software detected
844         monitorForSoftwareAvailable(asyncResp, req,
845                                     "/redfish/v1/UpdateService");
846 
847         uploadImageFile(asyncResp->res, req.body());
848     }
849     else if (contentType.starts_with("multipart/form-data"))
850     {
851         MultipartParser parser;
852 
853         ParserError ec = parser.parse(req);
854         if (ec != ParserError::PARSER_SUCCESS)
855         {
856             // handle error
857             BMCWEB_LOG_ERROR("MIME parse failed, ec : {}",
858                              static_cast<int>(ec));
859             messages::internalError(asyncResp->res);
860             return;
861         }
862 
863         updateMultipartContext(asyncResp, req, std::move(parser));
864     }
865     else
866     {
867         BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType);
868         asyncResp->res.result(boost::beast::http::status::bad_request);
869     }
870 }
871 
872 inline void
873     handleUpdateServiceGet(App& app, const crow::Request& req,
874                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
875 {
876     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
877     {
878         return;
879     }
880     asyncResp->res.jsonValue["@odata.type"] =
881         "#UpdateService.v1_11_1.UpdateService";
882     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService";
883     asyncResp->res.jsonValue["Id"] = "UpdateService";
884     asyncResp->res.jsonValue["Description"] = "Service for Software Update";
885     asyncResp->res.jsonValue["Name"] = "Update Service";
886 
887     asyncResp->res.jsonValue["HttpPushUri"] =
888         "/redfish/v1/UpdateService/update";
889     asyncResp->res.jsonValue["MultipartHttpPushUri"] =
890         "/redfish/v1/UpdateService/update";
891 
892     // UpdateService cannot be disabled
893     asyncResp->res.jsonValue["ServiceEnabled"] = true;
894     asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] =
895         "/redfish/v1/UpdateService/FirmwareInventory";
896     // Get the MaxImageSizeBytes
897     asyncResp->res.jsonValue["MaxImageSizeBytes"] = BMCWEB_HTTP_BODY_LIMIT *
898                                                     1024 * 1024;
899 
900     // Update Actions object.
901     nlohmann::json& updateSvcSimpleUpdate =
902         asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"];
903     updateSvcSimpleUpdate["target"] =
904         "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate";
905 
906     nlohmann::json::array_t allowed;
907     allowed.emplace_back(update_service::TransferProtocolType::HTTPS);
908 
909     if constexpr (BMCWEB_INSECURE_PUSH_STYLE_NOTIFICATION)
910     {
911         allowed.emplace_back(update_service::TransferProtocolType::TFTP);
912     }
913 
914     updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] =
915         std::move(allowed);
916 
917     // Get the current ApplyTime value
918     sdbusplus::asio::getProperty<std::string>(
919         *crow::connections::systemBus, "xyz.openbmc_project.Settings",
920         "/xyz/openbmc_project/software/apply_time",
921         "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime",
922         [asyncResp](const boost::system::error_code& ec,
923                     const std::string& applyTime) {
924         if (ec)
925         {
926             BMCWEB_LOG_DEBUG("DBUS response error {}", ec);
927             messages::internalError(asyncResp->res);
928             return;
929         }
930 
931         // Store the ApplyTime Value
932         if (applyTime == "xyz.openbmc_project.Software.ApplyTime."
933                          "RequestedApplyTimes.Immediate")
934         {
935             asyncResp->res.jsonValue["HttpPushUriOptions"]
936                                     ["HttpPushUriApplyTime"]["ApplyTime"] =
937                 "Immediate";
938         }
939         else if (applyTime == "xyz.openbmc_project.Software.ApplyTime."
940                               "RequestedApplyTimes.OnReset")
941         {
942             asyncResp->res.jsonValue["HttpPushUriOptions"]
943                                     ["HttpPushUriApplyTime"]["ApplyTime"] =
944                 "OnReset";
945         }
946     });
947 }
948 
949 inline void handleUpdateServicePatch(
950     App& app, const crow::Request& req,
951     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
952 {
953     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
954     {
955         return;
956     }
957     BMCWEB_LOG_DEBUG("doPatch...");
958 
959     std::optional<std::string> applyTime;
960     if (!json_util::readJsonPatch(
961             req, asyncResp->res,
962             "HttpPushUriOptions/HttpPushUriApplyTime/ApplyTime", applyTime))
963     {
964         return;
965     }
966 
967     if (applyTime)
968     {
969         setApplyTime(asyncResp, *applyTime);
970     }
971 }
972 
973 inline void handleUpdateServiceFirmwareInventoryCollectionGet(
974     App& app, const crow::Request& req,
975     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
976 {
977     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
978     {
979         return;
980     }
981     asyncResp->res.jsonValue["@odata.type"] =
982         "#SoftwareInventoryCollection.SoftwareInventoryCollection";
983     asyncResp->res.jsonValue["@odata.id"] =
984         "/redfish/v1/UpdateService/FirmwareInventory";
985     asyncResp->res.jsonValue["Name"] = "Software Inventory Collection";
986     const std::array<const std::string_view, 1> iface = {
987         "xyz.openbmc_project.Software.Version"};
988 
989     redfish::collection_util::getCollectionMembers(
990         asyncResp,
991         boost::urls::url("/redfish/v1/UpdateService/FirmwareInventory"), iface,
992         "/xyz/openbmc_project/software");
993 }
994 
995 /* Fill related item links (i.e. bmc, bios) in for inventory */
996 inline void getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
997                             const std::string& purpose)
998 {
999     if (purpose == sw_util::bmcPurpose)
1000     {
1001         nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
1002         nlohmann::json::object_t item;
1003         item["@odata.id"] = "/redfish/v1/Managers/bmc";
1004         relatedItem.emplace_back(std::move(item));
1005         asyncResp->res.jsonValue["RelatedItem@odata.count"] =
1006             relatedItem.size();
1007     }
1008     else if (purpose == sw_util::biosPurpose)
1009     {
1010         nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"];
1011         nlohmann::json::object_t item;
1012         item["@odata.id"] = "/redfish/v1/Systems/system/Bios";
1013         relatedItem.emplace_back(std::move(item));
1014         asyncResp->res.jsonValue["RelatedItem@odata.count"] =
1015             relatedItem.size();
1016     }
1017     else
1018     {
1019         BMCWEB_LOG_DEBUG("Unknown software purpose {}", purpose);
1020     }
1021 }
1022 
1023 inline void
1024     getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1025                        const std::string& service, const std::string& path,
1026                        const std::string& swId)
1027 {
1028     sdbusplus::asio::getAllProperties(
1029         *crow::connections::systemBus, service, path,
1030         "xyz.openbmc_project.Software.Version",
1031         [asyncResp,
1032          swId](const boost::system::error_code& ec,
1033                const dbus::utility::DBusPropertiesMap& propertiesList) {
1034         if (ec)
1035         {
1036             messages::internalError(asyncResp->res);
1037             return;
1038         }
1039 
1040         const std::string* swInvPurpose = nullptr;
1041         const std::string* version = nullptr;
1042 
1043         const bool success = sdbusplus::unpackPropertiesNoThrow(
1044             dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose",
1045             swInvPurpose, "Version", version);
1046 
1047         if (!success)
1048         {
1049             messages::internalError(asyncResp->res);
1050             return;
1051         }
1052 
1053         if (swInvPurpose == nullptr)
1054         {
1055             BMCWEB_LOG_DEBUG("Can't find property \"Purpose\"!");
1056             messages::internalError(asyncResp->res);
1057             return;
1058         }
1059 
1060         BMCWEB_LOG_DEBUG("swInvPurpose = {}", *swInvPurpose);
1061 
1062         if (version == nullptr)
1063         {
1064             BMCWEB_LOG_DEBUG("Can't find property \"Version\"!");
1065 
1066             messages::internalError(asyncResp->res);
1067 
1068             return;
1069         }
1070         asyncResp->res.jsonValue["Version"] = *version;
1071         asyncResp->res.jsonValue["Id"] = swId;
1072 
1073         // swInvPurpose is of format:
1074         // xyz.openbmc_project.Software.Version.VersionPurpose.ABC
1075         // Translate this to "ABC image"
1076         size_t endDesc = swInvPurpose->rfind('.');
1077         if (endDesc == std::string::npos)
1078         {
1079             messages::internalError(asyncResp->res);
1080             return;
1081         }
1082         endDesc++;
1083         if (endDesc >= swInvPurpose->size())
1084         {
1085             messages::internalError(asyncResp->res);
1086             return;
1087         }
1088 
1089         std::string formatDesc = swInvPurpose->substr(endDesc);
1090         asyncResp->res.jsonValue["Description"] = formatDesc + " image";
1091         getRelatedItems(asyncResp, *swInvPurpose);
1092     });
1093 }
1094 
1095 inline void handleUpdateServiceFirmwareInventoryGet(
1096     App& app, const crow::Request& req,
1097     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1098     const std::string& param)
1099 {
1100     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1101     {
1102         return;
1103     }
1104     std::shared_ptr<std::string> swId = std::make_shared<std::string>(param);
1105 
1106     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
1107         "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId);
1108 
1109     constexpr std::array<std::string_view, 1> interfaces = {
1110         "xyz.openbmc_project.Software.Version"};
1111     dbus::utility::getSubTree(
1112         "/", 0, interfaces,
1113         [asyncResp,
1114          swId](const boost::system::error_code& ec,
1115                const dbus::utility::MapperGetSubTreeResponse& subtree) {
1116         BMCWEB_LOG_DEBUG("doGet callback...");
1117         if (ec)
1118         {
1119             messages::internalError(asyncResp->res);
1120             return;
1121         }
1122 
1123         // Ensure we find our input swId, otherwise return an error
1124         bool found = false;
1125         for (const std::pair<
1126                  std::string,
1127                  std::vector<std::pair<std::string, std::vector<std::string>>>>&
1128                  obj : subtree)
1129         {
1130             if (!obj.first.ends_with(*swId))
1131             {
1132                 continue;
1133             }
1134 
1135             if (obj.second.empty())
1136             {
1137                 continue;
1138             }
1139 
1140             found = true;
1141             sw_util::getSwStatus(asyncResp, swId, obj.second[0].first);
1142             getSoftwareVersion(asyncResp, obj.second[0].first, obj.first,
1143                                *swId);
1144         }
1145         if (!found)
1146         {
1147             BMCWEB_LOG_WARNING("Input swID {} not found!", *swId);
1148             messages::resourceMissingAtURI(
1149                 asyncResp->res,
1150                 boost::urls::format(
1151                     "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId));
1152             return;
1153         }
1154         asyncResp->res.jsonValue["@odata.type"] =
1155             "#SoftwareInventory.v1_1_0.SoftwareInventory";
1156         asyncResp->res.jsonValue["Name"] = "Software Inventory";
1157         asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
1158 
1159         asyncResp->res.jsonValue["Updateable"] = false;
1160         sw_util::getSwUpdatableStatus(asyncResp, swId);
1161     });
1162 }
1163 
1164 inline void requestRoutesUpdateService(App& app)
1165 {
1166     BMCWEB_ROUTE(
1167         app, "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/")
1168         .privileges(redfish::privileges::postUpdateService)
1169         .methods(boost::beast::http::verb::post)(std::bind_front(
1170             handleUpdateServiceSimpleUpdateAction, std::ref(app)));
1171 
1172     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/")
1173         .privileges(redfish::privileges::getSoftwareInventory)
1174         .methods(boost::beast::http::verb::get)(std::bind_front(
1175             handleUpdateServiceFirmwareInventoryGet, std::ref(app)));
1176 
1177     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
1178         .privileges(redfish::privileges::getUpdateService)
1179         .methods(boost::beast::http::verb::get)(
1180             std::bind_front(handleUpdateServiceGet, std::ref(app)));
1181 
1182     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/")
1183         .privileges(redfish::privileges::patchUpdateService)
1184         .methods(boost::beast::http::verb::patch)(
1185             std::bind_front(handleUpdateServicePatch, std::ref(app)));
1186 
1187     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/")
1188         .privileges(redfish::privileges::postUpdateService)
1189         .methods(boost::beast::http::verb::post)(
1190             std::bind_front(handleUpdateServicePost, std::ref(app)));
1191 
1192     BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/")
1193         .privileges(redfish::privileges::getSoftwareInventoryCollection)
1194         .methods(boost::beast::http::verb::get)(std::bind_front(
1195             handleUpdateServiceFirmwareInventoryCollectionGet, std::ref(app)));
1196 }
1197 
1198 } // namespace redfish
1199