1 #include <crow/app.h>
2 
3 #include <tinyxml2.h>
4 #include <dbus/connection.hpp>
5 #include <dbus/endpoint.hpp>
6 #include <dbus/filter.hpp>
7 #include <dbus/match.hpp>
8 #include <dbus/message.hpp>
9 #include <dbus_singleton.hpp>
10 #include <boost/container/flat_set.hpp>
11 
12 namespace crow {
13 namespace openbmc_mapper {
14 
15 void introspect_objects(crow::response &res, std::string process_name,
16                         std::string path,
17                         std::shared_ptr<nlohmann::json> transaction) {
18   dbus::endpoint introspect_endpoint(
19       process_name, path, "org.freedesktop.DBus.Introspectable", "Introspect");
20   crow::connections::system_bus->async_method_call(
21       [&, process_name{std::move(process_name)}, object_path{std::move(path)} ](
22           const boost::system::error_code ec,
23           const std::string &introspect_xml) {
24         if (ec) {
25           CROW_LOG_ERROR << "Introspect call failed with error: "
26                          << ec.message() << " on process: " << process_name
27                          << " path: " << object_path << "\n";
28 
29         } else {
30           transaction->push_back({{"path", object_path}});
31 
32           tinyxml2::XMLDocument doc;
33 
34           doc.Parse(introspect_xml.c_str());
35           tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node");
36           if (pRoot == nullptr) {
37             CROW_LOG_ERROR << "XML document failed to parse " << process_name
38                            << " " << path << "\n";
39 
40           } else {
41             tinyxml2::XMLElement *node = pRoot->FirstChildElement("node");
42             while (node != nullptr) {
43               std::string child_path = node->Attribute("name");
44               std::string newpath;
45               if (object_path != "/") {
46                 newpath += object_path;
47               }
48               newpath += "/" + child_path;
49               // introspect the subobjects as well
50               introspect_objects(res, process_name, newpath, transaction);
51 
52               node = node->NextSiblingElement("node");
53             }
54           }
55         }
56         // if we're the last outstanding caller, finish the request
57         if (transaction.use_count() == 1) {
58           res.json_value = {{"status", "ok"},
59                             {"bus_name", process_name},
60                             {"objects", *transaction}};
61           res.end();
62         }
63       },
64       introspect_endpoint);
65 }
66 using ManagedObjectType = std::vector<std::pair<
67     dbus::object_path, boost::container::flat_map<
68                            std::string, boost::container::flat_map<
69                                             std::string, dbus::dbus_variant>>>>;
70 
71 void get_manged_objects_for_enumerate(
72     const std::string &object_name, const std::string &connection_name,
73     crow::response &res, std::shared_ptr<nlohmann::json> transaction) {
74   crow::connections::system_bus->async_method_call(
75       [&res, transaction](const boost::system::error_code ec,
76                           const ManagedObjectType &objects) {
77         if (ec) {
78           CROW_LOG_ERROR << ec;
79         } else {
80           nlohmann::json &data_json = *transaction;
81           for (auto &object_path : objects) {
82             nlohmann::json &object_json = data_json[object_path.first.value];
83             for (const auto &interface : object_path.second) {
84               for (const auto &property : interface.second) {
85                 boost::apply_visitor(
86                     [&](auto &&val) { object_json[property.first] = val; },
87                     property.second);
88               }
89             }
90           }
91         }
92 
93         if (transaction.use_count() == 1) {
94           res.json_value = {{"message", "200 OK"},
95                             {"status", "ok"},
96                             {"data", std::move(*transaction)}};
97           res.end();
98         }
99       },
100       {connection_name, object_name, "org.freedesktop.DBus.ObjectManager",
101        "GetManagedObjects"});
102 }  // namespace openbmc_mapper
103 
104 using GetSubTreeType = std::vector<
105     std::pair<std::string,
106               std::vector<std::pair<std::string, std::vector<std::string>>>>>;
107 
108 void handle_enumerate(crow::response &res, const std::string &object_path) {
109   crow::connections::system_bus->async_method_call(
110       [&res, object_path{std::string(object_path)} ](
111           const boost::system::error_code ec,
112           const GetSubTreeType &object_names) {
113         if (ec) {
114           res.code = 500;
115           res.end();
116           return;
117         }
118 
119         boost::container::flat_set<std::string> connections;
120 
121         for (const auto &object : object_names) {
122           for (const auto &connection : object.second) {
123             connections.insert(connection.first);
124           }
125         }
126 
127         if (connections.size() <= 0) {
128           res.code = 404;
129           res.end();
130           return;
131         }
132         auto transaction =
133             std::make_shared<nlohmann::json>(nlohmann::json::object());
134         for (const std::string &connection : connections) {
135           get_manged_objects_for_enumerate(object_path, connection, res,
136                                            transaction);
137         }
138 
139       },
140       {"xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper",
141        "xyz.openbmc_project.ObjectMapper", "GetSubTree"},
142       object_path, (int32_t)0, std::array<std::string, 0>());
143 }
144 
145 template <typename... Middlewares>
146 void request_routes(Crow<Middlewares...> &app) {
147   CROW_ROUTE(app, "/bus/").methods("GET"_method)([](const crow::request &req) {
148     return nlohmann::json{{"busses", {{{"name", "system"}}}}, {"status", "ok"}};
149 
150   });
151 
152   CROW_ROUTE(app, "/bus/system/")
153       .methods("GET"_method)([](const crow::request &req, crow::response &res) {
154         crow::connections::system_bus->async_method_call(
155             [&](const boost::system::error_code ec,
156                 std::vector<std::string> &names) {
157 
158               if (ec) {
159                 res.code = 500;
160               } else {
161                 std::sort(names.begin(), names.end());
162                 nlohmann::json j{{"status", "ok"}};
163                 auto &objects_sub = j["objects"];
164                 for (auto &name : names) {
165                   objects_sub.push_back({{"name", name}});
166                 }
167                 res.json_value = std::move(j);
168               }
169               res.end();
170             },
171             {"org.freedesktop.DBus", "/", "org.freedesktop.DBus", "ListNames"});
172 
173       });
174 
175   CROW_ROUTE(app, "/list/")
176       .methods("GET"_method)([](const crow::request &req, crow::response &res) {
177         crow::connections::system_bus->async_method_call(
178             [&](const boost::system::error_code ec,
179                 const std::vector<std::string> &object_paths) {
180 
181               if (ec) {
182                 res.code = 500;
183               } else {
184                 res.json_value = {{"status", "ok"},
185                                   {"message", "200 OK"},
186                                   {"data", std::move(object_paths)}};
187               }
188               res.end();
189             },
190             {"xyz.openbmc_project.ObjectMapper",
191              "/xyz/openbmc_project/object_mapper",
192              "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths"},
193             "", static_cast<int32_t>(99), std::array<std::string, 0>());
194       });
195 
196   CROW_ROUTE(app, "/xyz/<path>")
197       .methods("GET"_method,
198                "PUT"_method)([](const crow::request &req, crow::response &res,
199                                 const std::string &path) {
200         std::shared_ptr<nlohmann::json> transaction =
201             std::make_shared<nlohmann::json>(nlohmann::json::object());
202         using GetObjectType =
203             std::vector<std::pair<std::string, std::vector<std::string>>>;
204         std::string object_path;
205         std::string dest_property;
206         std::string property_set_value;
207         size_t attr_position = path.find("/attr/");
208         if (attr_position == path.npos) {
209           object_path = "/xyz/" + path;
210         } else {
211           object_path = "/xyz/" + path.substr(0, attr_position);
212           dest_property =
213               path.substr((attr_position + strlen("/attr/")), path.length());
214           auto request_dbus_data =
215               nlohmann::json::parse(req.body, nullptr, false);
216           if (request_dbus_data.is_discarded()) {
217             res.code = 400;
218             res.end();
219             return;
220           }
221 
222           auto property_value_it = request_dbus_data.find("data");
223           if (property_value_it == request_dbus_data.end()) {
224             res.code = 400;
225             res.end();
226             return;
227           }
228 
229           property_set_value = property_value_it->get<const std::string>();
230           if (property_set_value.empty()) {
231             res.code = 400;
232             res.end();
233             return;
234           }
235         }
236 
237         if (boost::ends_with(object_path, "/enumerate")) {
238           object_path.erase(object_path.end() - 10, object_path.end());
239           handle_enumerate(res, object_path);
240           return;
241         }
242 
243         crow::connections::system_bus->async_method_call(
244             [
245                   &, object_path{std::move(object_path)},
246                   dest_property{std::move(dest_property)},
247                   property_set_value{std::move(property_set_value)}, transaction
248             ](const boost::system::error_code ec,
249               const GetObjectType &object_names) {
250               if (ec) {
251                 res.code = 500;
252                 res.end();
253                 return;
254               }
255               if (object_names.size() != 1) {
256                 res.code = 404;
257                 res.end();
258                 return;
259               }
260               if (req.method == "GET"_method) {
261                 for (auto &interface : object_names[0].second) {
262                   crow::connections::system_bus->async_method_call(
263                       [&](const boost::system::error_code ec,
264                           const std::vector<std::pair<
265                               std::string, dbus::dbus_variant>> &properties) {
266                         if (ec) {
267                           CROW_LOG_ERROR << "Bad dbus request error: " << ec;
268                         } else {
269                           for (auto &property : properties) {
270                             boost::apply_visitor(
271                                 [&](auto val) {
272                                   (*transaction)[property.first] = val;
273                                 },
274                                 property.second);
275                           }
276                         }
277                         if (transaction.use_count() == 1) {
278                           res.json_value = {{"status", "ok"},
279                                             {"message", "200 OK"},
280                                             {"data", *transaction}};
281 
282                           res.end();
283                         }
284                       },
285                       {object_names[0].first, object_path,
286                        "org.freedesktop.DBus.Properties", "GetAll"},
287                       interface);
288                 }
289               } else if (req.method == "PUT"_method) {
290                 for (auto &interface : object_names[0].second) {
291                   crow::connections::system_bus->async_method_call(
292                       [
293                             &, interface{std::move(interface)},
294                             object_names{std::move(object_names)},
295                             object_path{std::move(object_path)},
296                             dest_property{std::move(dest_property)},
297                             property_set_value{std::move(property_set_value)},
298                             transaction
299                       ](const boost::system::error_code ec,
300                         const boost::container::flat_map<
301                             std::string, dbus::dbus_variant> &properties) {
302                         if (ec) {
303                           CROW_LOG_ERROR << "Bad dbus request error: " << ec;
304                         } else {
305                           auto it = properties.find(dest_property);
306                           if (it != properties.end()) {
307                             // find the matched property in the interface
308                             dbus::dbus_variant property_value(
309                                 property_set_value);  // create the dbus
310                                                       // variant for dbus call
311                             crow::connections::system_bus->async_method_call(
312                                 [&](const boost::system::error_code ec) {
313                                   // use the method "Set" to set the property
314                                   // value
315                                   if (ec) {
316                                     CROW_LOG_ERROR << "Bad dbus request error: "
317                                                    << ec;
318                                   }
319                                   // find the matched property and send the
320                                   // response
321                                   *transaction = {{"status", "ok"},
322                                                   {"message", "200 OK"},
323                                                   {"data", nullptr}};
324 
325                                 },
326                                 {object_names[0].first, object_path,
327                                  "org.freedesktop.DBus.Properties", "Set"},
328                                 interface, dest_property, property_value);
329                           }
330                         }
331                         // if we are the last caller, finish the transaction
332                         if (transaction.use_count() == 1) {
333                           // if nobody filled in the property, all calls either
334                           // errored, or failed
335                           if (transaction == nullptr) {
336                             res.code = 403;
337                             res.json_value = {{"status", "error"},
338                                               {"message", "403 Forbidden"},
339                                               {"data",
340                                                {{"message",
341                                                  "The specified property "
342                                                  "cannot be created: " +
343                                                      dest_property}}}};
344 
345                           } else {
346                             res.json_value = *transaction;
347                           }
348 
349                           res.end();
350                           return;
351                         }
352                       },
353                       {object_names[0].first, object_path,
354                        "org.freedesktop.DBus.Properties", "GetAll"},
355                       interface);
356                 }
357               }
358             },
359             {"xyz.openbmc_project.ObjectMapper",
360              "/xyz/openbmc_project/object_mapper",
361              "xyz.openbmc_project.ObjectMapper", "GetObject"},
362             object_path, std::array<std::string, 0>());
363       });
364 
365   CROW_ROUTE(app, "/bus/system/<str>/")
366       .methods("GET"_method)([](const crow::request &req, crow::response &res,
367                                 const std::string &connection) {
368         std::shared_ptr<nlohmann::json> transaction;
369         introspect_objects(res, connection, "/", transaction);
370       });
371 
372   CROW_ROUTE(app, "/bus/system/<str>/<path>")
373       .methods("GET"_method)([](const crow::request &req, crow::response &res,
374                                 const std::string &process_name,
375                                 const std::string &requested_path) {
376 
377         std::vector<std::string> strs;
378         boost::split(strs, requested_path, boost::is_any_of("/"));
379         std::string object_path;
380         std::string interface_name;
381         std::string method_name;
382         auto it = strs.begin();
383         if (it == strs.end()) {
384           object_path = "/";
385         }
386         while (it != strs.end()) {
387           // Check if segment contains ".".  If it does, it must be an
388           // interface
389           if ((*it).find(".") != std::string::npos) {
390             break;
391             // THis check is neccesary as the trailing slash gets parsed as
392             // part of our <path> specifier above, which causes the normal
393             // trailing backslash redirector to fail.
394           } else if (!it->empty()) {
395             object_path += "/" + *it;
396           }
397           it++;
398         }
399         if (it != strs.end()) {
400           interface_name = *it;
401           it++;
402 
403           // after interface, we might have a method name
404           if (it != strs.end()) {
405             method_name = *it;
406             it++;
407           }
408         }
409         if (it != strs.end()) {
410           // if there is more levels past the method name, something went
411           // wrong, throw an error
412           res.code = 404;
413           res.end();
414           return;
415         }
416         dbus::endpoint introspect_endpoint(
417             process_name, object_path, "org.freedesktop.DBus.Introspectable",
418             "Introspect");
419         if (interface_name.empty()) {
420           crow::connections::system_bus->async_method_call(
421               [
422                     &, process_name{std::move(process_name)},
423                     object_path{std::move(object_path)}
424               ](const boost::system::error_code ec,
425                 const std::string &introspect_xml) {
426                 if (ec) {
427                   CROW_LOG_ERROR
428                       << "Introspect call failed with error: " << ec.message()
429                       << " on process: " << process_name
430                       << " path: " << object_path << "\n";
431 
432                 } else {
433                   tinyxml2::XMLDocument doc;
434 
435                   doc.Parse(introspect_xml.c_str());
436                   tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node");
437                   if (pRoot == nullptr) {
438                     CROW_LOG_ERROR << "XML document failed to parse "
439                                    << process_name << " " << object_path
440                                    << "\n";
441                     res.write(nlohmann::json{{"status", "XML parse error"}});
442                     res.code = 500;
443                   } else {
444                     nlohmann::json interfaces_array = nlohmann::json::array();
445                     tinyxml2::XMLElement *interface =
446                         pRoot->FirstChildElement("interface");
447 
448                     while (interface != nullptr) {
449                       std::string iface_name = interface->Attribute("name");
450                       interfaces_array.push_back({{"name", iface_name}});
451 
452                       interface = interface->NextSiblingElement("interface");
453                     }
454                     res.json_value = {{"status", "ok"},
455                                       {"bus_name", process_name},
456                                       {"interfaces", interfaces_array},
457                                       {"object_path", object_path}};
458                   }
459                 }
460                 res.end();
461               },
462               introspect_endpoint);
463         } else {
464           crow::connections::system_bus->async_method_call(
465               [
466                     &, process_name{std::move(process_name)},
467                     interface_name{std::move(interface_name)},
468                     object_path{std::move(object_path)}
469               ](const boost::system::error_code ec,
470                 const std::string &introspect_xml) {
471                 if (ec) {
472                   CROW_LOG_ERROR
473                       << "Introspect call failed with error: " << ec.message()
474                       << " on process: " << process_name
475                       << " path: " << object_path << "\n";
476 
477                 } else {
478                   tinyxml2::XMLDocument doc;
479 
480                   doc.Parse(introspect_xml.c_str());
481                   tinyxml2::XMLNode *pRoot = doc.FirstChildElement("node");
482                   if (pRoot == nullptr) {
483                     CROW_LOG_ERROR << "XML document failed to parse "
484                                    << process_name << " " << object_path
485                                    << "\n";
486                     res.code = 500;
487 
488                   } else {
489                     tinyxml2::XMLElement *node =
490                         pRoot->FirstChildElement("node");
491 
492                     // if we know we're the only call, build the json directly
493                     nlohmann::json methods_array = nlohmann::json::array();
494                     nlohmann::json signals_array = nlohmann::json::array();
495                     tinyxml2::XMLElement *interface =
496                         pRoot->FirstChildElement("interface");
497 
498                     while (interface != nullptr) {
499                       std::string iface_name = interface->Attribute("name");
500 
501                       if (iface_name == interface_name) {
502                         tinyxml2::XMLElement *methods =
503                             interface->FirstChildElement("method");
504                         while (methods != nullptr) {
505                           nlohmann::json args_array = nlohmann::json::array();
506                           tinyxml2::XMLElement *arg =
507                               methods->FirstChildElement("arg");
508                           while (arg != nullptr) {
509                             args_array.push_back(
510                                 {{"name", arg->Attribute("name")},
511                                  {"type", arg->Attribute("type")},
512                                  {"direction", arg->Attribute("direction")}});
513                             arg = arg->NextSiblingElement("arg");
514                           }
515                           methods_array.push_back(
516                               {{"name", methods->Attribute("name")},
517                                {"uri", "/bus/system/" + process_name +
518                                            object_path + "/" + interface_name +
519                                            "/" + methods->Attribute("name")},
520                                {"args", args_array}});
521                           methods = methods->NextSiblingElement("method");
522                         }
523                         tinyxml2::XMLElement *signals =
524                             interface->FirstChildElement("signal");
525                         while (signals != nullptr) {
526                           nlohmann::json args_array = nlohmann::json::array();
527 
528                           tinyxml2::XMLElement *arg =
529                               signals->FirstChildElement("arg");
530                           while (arg != nullptr) {
531                             std::string name = arg->Attribute("name");
532                             std::string type = arg->Attribute("type");
533                             args_array.push_back({
534                                 {"name", name},
535                                 {"type", type},
536                             });
537                             arg = arg->NextSiblingElement("arg");
538                           }
539                           signals_array.push_back(
540                               {{"name", signals->Attribute("name")},
541                                {"args", args_array}});
542                           signals = signals->NextSiblingElement("signal");
543                         }
544 
545                         nlohmann::json j{
546                             {"status", "ok"},
547                             {"bus_name", process_name},
548                             {"interface", interface_name},
549                             {"methods", methods_array},
550                             {"object_path", object_path},
551                             {"properties", nlohmann::json::object()},
552                             {"signals", signals_array}};
553 
554                         res.write(j.dump());
555                         break;
556                       }
557 
558                       interface = interface->NextSiblingElement("interface");
559                     }
560                     if (interface == nullptr) {
561                       // if we got to the end of the list and never found a
562                       // match, throw 404
563                       res.code = 404;
564                     }
565                   }
566                 }
567                 res.end();
568               },
569               introspect_endpoint);
570         }
571 
572       });
573 }
574 }  // namespace openbmc_mapper
575 }  // namespace crow
576