xref: /openbmc/bmcweb/features/openbmc_rest/openbmc_dbus_rest.hpp (revision 76c2ad64440e2864a0cc3d66a3b8bb0f5ac2f288)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
4 
5 #pragma once
6 #include "app.hpp"
7 #include "async_resp.hpp"
8 #include "boost_formatters.hpp"
9 #include "dbus_singleton.hpp"
10 #include "dbus_utility.hpp"
11 #include "http_request.hpp"
12 #include "http_response.hpp"
13 #include "json_formatters.hpp"
14 #include "logging.hpp"
15 #include "parsing.hpp"
16 #include "str_utility.hpp"
17 
18 #include <systemd/sd-bus-protocol.h>
19 #include <systemd/sd-bus.h>
20 #include <tinyxml2.h>
21 
22 #include <boost/beast/http/field.hpp>
23 #include <boost/beast/http/status.hpp>
24 #include <boost/beast/http/verb.hpp>
25 #include <boost/container/flat_map.hpp>
26 #include <boost/system/error_code.hpp>
27 #include <nlohmann/json.hpp>
28 #include <sdbusplus/asio/connection.hpp>
29 #include <sdbusplus/message.hpp>
30 #include <sdbusplus/message/native_types.hpp>
31 
32 #include <algorithm>
33 #include <array>
34 #include <cerrno>
35 #include <cstdint>
36 #include <cstring>
37 #include <filesystem>
38 #include <functional>
39 #include <initializer_list>
40 #include <limits>
41 #include <map>
42 #include <memory>
43 #include <ranges>
44 #include <regex>
45 #include <string>
46 #include <string_view>
47 #include <type_traits>
48 #include <utility>
49 #include <variant>
50 #include <vector>
51 
52 namespace crow
53 {
54 namespace openbmc_mapper
55 {
56 const constexpr char* notFoundMsg = "404 Not Found";
57 const constexpr char* badReqMsg = "400 Bad Request";
58 const constexpr char* methodNotAllowedMsg = "405 Method Not Allowed";
59 const constexpr char* forbiddenMsg = "403 Forbidden";
60 const constexpr char* unsupportedMediaMsg = "415 Unsupported Media Type";
61 const constexpr char* methodFailedMsg = "500 Method Call Failed";
62 const constexpr char* methodOutputFailedMsg = "500 Method Output Error";
63 const constexpr char* notFoundDesc =
64     "org.freedesktop.DBus.Error.FileNotFound: path or object not found";
65 const constexpr char* propNotFoundDesc =
66     "The specified property cannot be found";
67 const constexpr char* noJsonDesc = "No JSON object could be decoded";
68 const constexpr char* invalidContentType =
69     "Content-type header is missing or invalid";
70 const constexpr char* methodNotFoundDesc =
71     "The specified method cannot be found";
72 const constexpr char* methodNotAllowedDesc = "Method not allowed";
73 const constexpr char* forbiddenPropDesc =
74     "The specified property cannot be created";
75 const constexpr char* forbiddenResDesc =
76     "The specified resource cannot be created";
77 
validateFilename(const std::string & filename)78 inline bool validateFilename(const std::string& filename)
79 {
80     static std::regex validFilename(R"(^[\w\- ]+(\.?[\w\- ]*)$)");
81 
82     return std::regex_match(filename, validFilename);
83 }
84 
setErrorResponse(crow::Response & res,boost::beast::http::status result,const std::string & desc,std::string_view msg)85 inline void setErrorResponse(crow::Response& res,
86                              boost::beast::http::status result,
87                              const std::string& desc, std::string_view msg)
88 {
89     res.result(result);
90     res.jsonValue["data"]["description"] = desc;
91     res.jsonValue["message"] = msg;
92     res.jsonValue["status"] = "error";
93 }
94 
introspectObjects(const std::string & processName,const std::string & objectPath,const std::shared_ptr<bmcweb::AsyncResp> & transaction)95 inline void introspectObjects(
96     const std::string& processName, const std::string& objectPath,
97     const std::shared_ptr<bmcweb::AsyncResp>& transaction)
98 {
99     if (transaction->res.jsonValue.is_null())
100     {
101         transaction->res.jsonValue["status"] = "ok";
102         transaction->res.jsonValue["bus_name"] = processName;
103         transaction->res.jsonValue["objects"] = nlohmann::json::array();
104     }
105 
106     dbus::utility::async_method_call(
107         [transaction, processName{std::string(processName)},
108          objectPath{std::string(objectPath)}](
109             const boost::system::error_code& ec,
110             const std::string& introspectXml) {
111             if (ec)
112             {
113                 BMCWEB_LOG_ERROR(
114                     "Introspect call failed with error: {} on process: {} path: {}",
115                     ec.message(), processName, objectPath);
116                 return;
117             }
118             nlohmann::json::object_t object;
119             object["path"] = objectPath;
120 
121             transaction->res.jsonValue["objects"].emplace_back(
122                 std::move(object));
123 
124             tinyxml2::XMLDocument doc;
125 
126             doc.Parse(introspectXml.c_str());
127             tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
128             if (pRoot == nullptr)
129             {
130                 BMCWEB_LOG_ERROR("XML document failed to parse {} {}",
131                                  processName, objectPath);
132             }
133             else
134             {
135                 tinyxml2::XMLElement* node = pRoot->FirstChildElement("node");
136                 while (node != nullptr)
137                 {
138                     const char* childPath = node->Attribute("name");
139                     if (childPath != nullptr)
140                     {
141                         std::string newpath;
142                         if (objectPath != "/")
143                         {
144                             newpath += objectPath;
145                         }
146                         newpath += std::string("/") + childPath;
147                         // introspect the subobjects as well
148                         introspectObjects(processName, newpath, transaction);
149                     }
150 
151                     node = node->NextSiblingElement("node");
152                 }
153             }
154         },
155         processName, objectPath, "org.freedesktop.DBus.Introspectable",
156         "Introspect");
157 }
158 
getPropertiesForEnumerate(const std::string & objectPath,const std::string & service,const std::string & interface,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)159 inline void getPropertiesForEnumerate(
160     const std::string& objectPath, const std::string& service,
161     const std::string& interface,
162     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
163 {
164     BMCWEB_LOG_DEBUG("getPropertiesForEnumerate {} {} {}", objectPath, service,
165                      interface);
166 
167     dbus::utility::getAllProperties(
168         service, objectPath, interface,
169         [asyncResp, objectPath, service,
170          interface](const boost::system::error_code& ec,
171                     const dbus::utility::DBusPropertiesMap& propertiesList) {
172             if (ec)
173             {
174                 BMCWEB_LOG_ERROR(
175                     "GetAll on path {} iface {} service {} failed with code {}",
176                     objectPath, interface, service, ec);
177                 return;
178             }
179 
180             nlohmann::json& dataJson = asyncResp->res.jsonValue["data"];
181             nlohmann::json& objectJson = dataJson[objectPath];
182             if (objectJson.is_null())
183             {
184                 objectJson = nlohmann::json::object();
185             }
186 
187             for (const auto& [name, value] : propertiesList)
188             {
189                 nlohmann::json& propertyJson = objectJson[name];
190                 std::visit([&propertyJson](auto&& val) { propertyJson = val; },
191                            value);
192             }
193         });
194 }
195 
196 // Find any results that weren't picked up by ObjectManagers, to be
197 // called after all ObjectManagers are searched for and called.
findRemainingObjectsForEnumerate(const std::string & objectPath,const std::shared_ptr<dbus::utility::MapperGetSubTreeResponse> & subtree,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)198 inline void findRemainingObjectsForEnumerate(
199     const std::string& objectPath,
200     const std::shared_ptr<dbus::utility::MapperGetSubTreeResponse>& subtree,
201     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
202 {
203     BMCWEB_LOG_DEBUG("findRemainingObjectsForEnumerate");
204     const nlohmann::json& dataJson = asyncResp->res.jsonValue["data"];
205 
206     for (const auto& [path, interface_map] : *subtree)
207     {
208         if (path == objectPath)
209         {
210             // An enumerate does not return the target path's properties
211             continue;
212         }
213         if (!dataJson.contains(path))
214         {
215             for (const auto& [service, interfaces] : interface_map)
216             {
217                 for (const auto& interface : interfaces)
218                 {
219                     if (!interface.starts_with("org.freedesktop.DBus"))
220                     {
221                         getPropertiesForEnumerate(path, service, interface,
222                                                   asyncResp);
223                     }
224                 }
225             }
226         }
227     }
228 }
229 
230 struct InProgressEnumerateData
231 {
InProgressEnumerateDatacrow::openbmc_mapper::InProgressEnumerateData232     InProgressEnumerateData(
233         const std::string& objectPathIn,
234         const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn) :
235         objectPath(objectPathIn), asyncResp(asyncRespIn)
236     {}
237 
~InProgressEnumerateDatacrow::openbmc_mapper::InProgressEnumerateData238     ~InProgressEnumerateData()
239     {
240         try
241         {
242             findRemainingObjectsForEnumerate(objectPath, subtree, asyncResp);
243         }
244         catch (...)
245         {
246             BMCWEB_LOG_CRITICAL(
247                 "findRemainingObjectsForEnumerate threw exception");
248         }
249     }
250 
251     InProgressEnumerateData(const InProgressEnumerateData&) = delete;
252     InProgressEnumerateData(InProgressEnumerateData&&) = delete;
253     InProgressEnumerateData& operator=(const InProgressEnumerateData&) = delete;
254     InProgressEnumerateData& operator=(InProgressEnumerateData&&) = delete;
255     const std::string objectPath;
256     std::shared_ptr<dbus::utility::MapperGetSubTreeResponse> subtree;
257     std::shared_ptr<bmcweb::AsyncResp> asyncResp;
258 };
259 
getManagedObjectsForEnumerate(const std::string & objectName,const std::string & objectManagerPath,const std::string & connectionName,const std::shared_ptr<InProgressEnumerateData> & transaction)260 inline void getManagedObjectsForEnumerate(
261     const std::string& objectName, const std::string& objectManagerPath,
262     const std::string& connectionName,
263     const std::shared_ptr<InProgressEnumerateData>& transaction)
264 {
265     BMCWEB_LOG_DEBUG(
266         "getManagedObjectsForEnumerate {} object_manager_path {} connection_name {}",
267         objectName, objectManagerPath, connectionName);
268     sdbusplus::message::object_path path(objectManagerPath);
269     dbus::utility::getManagedObjects(
270         connectionName, path,
271         [transaction, objectName,
272          connectionName](const boost::system::error_code& ec,
273                          const dbus::utility::ManagedObjectType& objects) {
274             if (ec)
275             {
276                 BMCWEB_LOG_ERROR(
277                     "GetManagedObjects on path {} on connection {} failed with code {}",
278                     objectName, connectionName, ec);
279                 return;
280             }
281 
282             nlohmann::json& dataJson =
283                 transaction->asyncResp->res.jsonValue["data"];
284 
285             for (const auto& objectPath : objects)
286             {
287                 if (objectPath.first.str.starts_with(objectName))
288                 {
289                     BMCWEB_LOG_DEBUG("Reading object {}", objectPath.first.str);
290                     nlohmann::json& objectJson = dataJson[objectPath.first.str];
291                     if (objectJson.is_null())
292                     {
293                         objectJson = nlohmann::json::object();
294                     }
295                     for (const auto& interface : objectPath.second)
296                     {
297                         for (const auto& property : interface.second)
298                         {
299                             nlohmann::json& propertyJson =
300                                 objectJson[property.first];
301                             std::visit(
302                                 [&propertyJson](auto&& val) {
303                                     if constexpr (
304                                         std::is_same_v<
305                                             std::decay_t<decltype(val)>,
306                                             sdbusplus::message::unix_fd>)
307                                     {
308                                         propertyJson = val.fd;
309                                     }
310                                     else
311                                     {
312                                         propertyJson = val;
313                                     }
314                                 },
315                                 property.second);
316                         }
317                     }
318                 }
319                 for (const auto& interface : objectPath.second)
320                 {
321                     if (interface.first == "org.freedesktop.DBus.ObjectManager")
322                     {
323                         getManagedObjectsForEnumerate(
324                             objectPath.first.str, objectPath.first.str,
325                             connectionName, transaction);
326                     }
327                 }
328             }
329         });
330 }
331 
findObjectManagerPathForEnumerate(const std::string & objectName,const std::string & connectionName,const std::shared_ptr<InProgressEnumerateData> & transaction)332 inline void findObjectManagerPathForEnumerate(
333     const std::string& objectName, const std::string& connectionName,
334     const std::shared_ptr<InProgressEnumerateData>& transaction)
335 {
336     BMCWEB_LOG_DEBUG("Finding objectmanager for path {} on connection:{}",
337                      objectName, connectionName);
338     dbus::utility::async_method_call(
339         [transaction, objectName, connectionName](
340             const boost::system::error_code& ec,
341             const dbus::utility::MapperGetAncestorsResponse& objects) {
342             if (ec)
343             {
344                 BMCWEB_LOG_ERROR("GetAncestors on path {} failed with code {}",
345                                  objectName, ec);
346                 return;
347             }
348 
349             for (const auto& pathGroup : objects)
350             {
351                 for (const auto& connectionGroup : pathGroup.second)
352                 {
353                     if (connectionGroup.first == connectionName)
354                     {
355                         // Found the object manager path for this resource.
356                         getManagedObjectsForEnumerate(
357                             objectName, pathGroup.first, connectionName,
358                             transaction);
359                         return;
360                     }
361                 }
362             }
363         },
364         "xyz.openbmc_project.ObjectMapper",
365         "/xyz/openbmc_project/object_mapper",
366         "xyz.openbmc_project.ObjectMapper", "GetAncestors", objectName,
367         std::array<const char*, 1>{"org.freedesktop.DBus.ObjectManager"});
368 }
369 
370 // Uses GetObject to add the object info about the target /enumerate path to
371 // the results of GetSubTree, as GetSubTree will not return info for the
372 // target path, and then continues on enumerating the rest of the tree.
getObjectAndEnumerate(const std::shared_ptr<InProgressEnumerateData> & transaction)373 inline void getObjectAndEnumerate(
374     const std::shared_ptr<InProgressEnumerateData>& transaction)
375 {
376     dbus::utility::getDbusObject(
377         transaction->objectPath, {},
378         [transaction](const boost::system::error_code& ec,
379                       const dbus::utility::MapperGetObject& objects) {
380             if (ec)
381             {
382                 BMCWEB_LOG_ERROR("GetObject for path {} failed with code {}",
383                                  transaction->objectPath, ec);
384                 return;
385             }
386 
387             BMCWEB_LOG_DEBUG("GetObject for {} has {} entries",
388                              transaction->objectPath, objects.size());
389             if (!objects.empty())
390             {
391                 transaction->subtree->emplace_back(transaction->objectPath,
392                                                    objects);
393             }
394 
395             // Map indicating connection name, and the path where the object
396             // manager exists
397             boost::container::flat_map<
398                 std::string, std::string, std::less<>,
399                 std::vector<std::pair<std::string, std::string>>>
400                 connections;
401 
402             for (const auto& object : *(transaction->subtree))
403             {
404                 for (const auto& connection : object.second)
405                 {
406                     for (const auto& interface : connection.second)
407                     {
408                         BMCWEB_LOG_DEBUG("{} has interface {}",
409                                          connection.first, interface);
410                         if (interface == "org.freedesktop.DBus.ObjectManager")
411                         {
412                             BMCWEB_LOG_DEBUG("found object manager path {}",
413                                              object.first);
414                             connections[connection.first] = object.first;
415                         }
416                     }
417                 }
418             }
419             BMCWEB_LOG_DEBUG("Got {} connections", connections.size());
420 
421             for (const auto& connection : connections)
422             {
423                 // If we already know where the object manager is, we don't
424                 // need to search for it, we can call directly in to
425                 // getManagedObjects
426                 if (!connection.second.empty())
427                 {
428                     getManagedObjectsForEnumerate(
429                         transaction->objectPath, connection.second,
430                         connection.first, transaction);
431                 }
432                 else
433                 {
434                     // otherwise we need to find the object manager path
435                     // before we can continue
436                     findObjectManagerPathForEnumerate(
437                         transaction->objectPath, connection.first, transaction);
438                 }
439             }
440         });
441 }
442 
443 // Structure for storing data on an in progress action
444 struct InProgressActionData
445 {
InProgressActionDatacrow::openbmc_mapper::InProgressActionData446     explicit InProgressActionData(
447         const std::shared_ptr<bmcweb::AsyncResp>& res) : asyncResp(res)
448     {}
~InProgressActionDatacrow::openbmc_mapper::InProgressActionData449     ~InProgressActionData()
450     {
451         // Methods could have been called across different owners
452         // and interfaces, where some calls failed and some passed.
453         //
454         // The rules for this are:
455         // * if no method was called - error
456         // * if a method failed and none passed - error
457         //   (converse: if at least one method passed - OK)
458         // * for the method output:
459         //   * if output processing didn't fail, return the data
460 
461         // Only deal with method returns if nothing failed earlier
462         if (asyncResp->res.result() == boost::beast::http::status::ok)
463         {
464             if (!methodPassed)
465             {
466                 if (!methodFailed)
467                 {
468                     setErrorResponse(asyncResp->res,
469                                      boost::beast::http::status::not_found,
470                                      methodNotFoundDesc, notFoundMsg);
471                 }
472             }
473             else
474             {
475                 if (outputFailed)
476                 {
477                     setErrorResponse(
478                         asyncResp->res,
479                         boost::beast::http::status::internal_server_error,
480                         "Method output failure", methodOutputFailedMsg);
481                 }
482                 else
483                 {
484                     asyncResp->res.jsonValue["status"] = "ok";
485                     asyncResp->res.jsonValue["message"] = "200 OK";
486                     asyncResp->res.jsonValue["data"] = methodResponse;
487                 }
488             }
489         }
490     }
491     InProgressActionData(const InProgressActionData&) = delete;
492     InProgressActionData(InProgressActionData&&) = delete;
493     InProgressActionData& operator=(const InProgressActionData&) = delete;
494     InProgressActionData& operator=(InProgressActionData&&) = delete;
495 
setErrorStatuscrow::openbmc_mapper::InProgressActionData496     void setErrorStatus(const std::string& desc)
497     {
498         setErrorResponse(asyncResp->res,
499                          boost::beast::http::status::bad_request, desc,
500                          badReqMsg);
501     }
502     std::shared_ptr<bmcweb::AsyncResp> asyncResp;
503     std::string path;
504     std::string methodName;
505     std::string interfaceName;
506     bool methodPassed = false;
507     bool methodFailed = false;
508     bool outputFailed = false;
509     bool convertedToArray = false;
510     nlohmann::json methodResponse;
511     nlohmann::json arguments;
512 };
513 
dbusArgSplit(const std::string & string)514 inline std::vector<std::string> dbusArgSplit(const std::string& string)
515 {
516     std::vector<std::string> ret;
517     if (string.empty())
518     {
519         return ret;
520     }
521     ret.emplace_back("");
522     int containerDepth = 0;
523 
524     for (std::string::const_iterator character = string.begin();
525          character != string.end(); character++)
526     {
527         ret.back() += *character;
528         switch (*character)
529         {
530             case ('a'):
531                 break;
532             case ('('):
533             case ('{'):
534                 containerDepth++;
535                 break;
536             case ('}'):
537             case (')'):
538                 containerDepth--;
539                 if (containerDepth == 0)
540                 {
541                     if (character + 1 != string.end())
542                     {
543                         ret.emplace_back("");
544                     }
545                 }
546                 break;
547             default:
548                 if (containerDepth == 0)
549                 {
550                     if (character + 1 != string.end())
551                     {
552                         ret.emplace_back("");
553                     }
554                 }
555                 break;
556         }
557     }
558 
559     return ret;
560 }
561 
convertJsonToDbus(sd_bus_message * m,const std::string & argType,const nlohmann::json & inputJson)562 inline int convertJsonToDbus(sd_bus_message* m, const std::string& argType,
563                              const nlohmann::json& inputJson)
564 {
565     int r = 0;
566     BMCWEB_LOG_DEBUG("Converting {} to type: {}", inputJson, argType);
567     const std::vector<std::string> argTypes = dbusArgSplit(argType);
568 
569     // Assume a single object for now.
570     const nlohmann::json* j = &inputJson;
571     const nlohmann::json::array_t* inputArr =
572         inputJson.get_ptr<const nlohmann::json::array_t*>();
573     if (inputArr == nullptr)
574     {
575         return -3;
576     }
577     nlohmann::json::array_t::const_iterator jIt = inputArr->begin();
578     for (const std::string& argCode : argTypes)
579     {
580         // If we are decoding multiple objects, grab the pointer to the
581         // iterator, and increment it for the next loop
582         if (argTypes.size() > 1)
583         {
584             if (jIt == inputArr->end())
585             {
586                 return -2;
587             }
588             j = &*jIt;
589             jIt++;
590         }
591         const int64_t* intValue = j->get_ptr<const int64_t*>();
592         const std::string* stringValue = j->get_ptr<const std::string*>();
593         const double* doubleValue = j->get_ptr<const double*>();
594         const bool* b = j->get_ptr<const bool*>();
595         int64_t v = 0;
596         double d = 0.0;
597 
598         // Do some basic type conversions that make sense.  uint can be
599         // converted to int.  int and uint can be converted to double
600         if (intValue == nullptr)
601         {
602             const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
603             if (uintValue != nullptr)
604             {
605                 v = static_cast<int64_t>(*uintValue);
606                 intValue = &v;
607             }
608         }
609         if (doubleValue == nullptr)
610         {
611             const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
612             if (uintValue != nullptr)
613             {
614                 d = static_cast<double>(*uintValue);
615                 doubleValue = &d;
616             }
617         }
618         if (doubleValue == nullptr)
619         {
620             if (intValue != nullptr)
621             {
622                 d = static_cast<double>(*intValue);
623                 doubleValue = &d;
624             }
625         }
626 
627         if (argCode == "s")
628         {
629             if (stringValue == nullptr)
630             {
631                 return -1;
632             }
633             r = sd_bus_message_append_basic(
634                 m, argCode[0], static_cast<const void*>(stringValue->data()));
635             if (r < 0)
636             {
637                 return r;
638             }
639         }
640         else if (argCode == "i")
641         {
642             if (intValue == nullptr)
643             {
644                 return -1;
645             }
646             if ((*intValue < std::numeric_limits<int32_t>::lowest()) ||
647                 (*intValue > std::numeric_limits<int32_t>::max()))
648             {
649                 return -ERANGE;
650             }
651             int32_t i = static_cast<int32_t>(*intValue);
652             r = sd_bus_message_append_basic(m, argCode[0], &i);
653             if (r < 0)
654             {
655                 return r;
656             }
657         }
658         else if (argCode == "b")
659         {
660             // lots of ways bool could be represented here.  Try them all
661             int boolInt = 0;
662             if (intValue != nullptr)
663             {
664                 if (*intValue == 1)
665                 {
666                     boolInt = 1;
667                 }
668                 else if (*intValue == 0)
669                 {
670                     boolInt = 0;
671                 }
672                 else
673                 {
674                     return -ERANGE;
675                 }
676             }
677             else if (b != nullptr)
678             {
679                 boolInt = *b ? 1 : 0;
680             }
681             else if (stringValue != nullptr)
682             {
683                 if (!stringValue->empty())
684                 {
685                     if (stringValue->front() == 't' ||
686                         stringValue->front() == 'T')
687                     {
688                         boolInt = 1;
689                     }
690                 }
691             }
692             else
693             {
694                 return -1;
695             }
696             r = sd_bus_message_append_basic(m, argCode[0], &boolInt);
697             if (r < 0)
698             {
699                 return r;
700             }
701         }
702         else if (argCode == "n")
703         {
704             if (intValue == nullptr)
705             {
706                 return -1;
707             }
708             if ((*intValue < std::numeric_limits<int16_t>::lowest()) ||
709                 (*intValue > std::numeric_limits<int16_t>::max()))
710             {
711                 return -ERANGE;
712             }
713             int16_t n = static_cast<int16_t>(*intValue);
714             r = sd_bus_message_append_basic(m, argCode[0], &n);
715             if (r < 0)
716             {
717                 return r;
718             }
719         }
720         else if (argCode == "x")
721         {
722             if (intValue == nullptr)
723             {
724                 return -1;
725             }
726             r = sd_bus_message_append_basic(m, argCode[0], intValue);
727             if (r < 0)
728             {
729                 return r;
730             }
731         }
732         else if (argCode == "y")
733         {
734             const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
735             if (uintValue == nullptr)
736             {
737                 return -1;
738             }
739             if (*uintValue > std::numeric_limits<uint8_t>::max())
740             {
741                 return -ERANGE;
742             }
743             uint8_t y = static_cast<uint8_t>(*uintValue);
744             r = sd_bus_message_append_basic(m, argCode[0], &y);
745         }
746         else if (argCode == "q")
747         {
748             const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
749             if (uintValue == nullptr)
750             {
751                 return -1;
752             }
753             if (*uintValue > std::numeric_limits<uint16_t>::max())
754             {
755                 return -ERANGE;
756             }
757             uint16_t q = static_cast<uint16_t>(*uintValue);
758             r = sd_bus_message_append_basic(m, argCode[0], &q);
759         }
760         else if (argCode == "u")
761         {
762             const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
763             if (uintValue == nullptr)
764             {
765                 return -1;
766             }
767             if (*uintValue > std::numeric_limits<uint32_t>::max())
768             {
769                 return -ERANGE;
770             }
771             uint32_t u = static_cast<uint32_t>(*uintValue);
772             r = sd_bus_message_append_basic(m, argCode[0], &u);
773         }
774         else if (argCode == "t")
775         {
776             const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
777             if (uintValue == nullptr)
778             {
779                 return -1;
780             }
781             r = sd_bus_message_append_basic(m, argCode[0], uintValue);
782         }
783         else if (argCode == "d")
784         {
785             if (doubleValue == nullptr)
786             {
787                 return -1;
788             }
789             if ((*doubleValue < std::numeric_limits<double>::lowest()) ||
790                 (*doubleValue > std::numeric_limits<double>::max()))
791             {
792                 return -ERANGE;
793             }
794             r = sd_bus_message_append_basic(m, argCode[0], doubleValue);
795             if (r < 0)
796             {
797                 return r;
798             }
799         }
800         else if (argCode.starts_with("a"))
801         {
802             std::string containedType = argCode.substr(1);
803             r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY,
804                                               containedType.c_str());
805             if (r < 0)
806             {
807                 return r;
808             }
809             const nlohmann::json::array_t* arr =
810                 j->get_ptr<const nlohmann::json::array_t*>();
811             if (arr == nullptr)
812             {
813                 return -1;
814             }
815             for (const auto& it : *arr)
816             {
817                 r = convertJsonToDbus(m, containedType, it);
818                 if (r < 0)
819                 {
820                     return r;
821                 }
822             }
823             sd_bus_message_close_container(m);
824         }
825         else if (argCode.starts_with("v"))
826         {
827             std::string containedType = argCode.substr(1);
828             BMCWEB_LOG_DEBUG("variant type: {} appending variant of type: {}",
829                              argCode, containedType);
830             r = sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT,
831                                               containedType.c_str());
832             if (r < 0)
833             {
834                 return r;
835             }
836 
837             r = convertJsonToDbus(m, containedType, inputJson);
838             if (r < 0)
839             {
840                 return r;
841             }
842 
843             r = sd_bus_message_close_container(m);
844             if (r < 0)
845             {
846                 return r;
847             }
848         }
849         else if (argCode.starts_with("(") && argCode.ends_with(")"))
850         {
851             std::string containedType = argCode.substr(1, argCode.size() - 2);
852             r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT,
853                                               containedType.c_str());
854             if (r < 0)
855             {
856                 return r;
857             }
858             const nlohmann::json::array_t* arr =
859                 j->get_ptr<const nlohmann::json::array_t*>();
860             if (arr == nullptr)
861             {
862                 return -1;
863             }
864             nlohmann::json::array_t::const_iterator it = arr->begin();
865             for (const std::string& argCode2 : dbusArgSplit(argType))
866             {
867                 if (it == arr->end())
868                 {
869                     return -1;
870                 }
871                 r = convertJsonToDbus(m, argCode2, *it);
872                 if (r < 0)
873                 {
874                     return r;
875                 }
876                 it++;
877             }
878             r = sd_bus_message_close_container(m);
879         }
880         else if (argCode.starts_with("{") && argCode.ends_with("}"))
881         {
882             std::string containedType = argCode.substr(1, argCode.size() - 2);
883             r = sd_bus_message_open_container(m, SD_BUS_TYPE_DICT_ENTRY,
884                                               containedType.c_str());
885             if (r < 0)
886             {
887                 return r;
888             }
889 
890             std::vector<std::string> codes = dbusArgSplit(containedType);
891             if (codes.size() != 2)
892             {
893                 return -1;
894             }
895             const std::string& keyType = codes[0];
896             const std::string& valueType = codes[1];
897             const nlohmann::json::object_t* arr =
898                 j->get_ptr<const nlohmann::json::object_t*>();
899             if (arr == nullptr)
900             {
901                 return -1;
902             }
903             for (const auto& it : *arr)
904             {
905                 r = convertJsonToDbus(m, keyType, it.first);
906                 if (r < 0)
907                 {
908                     return r;
909                 }
910 
911                 r = convertJsonToDbus(m, valueType, it.second);
912                 if (r < 0)
913                 {
914                     return r;
915                 }
916             }
917             r = sd_bus_message_close_container(m);
918         }
919         else
920         {
921             return -2;
922         }
923         if (r < 0)
924         {
925             return r;
926         }
927 
928         if (argTypes.size() > 1)
929         {
930             jIt++;
931         }
932     }
933 
934     return r;
935 }
936 
937 template <typename T>
readMessageItem(const std::string & typeCode,sdbusplus::message_t & m,nlohmann::json & data)938 int readMessageItem(const std::string& typeCode, sdbusplus::message_t& m,
939                     nlohmann::json& data)
940 {
941     T value;
942     // When T == char*, this warning fires.  Unclear how to resolve
943     // Given that sd-bus takes a void pointer to a char*, and that's
944     // Not something we can fix.
945     // NOLINTNEXTLINE(bugprone-multi-level-implicit-pointer-conversion)
946     int r = sd_bus_message_read_basic(m.get(), typeCode.front(), &value);
947     if (r < 0)
948     {
949         BMCWEB_LOG_ERROR("sd_bus_message_read_basic on type {} failed!",
950                          typeCode);
951         return r;
952     }
953 
954     data = value;
955     return 0;
956 }
957 
958 int convertDBusToJSON(const std::string& returnType, sdbusplus::message_t& m,
959                       nlohmann::json& response);
960 
readDictEntryFromMessage(const std::string & typeCode,sdbusplus::message_t & m,nlohmann::json & object)961 inline int readDictEntryFromMessage(const std::string& typeCode,
962                                     sdbusplus::message_t& m,
963                                     nlohmann::json& object)
964 {
965     std::vector<std::string> types = dbusArgSplit(typeCode);
966     if (types.size() != 2)
967     {
968         BMCWEB_LOG_ERROR("wrong number contained types in dictionary: {}",
969                          types.size());
970         return -1;
971     }
972 
973     int r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_DICT_ENTRY,
974                                            typeCode.c_str());
975     if (r < 0)
976     {
977         BMCWEB_LOG_ERROR("sd_bus_message_enter_container with rc {}", r);
978         return r;
979     }
980 
981     nlohmann::json key;
982     r = convertDBusToJSON(types[0], m, key);
983     if (r < 0)
984     {
985         return r;
986     }
987 
988     const std::string* keyPtr = key.get_ptr<const std::string*>();
989     if (keyPtr == nullptr)
990     {
991         // json doesn't support non-string keys.  If we hit this condition,
992         // convert the result to a string so we can proceed
993         key = key.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
994         keyPtr = key.get_ptr<const std::string*>();
995         // in theory this can't fail now, but lets be paranoid about it
996         // anyway
997         if (keyPtr == nullptr)
998         {
999             return -1;
1000         }
1001     }
1002     nlohmann::json& value = object[*keyPtr];
1003 
1004     r = convertDBusToJSON(types[1], m, value);
1005     if (r < 0)
1006     {
1007         return r;
1008     }
1009 
1010     r = sd_bus_message_exit_container(m.get());
1011     if (r < 0)
1012     {
1013         BMCWEB_LOG_ERROR("sd_bus_message_exit_container failed");
1014         return r;
1015     }
1016 
1017     return 0;
1018 }
1019 
readArrayFromMessage(const std::string & typeCode,sdbusplus::message_t & m,nlohmann::json & data)1020 inline int readArrayFromMessage(const std::string& typeCode,
1021                                 sdbusplus::message_t& m, nlohmann::json& data)
1022 {
1023     if (typeCode.size() < 2)
1024     {
1025         BMCWEB_LOG_ERROR("Type code {} too small for an array", typeCode);
1026         return -1;
1027     }
1028 
1029     std::string containedType = typeCode.substr(1);
1030 
1031     int r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_ARRAY,
1032                                            containedType.c_str());
1033     if (r < 0)
1034     {
1035         BMCWEB_LOG_ERROR("sd_bus_message_enter_container failed with rc {}", r);
1036         return r;
1037     }
1038 
1039     bool dict = containedType.starts_with("{") && containedType.ends_with("}");
1040 
1041     if (dict)
1042     {
1043         // Remove the { }
1044         containedType = containedType.substr(1, containedType.size() - 2);
1045         data = nlohmann::json::object();
1046     }
1047     else
1048     {
1049         data = nlohmann::json::array();
1050     }
1051 
1052     while (true)
1053     {
1054         r = sd_bus_message_at_end(m.get(), 0);
1055         if (r < 0)
1056         {
1057             BMCWEB_LOG_ERROR("sd_bus_message_at_end failed");
1058             return r;
1059         }
1060 
1061         if (r > 0)
1062         {
1063             break;
1064         }
1065 
1066         // Dictionaries are only ever seen in an array
1067         if (dict)
1068         {
1069             r = readDictEntryFromMessage(containedType, m, data);
1070             if (r < 0)
1071             {
1072                 return r;
1073             }
1074         }
1075         else
1076         {
1077             data.push_back(nlohmann::json());
1078 
1079             r = convertDBusToJSON(containedType, m, data.back());
1080             if (r < 0)
1081             {
1082                 return r;
1083             }
1084         }
1085     }
1086 
1087     r = sd_bus_message_exit_container(m.get());
1088     if (r < 0)
1089     {
1090         BMCWEB_LOG_ERROR("sd_bus_message_exit_container failed");
1091         return r;
1092     }
1093 
1094     return 0;
1095 }
1096 
readStructFromMessage(const std::string & typeCode,sdbusplus::message_t & m,nlohmann::json & data)1097 inline int readStructFromMessage(const std::string& typeCode,
1098                                  sdbusplus::message_t& m, nlohmann::json& data)
1099 {
1100     if (typeCode.size() < 3)
1101     {
1102         BMCWEB_LOG_ERROR("Type code {} too small for a struct", typeCode);
1103         return -1;
1104     }
1105 
1106     std::string containedTypes = typeCode.substr(1, typeCode.size() - 2);
1107     std::vector<std::string> types = dbusArgSplit(containedTypes);
1108 
1109     int r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_STRUCT,
1110                                            containedTypes.c_str());
1111     if (r < 0)
1112     {
1113         BMCWEB_LOG_ERROR("sd_bus_message_enter_container failed with rc {}", r);
1114         return r;
1115     }
1116 
1117     for (const std::string& type : types)
1118     {
1119         data.push_back(nlohmann::json());
1120         r = convertDBusToJSON(type, m, data.back());
1121         if (r < 0)
1122         {
1123             return r;
1124         }
1125     }
1126 
1127     r = sd_bus_message_exit_container(m.get());
1128     if (r < 0)
1129     {
1130         BMCWEB_LOG_ERROR("sd_bus_message_exit_container failed");
1131         return r;
1132     }
1133     return 0;
1134 }
1135 
readVariantFromMessage(sdbusplus::message_t & m,nlohmann::json & data)1136 inline int readVariantFromMessage(sdbusplus::message_t& m, nlohmann::json& data)
1137 {
1138     const char* containerType = nullptr;
1139     int r = sd_bus_message_peek_type(m.get(), nullptr, &containerType);
1140     if (r < 0)
1141     {
1142         BMCWEB_LOG_ERROR("sd_bus_message_peek_type failed");
1143         return r;
1144     }
1145 
1146     r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_VARIANT,
1147                                        containerType);
1148     if (r < 0)
1149     {
1150         BMCWEB_LOG_ERROR("sd_bus_message_enter_container failed with rc {}", r);
1151         return r;
1152     }
1153 
1154     r = convertDBusToJSON(containerType, m, data);
1155     if (r < 0)
1156     {
1157         return r;
1158     }
1159 
1160     r = sd_bus_message_exit_container(m.get());
1161     if (r < 0)
1162     {
1163         BMCWEB_LOG_ERROR("sd_bus_message_enter_container failed");
1164         return r;
1165     }
1166 
1167     return 0;
1168 }
1169 
convertDBusToJSON(const std::string & returnType,sdbusplus::message_t & m,nlohmann::json & response)1170 inline int convertDBusToJSON(const std::string& returnType,
1171                              sdbusplus::message_t& m, nlohmann::json& response)
1172 {
1173     int r = 0;
1174     const std::vector<std::string> returnTypes = dbusArgSplit(returnType);
1175 
1176     for (const std::string& typeCode : returnTypes)
1177     {
1178         nlohmann::json* thisElement = &response;
1179         if (returnTypes.size() > 1)
1180         {
1181             response.push_back(nlohmann::json{});
1182             thisElement = &response.back();
1183         }
1184 
1185         if (typeCode == "s" || typeCode == "g" || typeCode == "o")
1186         {
1187             r = readMessageItem<char*>(typeCode, m, *thisElement);
1188             if (r < 0)
1189             {
1190                 return r;
1191             }
1192         }
1193         else if (typeCode == "b")
1194         {
1195             r = readMessageItem<int>(typeCode, m, *thisElement);
1196             if (r < 0)
1197             {
1198                 return r;
1199             }
1200 
1201             *thisElement = static_cast<bool>(thisElement->get<int>());
1202         }
1203         else if (typeCode == "u")
1204         {
1205             r = readMessageItem<uint32_t>(typeCode, m, *thisElement);
1206             if (r < 0)
1207             {
1208                 return r;
1209             }
1210         }
1211         else if (typeCode == "i")
1212         {
1213             r = readMessageItem<int32_t>(typeCode, m, *thisElement);
1214             if (r < 0)
1215             {
1216                 return r;
1217             }
1218         }
1219         else if (typeCode == "x")
1220         {
1221             r = readMessageItem<int64_t>(typeCode, m, *thisElement);
1222             if (r < 0)
1223             {
1224                 return r;
1225             }
1226         }
1227         else if (typeCode == "t")
1228         {
1229             r = readMessageItem<uint64_t>(typeCode, m, *thisElement);
1230             if (r < 0)
1231             {
1232                 return r;
1233             }
1234         }
1235         else if (typeCode == "n")
1236         {
1237             r = readMessageItem<int16_t>(typeCode, m, *thisElement);
1238             if (r < 0)
1239             {
1240                 return r;
1241             }
1242         }
1243         else if (typeCode == "q")
1244         {
1245             r = readMessageItem<uint16_t>(typeCode, m, *thisElement);
1246             if (r < 0)
1247             {
1248                 return r;
1249             }
1250         }
1251         else if (typeCode == "y")
1252         {
1253             r = readMessageItem<uint8_t>(typeCode, m, *thisElement);
1254             if (r < 0)
1255             {
1256                 return r;
1257             }
1258         }
1259         else if (typeCode == "d")
1260         {
1261             r = readMessageItem<double>(typeCode, m, *thisElement);
1262             if (r < 0)
1263             {
1264                 return r;
1265             }
1266         }
1267         else if (typeCode == "h")
1268         {
1269             r = readMessageItem<int>(typeCode, m, *thisElement);
1270             if (r < 0)
1271             {
1272                 return r;
1273             }
1274         }
1275         else if (typeCode.starts_with("a"))
1276         {
1277             r = readArrayFromMessage(typeCode, m, *thisElement);
1278             if (r < 0)
1279             {
1280                 return r;
1281             }
1282         }
1283         else if (typeCode.starts_with("(") && typeCode.ends_with(")"))
1284         {
1285             r = readStructFromMessage(typeCode, m, *thisElement);
1286             if (r < 0)
1287             {
1288                 return r;
1289             }
1290         }
1291         else if (typeCode.starts_with("v"))
1292         {
1293             r = readVariantFromMessage(m, *thisElement);
1294             if (r < 0)
1295             {
1296                 return r;
1297             }
1298         }
1299         else
1300         {
1301             BMCWEB_LOG_ERROR("Invalid D-Bus signature type {}", typeCode);
1302             return -2;
1303         }
1304     }
1305 
1306     return 0;
1307 }
1308 
handleMethodResponse(const std::shared_ptr<InProgressActionData> & transaction,sdbusplus::message_t & m,const std::string & returnType)1309 inline void handleMethodResponse(
1310     const std::shared_ptr<InProgressActionData>& transaction,
1311     sdbusplus::message_t& m, const std::string& returnType)
1312 {
1313     nlohmann::json data;
1314 
1315     int r = convertDBusToJSON(returnType, m, data);
1316     if (r < 0)
1317     {
1318         transaction->outputFailed = true;
1319         return;
1320     }
1321 
1322     if (data.is_null())
1323     {
1324         return;
1325     }
1326 
1327     if (transaction->methodResponse.is_null())
1328     {
1329         transaction->methodResponse = std::move(data);
1330         return;
1331     }
1332 
1333     // If they're both dictionaries or arrays, merge into one.
1334     // Otherwise, make the results an array with every result
1335     // an entry.  Could also just fail in that case, but it
1336     // seems better to get the data back somehow.
1337     nlohmann::json::object_t* dataobj =
1338         data.get_ptr<nlohmann::json::object_t*>();
1339 
1340     nlohmann::json::object_t* methodResponseObj =
1341         transaction->methodResponse.get_ptr<nlohmann::json::object_t*>();
1342     if (methodResponseObj != nullptr && dataobj != nullptr)
1343     {
1344         for (auto& obj : *dataobj)
1345         {
1346             // Note: Will overwrite the data for a duplicate key
1347             methodResponseObj->emplace(obj.first, std::move(obj.second));
1348         }
1349         return;
1350     }
1351 
1352     nlohmann::json::array_t* dataarr = data.get_ptr<nlohmann::json::array_t*>();
1353     nlohmann::json::array_t* methodResponseArr =
1354         transaction->methodResponse.get_ptr<nlohmann::json::array_t*>();
1355     if (methodResponseArr != nullptr && dataarr != nullptr)
1356     {
1357         for (auto& obj : *dataarr)
1358         {
1359             methodResponseArr->emplace_back(std::move(obj));
1360         }
1361         return;
1362     }
1363 
1364     if (!transaction->convertedToArray)
1365     {
1366         // They are different types. May as well turn them into an array
1367         nlohmann::json j = std::move(transaction->methodResponse);
1368         transaction->methodResponse = nlohmann::json::array();
1369         transaction->methodResponse.emplace_back(std::move(j));
1370         transaction->methodResponse.emplace_back(std::move(data));
1371         transaction->convertedToArray = true;
1372     }
1373     else
1374     {
1375         transaction->methodResponse.emplace_back(std::move(data));
1376     }
1377 }
1378 
findActionOnInterface(const std::shared_ptr<InProgressActionData> & transaction,const std::string & connectionName)1379 inline void findActionOnInterface(
1380     const std::shared_ptr<InProgressActionData>& transaction,
1381     const std::string& connectionName)
1382 {
1383     BMCWEB_LOG_DEBUG("findActionOnInterface for connection {}", connectionName);
1384     dbus::utility::async_method_call(
1385         [transaction, connectionName{std::string(connectionName)}](
1386             const boost::system::error_code& ec,
1387             const std::string& introspectXml) {
1388             BMCWEB_LOG_DEBUG("got xml:\n {}", introspectXml);
1389             if (ec)
1390             {
1391                 BMCWEB_LOG_ERROR(
1392                     "Introspect call failed with error: {} on process: {}",
1393                     ec.message(), connectionName);
1394                 return;
1395             }
1396             tinyxml2::XMLDocument doc;
1397 
1398             doc.Parse(introspectXml.data(), introspectXml.size());
1399             tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
1400             if (pRoot == nullptr)
1401             {
1402                 BMCWEB_LOG_ERROR("XML document failed to parse {}",
1403                                  connectionName);
1404                 return;
1405             }
1406             tinyxml2::XMLElement* interfaceNode =
1407                 pRoot->FirstChildElement("interface");
1408             while (interfaceNode != nullptr)
1409             {
1410                 const char* thisInterfaceName =
1411                     interfaceNode->Attribute("name");
1412                 if (thisInterfaceName != nullptr)
1413                 {
1414                     if (!transaction->interfaceName.empty() &&
1415                         (transaction->interfaceName != thisInterfaceName))
1416                     {
1417                         interfaceNode =
1418                             interfaceNode->NextSiblingElement("interface");
1419                         continue;
1420                     }
1421 
1422                     tinyxml2::XMLElement* methodNode =
1423                         interfaceNode->FirstChildElement("method");
1424                     while (methodNode != nullptr)
1425                     {
1426                         const char* thisMethodName =
1427                             methodNode->Attribute("name");
1428                         BMCWEB_LOG_DEBUG("Found method: {}", thisMethodName);
1429                         if (thisMethodName != nullptr &&
1430                             thisMethodName == transaction->methodName)
1431                         {
1432                             BMCWEB_LOG_DEBUG(
1433                                 "Found method named {} on interface {}",
1434                                 thisMethodName, thisInterfaceName);
1435                             sdbusplus::message_t m =
1436                                 crow::connections::systemBus->new_method_call(
1437                                     connectionName.c_str(),
1438                                     transaction->path.c_str(),
1439                                     thisInterfaceName,
1440                                     transaction->methodName.c_str());
1441 
1442                             tinyxml2::XMLElement* argumentNode =
1443                                 methodNode->FirstChildElement("arg");
1444 
1445                             std::string returnType;
1446 
1447                             // Find the output type
1448                             while (argumentNode != nullptr)
1449                             {
1450                                 const char* argDirection =
1451                                     argumentNode->Attribute("direction");
1452                                 const char* argType =
1453                                     argumentNode->Attribute("type");
1454                                 if (argDirection != nullptr &&
1455                                     argType != nullptr &&
1456                                     std::string(argDirection) == "out")
1457                                 {
1458                                     returnType = argType;
1459                                     break;
1460                                 }
1461                                 argumentNode =
1462                                     argumentNode->NextSiblingElement("arg");
1463                             }
1464 
1465                             auto argIt = transaction->arguments.begin();
1466 
1467                             argumentNode = methodNode->FirstChildElement("arg");
1468 
1469                             while (argumentNode != nullptr)
1470                             {
1471                                 const char* argDirection =
1472                                     argumentNode->Attribute("direction");
1473                                 const char* argType =
1474                                     argumentNode->Attribute("type");
1475                                 if (argDirection != nullptr &&
1476                                     argType != nullptr &&
1477                                     std::string(argDirection) == "in")
1478                                 {
1479                                     if (argIt == transaction->arguments.end())
1480                                     {
1481                                         transaction->setErrorStatus(
1482                                             "Invalid method args");
1483                                         return;
1484                                     }
1485                                     if (convertJsonToDbus(m.get(),
1486                                                           std::string(argType),
1487                                                           *argIt) < 0)
1488                                     {
1489                                         transaction->setErrorStatus(
1490                                             "Invalid method arg type");
1491                                         return;
1492                                     }
1493 
1494                                     argIt++;
1495                                 }
1496                                 argumentNode =
1497                                     argumentNode->NextSiblingElement("arg");
1498                             }
1499 
1500                             crow::connections::systemBus->async_send(
1501                                 m, [transaction, returnType](
1502                                        const boost::system::error_code& ec2,
1503                                        sdbusplus::message_t& m2) {
1504                                     if (ec2)
1505                                     {
1506                                         transaction->methodFailed = true;
1507                                         const sd_bus_error* e = m2.get_error();
1508 
1509                                         if (e != nullptr)
1510                                         {
1511                                             setErrorResponse(
1512                                                 transaction->asyncResp->res,
1513                                                 boost::beast::http::status::
1514                                                     bad_request,
1515                                                 e->name, e->message);
1516                                         }
1517                                         else
1518                                         {
1519                                             setErrorResponse(
1520                                                 transaction->asyncResp->res,
1521                                                 boost::beast::http::status::
1522                                                     bad_request,
1523                                                 "Method call failed",
1524                                                 methodFailedMsg);
1525                                         }
1526                                         return;
1527                                     }
1528                                     transaction->methodPassed = true;
1529 
1530                                     handleMethodResponse(transaction, m2,
1531                                                          returnType);
1532                                 });
1533                             break;
1534                         }
1535                         methodNode = methodNode->NextSiblingElement("method");
1536                     }
1537                 }
1538                 interfaceNode = interfaceNode->NextSiblingElement("interface");
1539             }
1540         },
1541         connectionName, transaction->path,
1542         "org.freedesktop.DBus.Introspectable", "Introspect");
1543 }
1544 
handleAction(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & objectPath,const std::string & methodName)1545 inline void handleAction(const crow::Request& req,
1546                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1547                          const std::string& objectPath,
1548                          const std::string& methodName)
1549 {
1550     BMCWEB_LOG_DEBUG("handleAction on path: {} and method {}", objectPath,
1551                      methodName);
1552     nlohmann::json requestDbusData;
1553 
1554     JsonParseResult ret = parseRequestAsJson(req, requestDbusData);
1555     if (ret == JsonParseResult::BadContentType)
1556     {
1557         setErrorResponse(asyncResp->res,
1558                          boost::beast::http::status::unsupported_media_type,
1559                          invalidContentType, unsupportedMediaMsg);
1560         return;
1561     }
1562     if (ret != JsonParseResult::Success)
1563     {
1564         setErrorResponse(asyncResp->res,
1565                          boost::beast::http::status::bad_request, noJsonDesc,
1566                          badReqMsg);
1567         return;
1568     }
1569     nlohmann::json::object_t* obj =
1570         requestDbusData.get_ptr<nlohmann::json::object_t*>();
1571     if (obj == nullptr)
1572     {
1573         setErrorResponse(asyncResp->res,
1574                          boost::beast::http::status::bad_request, noJsonDesc,
1575                          badReqMsg);
1576         return;
1577     }
1578     nlohmann::json::object_t::iterator data = obj->find("data");
1579     if (data == obj->end())
1580     {
1581         setErrorResponse(asyncResp->res,
1582                          boost::beast::http::status::bad_request, noJsonDesc,
1583                          badReqMsg);
1584         return;
1585     }
1586 
1587     if (!data->second.is_array())
1588     {
1589         setErrorResponse(asyncResp->res,
1590                          boost::beast::http::status::bad_request, noJsonDesc,
1591                          badReqMsg);
1592         return;
1593     }
1594     auto transaction = std::make_shared<InProgressActionData>(asyncResp);
1595 
1596     transaction->path = objectPath;
1597     transaction->methodName = methodName;
1598     transaction->arguments = std::move(*data);
1599     dbus::utility::getDbusObject(
1600         objectPath, {},
1601         [transaction](
1602             const boost::system::error_code& ec,
1603             const std::vector<std::pair<std::string, std::vector<std::string>>>&
1604                 interfaceNames) {
1605             if (ec || interfaceNames.empty())
1606             {
1607                 BMCWEB_LOG_ERROR("Can't find object");
1608                 setErrorResponse(transaction->asyncResp->res,
1609                                  boost::beast::http::status::not_found,
1610                                  notFoundDesc, notFoundMsg);
1611                 return;
1612             }
1613 
1614             BMCWEB_LOG_DEBUG("GetObject returned {} object(s)",
1615                              interfaceNames.size());
1616 
1617             for (const std::pair<std::string, std::vector<std::string>>&
1618                      object : interfaceNames)
1619             {
1620                 findActionOnInterface(transaction, object.first);
1621             }
1622         });
1623 }
1624 
handleDelete(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & objectPath)1625 inline void handleDelete(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1626                          const std::string& objectPath)
1627 {
1628     BMCWEB_LOG_DEBUG("handleDelete on path: {}", objectPath);
1629 
1630     dbus::utility::getDbusObject(
1631         objectPath, {},
1632         [asyncResp, objectPath](
1633             const boost::system::error_code& ec,
1634             const std::vector<std::pair<std::string, std::vector<std::string>>>&
1635                 interfaceNames) {
1636             if (ec || interfaceNames.empty())
1637             {
1638                 BMCWEB_LOG_ERROR("Can't find object");
1639                 setErrorResponse(asyncResp->res,
1640                                  boost::beast::http::status::method_not_allowed,
1641                                  methodNotAllowedDesc, methodNotAllowedMsg);
1642                 return;
1643             }
1644 
1645             auto transaction =
1646                 std::make_shared<InProgressActionData>(asyncResp);
1647             transaction->path = objectPath;
1648             transaction->methodName = "Delete";
1649             transaction->interfaceName = "xyz.openbmc_project.Object.Delete";
1650 
1651             for (const std::pair<std::string, std::vector<std::string>>&
1652                      object : interfaceNames)
1653             {
1654                 findActionOnInterface(transaction, object.first);
1655             }
1656         });
1657 }
1658 
handleList(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & objectPath,int32_t depth=0)1659 inline void handleList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1660                        const std::string& objectPath, int32_t depth = 0)
1661 {
1662     dbus::utility::getSubTreePaths(
1663         objectPath, depth, {},
1664         [asyncResp](
1665             const boost::system::error_code& ec,
1666             const dbus::utility::MapperGetSubTreePathsResponse& objectPaths) {
1667             if (ec)
1668             {
1669                 setErrorResponse(asyncResp->res,
1670                                  boost::beast::http::status::not_found,
1671                                  notFoundDesc, notFoundMsg);
1672             }
1673             else
1674             {
1675                 asyncResp->res.jsonValue["status"] = "ok";
1676                 asyncResp->res.jsonValue["message"] = "200 OK";
1677                 asyncResp->res.jsonValue["data"] = objectPaths;
1678             }
1679         });
1680 }
1681 
handleEnumerate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & objectPath)1682 inline void handleEnumerate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1683                             const std::string& objectPath)
1684 {
1685     BMCWEB_LOG_DEBUG("Doing enumerate on {}", objectPath);
1686 
1687     asyncResp->res.jsonValue["message"] = "200 OK";
1688     asyncResp->res.jsonValue["status"] = "ok";
1689     asyncResp->res.jsonValue["data"] = nlohmann::json::object();
1690 
1691     dbus::utility::getSubTree(
1692         objectPath, 0, {},
1693         [objectPath, asyncResp](
1694             const boost::system::error_code& ec,
1695             const dbus::utility::MapperGetSubTreeResponse& objectNames) {
1696             auto transaction = std::make_shared<InProgressEnumerateData>(
1697                 objectPath, asyncResp);
1698 
1699             transaction->subtree =
1700                 std::make_shared<dbus::utility::MapperGetSubTreeResponse>(
1701                     objectNames);
1702 
1703             if (ec)
1704             {
1705                 BMCWEB_LOG_ERROR("GetSubTree failed on {}",
1706                                  transaction->objectPath);
1707                 setErrorResponse(transaction->asyncResp->res,
1708                                  boost::beast::http::status::not_found,
1709                                  notFoundDesc, notFoundMsg);
1710                 return;
1711             }
1712 
1713             // Add the data for the path passed in to the results
1714             // as if GetSubTree returned it, and continue on enumerating
1715             getObjectAndEnumerate(transaction);
1716         });
1717 }
1718 
handleGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string & objectPath,std::string & destProperty)1719 inline void handleGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1720                       std::string& objectPath, std::string& destProperty)
1721 {
1722     BMCWEB_LOG_DEBUG("handleGet: {} prop:{}", objectPath, destProperty);
1723     std::shared_ptr<std::string> propertyName =
1724         std::make_shared<std::string>(std::move(destProperty));
1725 
1726     std::shared_ptr<std::string> path =
1727         std::make_shared<std::string>(std::move(objectPath));
1728 
1729     dbus::utility::getDbusObject(
1730         *path, {},
1731         [asyncResp, path,
1732          propertyName](const boost::system::error_code& ec,
1733                        const dbus::utility::MapperGetObject& objectNames) {
1734             if (ec || objectNames.empty())
1735             {
1736                 setErrorResponse(asyncResp->res,
1737                                  boost::beast::http::status::not_found,
1738                                  notFoundDesc, notFoundMsg);
1739                 return;
1740             }
1741             std::shared_ptr<nlohmann::json> response =
1742                 std::make_shared<nlohmann::json>(nlohmann::json::object());
1743             for (const std::pair<std::string, std::vector<std::string>>&
1744                      connection : objectNames)
1745             {
1746                 const std::vector<std::string>& interfaceNames =
1747                     connection.second;
1748 
1749                 if (interfaceNames.empty())
1750                 {
1751                     // mapper allows empty interfaces in case an
1752                     // object does not implement any interface.
1753                     continue;
1754                 }
1755 
1756                 for (const std::string& interface : interfaceNames)
1757                 {
1758                     sdbusplus::message_t m =
1759                         crow::connections::systemBus->new_method_call(
1760                             connection.first.c_str(), path->c_str(),
1761                             "org.freedesktop.DBus.Properties", "GetAll");
1762                     m.append(interface);
1763                     crow::connections::systemBus->async_send(
1764                         m, [asyncResp, response,
1765                             propertyName](const boost::system::error_code& ec2,
1766                                           sdbusplus::message_t& msg) {
1767                             if (ec2)
1768                             {
1769                                 BMCWEB_LOG_ERROR("Bad dbus request error: {}",
1770                                                  ec2);
1771                             }
1772                             else
1773                             {
1774                                 nlohmann::json properties;
1775                                 int r =
1776                                     convertDBusToJSON("a{sv}", msg, properties);
1777                                 if (r < 0)
1778                                 {
1779                                     BMCWEB_LOG_ERROR(
1780                                         "convertDBusToJSON failed");
1781                                 }
1782                                 else
1783                                 {
1784                                     nlohmann::json::object_t* obj =
1785                                         properties.get_ptr<
1786                                             nlohmann::json::object_t*>();
1787                                     if (obj == nullptr)
1788                                     {
1789                                         return;
1790                                     }
1791                                     for (auto& prop : *obj)
1792                                     {
1793                                         // if property name is empty, or
1794                                         // matches our search query, add it
1795                                         // to the response json
1796 
1797                                         if (propertyName->empty())
1798                                         {
1799                                             (*response)[prop.first] =
1800                                                 std::move(prop.second);
1801                                         }
1802                                         else if (prop.first == *propertyName)
1803                                         {
1804                                             *response = std::move(prop.second);
1805                                         }
1806                                     }
1807                                 }
1808                             }
1809                             if (response.use_count() == 1)
1810                             {
1811                                 if (!propertyName->empty() && response->empty())
1812                                 {
1813                                     setErrorResponse(
1814                                         asyncResp->res,
1815                                         boost::beast::http::status::not_found,
1816                                         propNotFoundDesc, notFoundMsg);
1817                                 }
1818                                 else
1819                                 {
1820                                     asyncResp->res.jsonValue["status"] = "ok";
1821                                     asyncResp->res.jsonValue["message"] =
1822                                         "200 OK";
1823                                     asyncResp->res.jsonValue["data"] =
1824                                         *response;
1825                                 }
1826                             }
1827                         });
1828                 }
1829             }
1830         });
1831 }
1832 
1833 struct AsyncPutRequest
1834 {
AsyncPutRequestcrow::openbmc_mapper::AsyncPutRequest1835     explicit AsyncPutRequest(const std::shared_ptr<bmcweb::AsyncResp>& resIn) :
1836         asyncResp(resIn)
1837     {}
~AsyncPutRequestcrow::openbmc_mapper::AsyncPutRequest1838     ~AsyncPutRequest()
1839     {
1840         if (asyncResp->res.jsonValue.empty())
1841         {
1842             setErrorResponse(asyncResp->res,
1843                              boost::beast::http::status::forbidden,
1844                              forbiddenMsg, forbiddenPropDesc);
1845         }
1846     }
1847 
1848     AsyncPutRequest(const AsyncPutRequest&) = delete;
1849     AsyncPutRequest(AsyncPutRequest&&) = delete;
1850     AsyncPutRequest& operator=(const AsyncPutRequest&) = delete;
1851     AsyncPutRequest& operator=(AsyncPutRequest&&) = delete;
1852 
setErrorStatuscrow::openbmc_mapper::AsyncPutRequest1853     void setErrorStatus(const std::string& desc)
1854     {
1855         setErrorResponse(asyncResp->res,
1856                          boost::beast::http::status::internal_server_error,
1857                          desc, badReqMsg);
1858     }
1859 
1860     const std::shared_ptr<bmcweb::AsyncResp> asyncResp;
1861     std::string objectPath;
1862     std::string propertyName;
1863     nlohmann::json propertyValue;
1864 };
1865 
handlePut(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & objectPath,const std::string & destProperty)1866 inline void handlePut(const crow::Request& req,
1867                       const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1868                       const std::string& objectPath,
1869                       const std::string& destProperty)
1870 {
1871     if (destProperty.empty())
1872     {
1873         setErrorResponse(asyncResp->res, boost::beast::http::status::forbidden,
1874                          forbiddenResDesc, forbiddenMsg);
1875         return;
1876     }
1877     nlohmann::json requestDbusData;
1878 
1879     JsonParseResult ret = parseRequestAsJson(req, requestDbusData);
1880     if (ret == JsonParseResult::BadContentType)
1881     {
1882         setErrorResponse(asyncResp->res,
1883                          boost::beast::http::status::unsupported_media_type,
1884                          invalidContentType, unsupportedMediaMsg);
1885         return;
1886     }
1887 
1888     if (ret != JsonParseResult::Success)
1889     {
1890         setErrorResponse(asyncResp->res,
1891                          boost::beast::http::status::bad_request, noJsonDesc,
1892                          badReqMsg);
1893         return;
1894     }
1895 
1896     nlohmann::json::object_t* obj =
1897         requestDbusData.get_ptr<nlohmann::json::object_t*>();
1898     if (obj == nullptr)
1899     {
1900         setErrorResponse(asyncResp->res,
1901                          boost::beast::http::status::bad_request, noJsonDesc,
1902                          badReqMsg);
1903         return;
1904     }
1905     auto propertyIt = obj->find("data");
1906     if (propertyIt == obj->end())
1907     {
1908         setErrorResponse(asyncResp->res,
1909                          boost::beast::http::status::bad_request, noJsonDesc,
1910                          badReqMsg);
1911         return;
1912     }
1913     const nlohmann::json& propertySetValue = *propertyIt;
1914     auto transaction = std::make_shared<AsyncPutRequest>(asyncResp);
1915     transaction->objectPath = objectPath;
1916     transaction->propertyName = destProperty;
1917     transaction->propertyValue = propertySetValue;
1918 
1919     dbus::utility::getDbusObject(
1920         transaction->objectPath, {},
1921         [transaction](const boost::system::error_code& ec2,
1922                       const dbus::utility::MapperGetObject& objectNames) {
1923             if (!ec2 && objectNames.empty())
1924             {
1925                 setErrorResponse(transaction->asyncResp->res,
1926                                  boost::beast::http::status::not_found,
1927                                  propNotFoundDesc, notFoundMsg);
1928                 return;
1929             }
1930 
1931             for (const std::pair<std::string, std::vector<std::string>>&
1932                      connection : objectNames)
1933             {
1934                 const std::string& connectionName = connection.first;
1935 
1936                 dbus::utility::async_method_call(
1937                     [connectionName{std::string(connectionName)},
1938                      transaction](const boost::system::error_code& ec3,
1939                                   const std::string& introspectXml) {
1940                         if (ec3)
1941                         {
1942                             BMCWEB_LOG_ERROR(
1943                                 "Introspect call failed with error: {} on process: {}",
1944                                 ec3.message(), connectionName);
1945                             transaction->setErrorStatus("Unexpected Error");
1946                             return;
1947                         }
1948                         tinyxml2::XMLDocument doc;
1949 
1950                         doc.Parse(introspectXml.c_str());
1951                         tinyxml2::XMLNode* pRoot =
1952                             doc.FirstChildElement("node");
1953                         if (pRoot == nullptr)
1954                         {
1955                             BMCWEB_LOG_ERROR("XML document failed to parse: {}",
1956                                              introspectXml);
1957                             transaction->setErrorStatus("Unexpected Error");
1958                             return;
1959                         }
1960                         tinyxml2::XMLElement* ifaceNode =
1961                             pRoot->FirstChildElement("interface");
1962                         while (ifaceNode != nullptr)
1963                         {
1964                             const char* interfaceName =
1965                                 ifaceNode->Attribute("name");
1966                             BMCWEB_LOG_DEBUG("found interface {}",
1967                                              interfaceName);
1968                             tinyxml2::XMLElement* propNode =
1969                                 ifaceNode->FirstChildElement("property");
1970                             while (propNode != nullptr)
1971                             {
1972                                 const char* propertyName =
1973                                     propNode->Attribute("name");
1974                                 if (propertyName == nullptr)
1975                                 {
1976                                     BMCWEB_LOG_DEBUG(
1977                                         "Couldn't find name property");
1978                                     continue;
1979                                 }
1980                                 BMCWEB_LOG_DEBUG("Found property {}",
1981                                                  propertyName);
1982                                 if (propertyName == transaction->propertyName)
1983                                 {
1984                                     const char* argType =
1985                                         propNode->Attribute("type");
1986                                     if (argType != nullptr)
1987                                     {
1988                                         sdbusplus::message_t m =
1989                                             crow::connections::systemBus
1990                                                 ->new_method_call(
1991                                                     connectionName.c_str(),
1992                                                     transaction->objectPath
1993                                                         .c_str(),
1994                                                     "org.freedesktop.DBus."
1995                                                     "Properties",
1996                                                     "Set");
1997                                         m.append(interfaceName,
1998                                                  transaction->propertyName);
1999                                         int r = sd_bus_message_open_container(
2000                                             m.get(), SD_BUS_TYPE_VARIANT,
2001                                             argType);
2002                                         if (r < 0)
2003                                         {
2004                                             transaction->setErrorStatus(
2005                                                 "Unexpected Error");
2006                                             return;
2007                                         }
2008                                         r = convertJsonToDbus(
2009                                             m.get(), argType,
2010                                             transaction->propertyValue);
2011                                         if (r < 0)
2012                                         {
2013                                             if (r == -ERANGE)
2014                                             {
2015                                                 transaction->setErrorStatus(
2016                                                     "Provided property value "
2017                                                     "is out of range for the "
2018                                                     "property type");
2019                                             }
2020                                             else
2021                                             {
2022                                                 transaction->setErrorStatus(
2023                                                     "Invalid arg type");
2024                                             }
2025                                             return;
2026                                         }
2027                                         r = sd_bus_message_close_container(
2028                                             m.get());
2029                                         if (r < 0)
2030                                         {
2031                                             transaction->setErrorStatus(
2032                                                 "Unexpected Error");
2033                                             return;
2034                                         }
2035                                         crow::connections::systemBus
2036                                             ->async_send(
2037                                                 m,
2038                                                 [transaction](
2039                                                     const boost::system::
2040                                                         error_code& ec,
2041                                                     sdbusplus::message_t& m2) {
2042                                                     BMCWEB_LOG_DEBUG("sent");
2043                                                     if (ec)
2044                                                     {
2045                                                         const sd_bus_error* e =
2046                                                             m2.get_error();
2047                                                         setErrorResponse(
2048                                                             transaction
2049                                                                 ->asyncResp
2050                                                                 ->res,
2051                                                             boost::beast::http::
2052                                                                 status::
2053                                                                     forbidden,
2054                                                             (e) != nullptr
2055                                                                 ? e->name
2056                                                                 : ec.category()
2057                                                                       .name(),
2058                                                             (e) != nullptr
2059                                                                 ? e->message
2060                                                                 : ec.message());
2061                                                     }
2062                                                     else
2063                                                     {
2064                                                         transaction->asyncResp
2065                                                             ->res.jsonValue
2066                                                                 ["status"] =
2067                                                             "ok";
2068                                                         transaction->asyncResp
2069                                                             ->res.jsonValue
2070                                                                 ["message"] =
2071                                                             "200 OK";
2072                                                         transaction->asyncResp
2073                                                             ->res
2074                                                             .jsonValue["data"] =
2075                                                             nullptr;
2076                                                     }
2077                                                 });
2078                                     }
2079                                 }
2080                                 propNode =
2081                                     propNode->NextSiblingElement("property");
2082                             }
2083                             ifaceNode =
2084                                 ifaceNode->NextSiblingElement("interface");
2085                         }
2086                     },
2087                     connectionName, transaction->objectPath,
2088                     "org.freedesktop.DBus.Introspectable", "Introspect");
2089             }
2090         });
2091 }
2092 
handleDBusUrl(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string & objectPath)2093 inline void handleDBusUrl(const crow::Request& req,
2094                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2095                           std::string& objectPath)
2096 {
2097     // If accessing a single attribute, fill in and update objectPath,
2098     // otherwise leave destProperty blank
2099     std::string destProperty;
2100     const char* attrSeperator = "/attr/";
2101     size_t attrPosition = objectPath.find(attrSeperator);
2102     if (attrPosition != std::string::npos)
2103     {
2104         destProperty = objectPath.substr(attrPosition + strlen(attrSeperator),
2105                                          objectPath.length());
2106         objectPath.resize(attrPosition);
2107     }
2108 
2109     if (req.method() == boost::beast::http::verb::post)
2110     {
2111         constexpr const char* actionSeperator = "/action/";
2112         size_t actionPosition = objectPath.find(actionSeperator);
2113         if (actionPosition != std::string::npos)
2114         {
2115             std::string postProperty =
2116                 objectPath.substr((actionPosition + strlen(actionSeperator)),
2117                                   objectPath.length());
2118             objectPath.resize(actionPosition);
2119             handleAction(req, asyncResp, objectPath, postProperty);
2120             return;
2121         }
2122     }
2123     else if (req.method() == boost::beast::http::verb::get)
2124     {
2125         if (objectPath.ends_with("/enumerate"))
2126         {
2127             objectPath.erase(objectPath.end() - sizeof("enumerate"),
2128                              objectPath.end());
2129             handleEnumerate(asyncResp, objectPath);
2130         }
2131         else if (objectPath.ends_with("/list"))
2132         {
2133             objectPath.erase(objectPath.end() - sizeof("list"),
2134                              objectPath.end());
2135             handleList(asyncResp, objectPath);
2136         }
2137         else
2138         {
2139             // Trim any trailing "/" at the end
2140             if (objectPath.ends_with("/"))
2141             {
2142                 objectPath.pop_back();
2143                 handleList(asyncResp, objectPath, 1);
2144             }
2145             else
2146             {
2147                 handleGet(asyncResp, objectPath, destProperty);
2148             }
2149         }
2150         return;
2151     }
2152     else if (req.method() == boost::beast::http::verb::put)
2153     {
2154         handlePut(req, asyncResp, objectPath, destProperty);
2155         return;
2156     }
2157     else if (req.method() == boost::beast::http::verb::delete_)
2158     {
2159         handleDelete(asyncResp, objectPath);
2160         return;
2161     }
2162 
2163     setErrorResponse(asyncResp->res,
2164                      boost::beast::http::status::method_not_allowed,
2165                      methodNotAllowedDesc, methodNotAllowedMsg);
2166 }
2167 
handleBusSystemPost(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & processName,const std::string & requestedPath)2168 inline void handleBusSystemPost(
2169     const crow::Request& req,
2170     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2171     const std::string& processName, const std::string& requestedPath)
2172 {
2173     std::vector<std::string> strs;
2174 
2175     bmcweb::split(strs, requestedPath, '/');
2176     std::string objectPath;
2177     std::string interfaceName;
2178     std::string methodName;
2179     auto it = strs.begin();
2180     if (it == strs.end())
2181     {
2182         objectPath = "/";
2183     }
2184     while (it != strs.end())
2185     {
2186         // Check if segment contains ".".  If it does, it must be an
2187         // interface
2188         if (it->find(".") != std::string::npos)
2189         {
2190             break;
2191             // This check is necessary as the trailing slash gets
2192             // parsed as part of our <path> specifier above, which
2193             // causes the normal trailing backslash redirector to
2194             // fail.
2195         }
2196         if (!it->empty())
2197         {
2198             objectPath += "/" + *it;
2199         }
2200         it++;
2201     }
2202     if (it != strs.end())
2203     {
2204         interfaceName = *it;
2205         it++;
2206 
2207         // after interface, we might have a method name
2208         if (it != strs.end())
2209         {
2210             methodName = *it;
2211             it++;
2212         }
2213     }
2214     if (it != strs.end())
2215     {
2216         // if there is more levels past the method name, something
2217         // went wrong, return not found
2218         asyncResp->res.result(boost::beast::http::status::not_found);
2219         return;
2220     }
2221     if (interfaceName.empty())
2222     {
2223         dbus::utility::async_method_call(
2224             [asyncResp, processName,
2225              objectPath](const boost::system::error_code& ec,
2226                          const std::string& introspectXml) {
2227                 if (ec)
2228                 {
2229                     BMCWEB_LOG_ERROR(
2230                         "Introspect call failed with error: {} on process: {} path: {}",
2231                         ec.message(), processName, objectPath);
2232                     return;
2233                 }
2234                 tinyxml2::XMLDocument doc;
2235 
2236                 doc.Parse(introspectXml.c_str());
2237                 tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
2238                 if (pRoot == nullptr)
2239                 {
2240                     BMCWEB_LOG_ERROR("XML document failed to parse {} {}",
2241                                      processName, objectPath);
2242                     asyncResp->res.jsonValue["status"] = "XML parse error";
2243                     asyncResp->res.result(
2244                         boost::beast::http::status::internal_server_error);
2245                     return;
2246                 }
2247 
2248                 BMCWEB_LOG_DEBUG("{}", introspectXml);
2249                 asyncResp->res.jsonValue["status"] = "ok";
2250                 asyncResp->res.jsonValue["bus_name"] = processName;
2251                 asyncResp->res.jsonValue["object_path"] = objectPath;
2252 
2253                 nlohmann::json& interfacesArray =
2254                     asyncResp->res.jsonValue["interfaces"];
2255                 interfacesArray = nlohmann::json::array();
2256                 tinyxml2::XMLElement* interface =
2257                     pRoot->FirstChildElement("interface");
2258 
2259                 while (interface != nullptr)
2260                 {
2261                     const char* ifaceName = interface->Attribute("name");
2262                     if (ifaceName != nullptr)
2263                     {
2264                         nlohmann::json::object_t interfaceObj;
2265                         interfaceObj["name"] = ifaceName;
2266                         interfacesArray.emplace_back(std::move(interfaceObj));
2267                     }
2268 
2269                     interface = interface->NextSiblingElement("interface");
2270                 }
2271             },
2272             processName, objectPath, "org.freedesktop.DBus.Introspectable",
2273             "Introspect");
2274     }
2275     else if (methodName.empty())
2276     {
2277         dbus::utility::async_method_call(
2278             [asyncResp, processName, objectPath,
2279              interfaceName](const boost::system::error_code& ec,
2280                             const std::string& introspectXml) {
2281                 if (ec)
2282                 {
2283                     BMCWEB_LOG_ERROR(
2284                         "Introspect call failed with error: {} on process: {} path: {}",
2285                         ec.message(), processName, objectPath);
2286                     return;
2287                 }
2288                 tinyxml2::XMLDocument doc;
2289 
2290                 doc.Parse(introspectXml.data(), introspectXml.size());
2291                 tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
2292                 if (pRoot == nullptr)
2293                 {
2294                     BMCWEB_LOG_ERROR("XML document failed to parse {} {}",
2295                                      processName, objectPath);
2296                     asyncResp->res.result(
2297                         boost::beast::http::status::internal_server_error);
2298                     return;
2299                 }
2300 
2301                 asyncResp->res.jsonValue["status"] = "ok";
2302                 asyncResp->res.jsonValue["bus_name"] = processName;
2303                 asyncResp->res.jsonValue["interface"] = interfaceName;
2304                 asyncResp->res.jsonValue["object_path"] = objectPath;
2305 
2306                 nlohmann::json& methodsArray =
2307                     asyncResp->res.jsonValue["methods"];
2308                 methodsArray = nlohmann::json::array();
2309 
2310                 nlohmann::json& signalsArray =
2311                     asyncResp->res.jsonValue["signals"];
2312                 signalsArray = nlohmann::json::array();
2313 
2314                 nlohmann::json& propertiesObj =
2315                     asyncResp->res.jsonValue["properties"];
2316                 propertiesObj = nlohmann::json::object();
2317 
2318                 // if we know we're the only call, build the
2319                 // json directly
2320                 tinyxml2::XMLElement* interface =
2321                     pRoot->FirstChildElement("interface");
2322                 while (interface != nullptr)
2323                 {
2324                     const char* ifaceName = interface->Attribute("name");
2325 
2326                     if (ifaceName != nullptr && ifaceName == interfaceName)
2327                     {
2328                         break;
2329                     }
2330 
2331                     interface = interface->NextSiblingElement("interface");
2332                 }
2333                 if (interface == nullptr)
2334                 {
2335                     // if we got to the end of the list and
2336                     // never found a match, throw 404
2337                     asyncResp->res.result(
2338                         boost::beast::http::status::not_found);
2339                     return;
2340                 }
2341 
2342                 tinyxml2::XMLElement* methods =
2343                     interface->FirstChildElement("method");
2344                 while (methods != nullptr)
2345                 {
2346                     nlohmann::json argsArray = nlohmann::json::array();
2347                     tinyxml2::XMLElement* arg =
2348                         methods->FirstChildElement("arg");
2349                     while (arg != nullptr)
2350                     {
2351                         nlohmann::json thisArg;
2352                         for (const char* fieldName : std::array<const char*, 3>{
2353                                  "name", "direction", "type"})
2354                         {
2355                             const char* fieldValue = arg->Attribute(fieldName);
2356                             if (fieldValue != nullptr)
2357                             {
2358                                 thisArg[fieldName] = fieldValue;
2359                             }
2360                         }
2361                         argsArray.emplace_back(std::move(thisArg));
2362                         arg = arg->NextSiblingElement("arg");
2363                     }
2364 
2365                     const char* name = methods->Attribute("name");
2366                     if (name != nullptr)
2367                     {
2368                         std::string uri;
2369                         uri.reserve(14 + processName.size() +
2370                                     objectPath.size() + interfaceName.size() +
2371                                     strlen(name));
2372                         uri += "/bus/system/";
2373                         uri += processName;
2374                         uri += objectPath;
2375                         uri += "/";
2376                         uri += interfaceName;
2377                         uri += "/";
2378                         uri += name;
2379 
2380                         nlohmann::json::object_t object;
2381                         object["name"] = name;
2382                         object["uri"] = std::move(uri);
2383                         object["args"] = argsArray;
2384 
2385                         methodsArray.emplace_back(std::move(object));
2386                     }
2387                     methods = methods->NextSiblingElement("method");
2388                 }
2389                 tinyxml2::XMLElement* signals =
2390                     interface->FirstChildElement("signal");
2391                 while (signals != nullptr)
2392                 {
2393                     nlohmann::json argsArray = nlohmann::json::array();
2394 
2395                     tinyxml2::XMLElement* arg =
2396                         signals->FirstChildElement("arg");
2397                     while (arg != nullptr)
2398                     {
2399                         const char* name = arg->Attribute("name");
2400                         const char* type = arg->Attribute("type");
2401                         if (name != nullptr && type != nullptr)
2402                         {
2403                             nlohmann::json::object_t params;
2404                             params["name"] = name;
2405                             params["type"] = type;
2406                             argsArray.push_back(std::move(params));
2407                         }
2408                         arg = arg->NextSiblingElement("arg");
2409                     }
2410                     const char* name = signals->Attribute("name");
2411                     if (name != nullptr)
2412                     {
2413                         nlohmann::json::object_t object;
2414                         object["name"] = name;
2415                         object["args"] = argsArray;
2416                         signalsArray.emplace_back(std::move(object));
2417                     }
2418 
2419                     signals = signals->NextSiblingElement("signal");
2420                 }
2421 
2422                 tinyxml2::XMLElement* property =
2423                     interface->FirstChildElement("property");
2424                 while (property != nullptr)
2425                 {
2426                     const char* name = property->Attribute("name");
2427                     const char* type = property->Attribute("type");
2428                     if (type != nullptr && name != nullptr)
2429                     {
2430                         sdbusplus::message_t m =
2431                             crow::connections::systemBus->new_method_call(
2432                                 processName.c_str(), objectPath.c_str(),
2433                                 "org.freedesktop."
2434                                 "DBus."
2435                                 "Properties",
2436                                 "Get");
2437                         m.append(interfaceName, name);
2438                         nlohmann::json& propertyItem = propertiesObj[name];
2439                         crow::connections::systemBus->async_send(
2440                             m, [&propertyItem,
2441                                 asyncResp](const boost::system::error_code& ec2,
2442                                            sdbusplus::message_t& msg) {
2443                                 if (ec2)
2444                                 {
2445                                     return;
2446                                 }
2447 
2448                                 int r =
2449                                     convertDBusToJSON("v", msg, propertyItem);
2450                                 if (r < 0)
2451                                 {
2452                                     BMCWEB_LOG_ERROR(
2453                                         "Couldn't convert vector to json");
2454                                 }
2455                             });
2456                     }
2457                     property = property->NextSiblingElement("property");
2458                 }
2459             },
2460             processName, objectPath, "org.freedesktop.DBus.Introspectable",
2461             "Introspect");
2462     }
2463     else
2464     {
2465         if (req.method() != boost::beast::http::verb::post)
2466         {
2467             asyncResp->res.result(boost::beast::http::status::not_found);
2468             return;
2469         }
2470 
2471         nlohmann::json requestDbusData;
2472         JsonParseResult ret = parseRequestAsJson(req, requestDbusData);
2473         if (ret == JsonParseResult::BadContentType)
2474         {
2475             setErrorResponse(asyncResp->res,
2476                              boost::beast::http::status::unsupported_media_type,
2477                              invalidContentType, unsupportedMediaMsg);
2478             return;
2479         }
2480         if (ret != JsonParseResult::Success)
2481         {
2482             setErrorResponse(asyncResp->res,
2483                              boost::beast::http::status::bad_request,
2484                              noJsonDesc, badReqMsg);
2485             return;
2486         }
2487 
2488         if (!requestDbusData.is_array())
2489         {
2490             asyncResp->res.result(boost::beast::http::status::bad_request);
2491             return;
2492         }
2493         auto transaction = std::make_shared<InProgressActionData>(asyncResp);
2494 
2495         transaction->path = objectPath;
2496         transaction->methodName = methodName;
2497         transaction->arguments = std::move(requestDbusData);
2498 
2499         findActionOnInterface(transaction, processName);
2500     }
2501 }
2502 
requestRoutes(App & app)2503 inline void requestRoutes(App& app)
2504 {
2505     BMCWEB_ROUTE(app, "/bus/")
2506         .privileges({{"Login"}})
2507         .methods(boost::beast::http::verb::get)(
2508             [](const crow::Request&,
2509                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2510                 nlohmann::json::array_t buses;
2511                 nlohmann::json& bus = buses.emplace_back();
2512                 bus["name"] = "system";
2513                 asyncResp->res.jsonValue["busses"] = std::move(buses);
2514                 asyncResp->res.jsonValue["status"] = "ok";
2515             });
2516 
2517     BMCWEB_ROUTE(app, "/bus/system/")
2518         .privileges({{"Login"}})
2519         .methods(boost::beast::http::verb::get)(
2520             [](const crow::Request&,
2521                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2522                 auto myCallback = [asyncResp](
2523                                       const boost::system::error_code& ec,
2524                                       std::vector<std::string>& names) {
2525                     if (ec)
2526                     {
2527                         BMCWEB_LOG_ERROR("Dbus call failed with code {}", ec);
2528                         asyncResp->res.result(
2529                             boost::beast::http::status::internal_server_error);
2530                     }
2531                     else
2532                     {
2533                         std::ranges::sort(names);
2534                         asyncResp->res.jsonValue["status"] = "ok";
2535                         auto& objectsSub = asyncResp->res.jsonValue["objects"];
2536                         for (const auto& name : names)
2537                         {
2538                             nlohmann::json::object_t object;
2539                             object["name"] = name;
2540                             objectsSub.emplace_back(std::move(object));
2541                         }
2542                     }
2543                 };
2544                 dbus::utility::async_method_call(
2545                     std::move(myCallback), "org.freedesktop.DBus", "/",
2546                     "org.freedesktop.DBus", "ListNames");
2547             });
2548 
2549     BMCWEB_ROUTE(app, "/list/")
2550         .privileges({{"Login"}})
2551         .methods(boost::beast::http::verb::get)(
2552             [](const crow::Request&,
2553                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2554                 handleList(asyncResp, "/");
2555             });
2556 
2557     BMCWEB_ROUTE(app, "/xyz/<path>")
2558         .privileges({{"Login"}})
2559         .methods(boost::beast::http::verb::get)(
2560             [](const crow::Request& req,
2561                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2562                const std::string& path) {
2563                 std::string objectPath = "/xyz/" + path;
2564                 handleDBusUrl(req, asyncResp, objectPath);
2565             });
2566 
2567     BMCWEB_ROUTE(app, "/xyz/<path>")
2568         .privileges({{"ConfigureComponents", "ConfigureManager"}})
2569         .methods(boost::beast::http::verb::put, boost::beast::http::verb::post,
2570                  boost::beast::http::verb::delete_)(
2571             [](const crow::Request& req,
2572                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2573                const std::string& path) {
2574                 std::string objectPath = "/xyz/" + path;
2575                 handleDBusUrl(req, asyncResp, objectPath);
2576             });
2577 
2578     BMCWEB_ROUTE(app, "/org/<path>")
2579         .privileges({{"Login"}})
2580         .methods(boost::beast::http::verb::get)(
2581             [](const crow::Request& req,
2582                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2583                const std::string& path) {
2584                 std::string objectPath = "/org/" + path;
2585                 handleDBusUrl(req, asyncResp, objectPath);
2586             });
2587 
2588     BMCWEB_ROUTE(app, "/org/<path>")
2589         .privileges({{"ConfigureComponents", "ConfigureManager"}})
2590         .methods(boost::beast::http::verb::put, boost::beast::http::verb::post,
2591                  boost::beast::http::verb::delete_)(
2592             [](const crow::Request& req,
2593                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2594                const std::string& path) {
2595                 std::string objectPath = "/org/" + path;
2596                 handleDBusUrl(req, asyncResp, objectPath);
2597             });
2598 
2599     BMCWEB_ROUTE(app, "/download/dump/<str>/")
2600         .privileges({{"ConfigureManager"}})
2601         .methods(boost::beast::http::verb::get)(
2602             [](const crow::Request&,
2603                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2604                const std::string& dumpId) {
2605                 if (!validateFilename(dumpId))
2606                 {
2607                     asyncResp->res.result(
2608                         boost::beast::http::status::bad_request);
2609                     return;
2610                 }
2611                 std::filesystem::path loc(
2612                     "/var/lib/phosphor-debug-collector/dumps");
2613 
2614                 loc /= dumpId;
2615 
2616                 if (!std::filesystem::exists(loc) ||
2617                     !std::filesystem::is_directory(loc))
2618                 {
2619                     BMCWEB_LOG_ERROR("{}Not found", loc.string());
2620                     asyncResp->res.result(
2621                         boost::beast::http::status::not_found);
2622                     return;
2623                 }
2624                 std::filesystem::directory_iterator files(loc);
2625 
2626                 for (const auto& file : files)
2627                 {
2628                     if (asyncResp->res.openFile(file) !=
2629                         crow::OpenCode::Success)
2630                     {
2631                         continue;
2632                     }
2633 
2634                     asyncResp->res.addHeader(
2635                         boost::beast::http::field::content_type,
2636                         "application/octet-stream");
2637 
2638                     // Assuming only one dump file will be present in the dump
2639                     // id directory
2640                     std::string dumpFileName = file.path().filename().string();
2641 
2642                     // Filename should be in alphanumeric, dot and underscore
2643                     // Its based on phosphor-debug-collector application
2644                     // dumpfile format
2645                     static std::regex dumpFileRegex("[a-zA-Z0-9\\._]+");
2646                     if (!std::regex_match(dumpFileName, dumpFileRegex))
2647                     {
2648                         BMCWEB_LOG_ERROR("Invalid dump filename {}",
2649                                          dumpFileName);
2650                         asyncResp->res.result(
2651                             boost::beast::http::status::not_found);
2652                         return;
2653                     }
2654                     std::string contentDispositionParam =
2655                         "attachment; filename=\"" + dumpFileName + "\"";
2656 
2657                     asyncResp->res.addHeader(
2658                         boost::beast::http::field::content_disposition,
2659                         contentDispositionParam);
2660 
2661                     return;
2662                 }
2663                 asyncResp->res.result(boost::beast::http::status::not_found);
2664                 return;
2665             });
2666 
2667     BMCWEB_ROUTE(app, "/bus/system/<str>/")
2668         .privileges({{"Login"}})
2669 
2670         .methods(boost::beast::http::verb::get)(
2671             [](const crow::Request&,
2672                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2673                const std::string& connection) {
2674                 introspectObjects(connection, "/", asyncResp);
2675             });
2676 
2677     BMCWEB_ROUTE(app, "/bus/system/<str>/<path>")
2678         .privileges({{"ConfigureComponents", "ConfigureManager"}})
2679         .methods(boost::beast::http::verb::get,
2680                  boost::beast::http::verb::post)(handleBusSystemPost);
2681 }
2682 } // namespace openbmc_mapper
2683 } // namespace crow
2684