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 nlohmann::json::const_iterator jIt = inputJson.begin();
572
573 for (const std::string& argCode : argTypes)
574 {
575 // If we are decoding multiple objects, grab the pointer to the
576 // iterator, and increment it for the next loop
577 if (argTypes.size() > 1)
578 {
579 if (jIt == inputJson.end())
580 {
581 return -2;
582 }
583 j = &*jIt;
584 jIt++;
585 }
586 const int64_t* intValue = j->get_ptr<const int64_t*>();
587 const std::string* stringValue = j->get_ptr<const std::string*>();
588 const double* doubleValue = j->get_ptr<const double*>();
589 const bool* b = j->get_ptr<const bool*>();
590 int64_t v = 0;
591 double d = 0.0;
592
593 // Do some basic type conversions that make sense. uint can be
594 // converted to int. int and uint can be converted to double
595 if (intValue == nullptr)
596 {
597 const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
598 if (uintValue != nullptr)
599 {
600 v = static_cast<int64_t>(*uintValue);
601 intValue = &v;
602 }
603 }
604 if (doubleValue == nullptr)
605 {
606 const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
607 if (uintValue != nullptr)
608 {
609 d = static_cast<double>(*uintValue);
610 doubleValue = &d;
611 }
612 }
613 if (doubleValue == nullptr)
614 {
615 if (intValue != nullptr)
616 {
617 d = static_cast<double>(*intValue);
618 doubleValue = &d;
619 }
620 }
621
622 if (argCode == "s")
623 {
624 if (stringValue == nullptr)
625 {
626 return -1;
627 }
628 r = sd_bus_message_append_basic(
629 m, argCode[0], static_cast<const void*>(stringValue->data()));
630 if (r < 0)
631 {
632 return r;
633 }
634 }
635 else if (argCode == "i")
636 {
637 if (intValue == nullptr)
638 {
639 return -1;
640 }
641 if ((*intValue < std::numeric_limits<int32_t>::lowest()) ||
642 (*intValue > std::numeric_limits<int32_t>::max()))
643 {
644 return -ERANGE;
645 }
646 int32_t i = static_cast<int32_t>(*intValue);
647 r = sd_bus_message_append_basic(m, argCode[0], &i);
648 if (r < 0)
649 {
650 return r;
651 }
652 }
653 else if (argCode == "b")
654 {
655 // lots of ways bool could be represented here. Try them all
656 int boolInt = 0;
657 if (intValue != nullptr)
658 {
659 if (*intValue == 1)
660 {
661 boolInt = 1;
662 }
663 else if (*intValue == 0)
664 {
665 boolInt = 0;
666 }
667 else
668 {
669 return -ERANGE;
670 }
671 }
672 else if (b != nullptr)
673 {
674 boolInt = *b ? 1 : 0;
675 }
676 else if (stringValue != nullptr)
677 {
678 if (!stringValue->empty())
679 {
680 if (stringValue->front() == 't' ||
681 stringValue->front() == 'T')
682 {
683 boolInt = 1;
684 }
685 }
686 }
687 else
688 {
689 return -1;
690 }
691 r = sd_bus_message_append_basic(m, argCode[0], &boolInt);
692 if (r < 0)
693 {
694 return r;
695 }
696 }
697 else if (argCode == "n")
698 {
699 if (intValue == nullptr)
700 {
701 return -1;
702 }
703 if ((*intValue < std::numeric_limits<int16_t>::lowest()) ||
704 (*intValue > std::numeric_limits<int16_t>::max()))
705 {
706 return -ERANGE;
707 }
708 int16_t n = static_cast<int16_t>(*intValue);
709 r = sd_bus_message_append_basic(m, argCode[0], &n);
710 if (r < 0)
711 {
712 return r;
713 }
714 }
715 else if (argCode == "x")
716 {
717 if (intValue == nullptr)
718 {
719 return -1;
720 }
721 r = sd_bus_message_append_basic(m, argCode[0], intValue);
722 if (r < 0)
723 {
724 return r;
725 }
726 }
727 else if (argCode == "y")
728 {
729 const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
730 if (uintValue == nullptr)
731 {
732 return -1;
733 }
734 if (*uintValue > std::numeric_limits<uint8_t>::max())
735 {
736 return -ERANGE;
737 }
738 uint8_t y = static_cast<uint8_t>(*uintValue);
739 r = sd_bus_message_append_basic(m, argCode[0], &y);
740 }
741 else if (argCode == "q")
742 {
743 const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
744 if (uintValue == nullptr)
745 {
746 return -1;
747 }
748 if (*uintValue > std::numeric_limits<uint16_t>::max())
749 {
750 return -ERANGE;
751 }
752 uint16_t q = static_cast<uint16_t>(*uintValue);
753 r = sd_bus_message_append_basic(m, argCode[0], &q);
754 }
755 else if (argCode == "u")
756 {
757 const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
758 if (uintValue == nullptr)
759 {
760 return -1;
761 }
762 if (*uintValue > std::numeric_limits<uint32_t>::max())
763 {
764 return -ERANGE;
765 }
766 uint32_t u = static_cast<uint32_t>(*uintValue);
767 r = sd_bus_message_append_basic(m, argCode[0], &u);
768 }
769 else if (argCode == "t")
770 {
771 const uint64_t* uintValue = j->get_ptr<const uint64_t*>();
772 if (uintValue == nullptr)
773 {
774 return -1;
775 }
776 r = sd_bus_message_append_basic(m, argCode[0], uintValue);
777 }
778 else if (argCode == "d")
779 {
780 if (doubleValue == nullptr)
781 {
782 return -1;
783 }
784 if ((*doubleValue < std::numeric_limits<double>::lowest()) ||
785 (*doubleValue > std::numeric_limits<double>::max()))
786 {
787 return -ERANGE;
788 }
789 r = sd_bus_message_append_basic(m, argCode[0], doubleValue);
790 if (r < 0)
791 {
792 return r;
793 }
794 }
795 else if (argCode.starts_with("a"))
796 {
797 std::string containedType = argCode.substr(1);
798 r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY,
799 containedType.c_str());
800 if (r < 0)
801 {
802 return r;
803 }
804
805 for (const auto& it : *j)
806 {
807 r = convertJsonToDbus(m, containedType, it);
808 if (r < 0)
809 {
810 return r;
811 }
812 }
813 sd_bus_message_close_container(m);
814 }
815 else if (argCode.starts_with("v"))
816 {
817 std::string containedType = argCode.substr(1);
818 BMCWEB_LOG_DEBUG("variant type: {} appending variant of type: {}",
819 argCode, containedType);
820 r = sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT,
821 containedType.c_str());
822 if (r < 0)
823 {
824 return r;
825 }
826
827 r = convertJsonToDbus(m, containedType, inputJson);
828 if (r < 0)
829 {
830 return r;
831 }
832
833 r = sd_bus_message_close_container(m);
834 if (r < 0)
835 {
836 return r;
837 }
838 }
839 else if (argCode.starts_with("(") && argCode.ends_with(")"))
840 {
841 std::string containedType = argCode.substr(1, argCode.size() - 2);
842 r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT,
843 containedType.c_str());
844 if (r < 0)
845 {
846 return r;
847 }
848
849 nlohmann::json::const_iterator it = j->begin();
850 for (const std::string& argCode2 : dbusArgSplit(containedType))
851 {
852 if (it == j->end())
853 {
854 return -1;
855 }
856 r = convertJsonToDbus(m, argCode2, *it);
857 if (r < 0)
858 {
859 return r;
860 }
861 it++;
862 }
863 r = sd_bus_message_close_container(m);
864 }
865 else if (argCode.starts_with("{") && argCode.ends_with("}"))
866 {
867 std::string containedType = argCode.substr(1, argCode.size() - 2);
868 r = sd_bus_message_open_container(m, SD_BUS_TYPE_DICT_ENTRY,
869 containedType.c_str());
870 if (r < 0)
871 {
872 return r;
873 }
874
875 std::vector<std::string> codes = dbusArgSplit(containedType);
876 if (codes.size() != 2)
877 {
878 return -1;
879 }
880 const std::string& keyType = codes[0];
881 const std::string& valueType = codes[1];
882 const nlohmann::json::object_t* arr =
883 j->get_ptr<const nlohmann::json::object_t*>();
884 if (arr == nullptr)
885 {
886 return -1;
887 }
888 for (const auto& it : *arr)
889 {
890 r = convertJsonToDbus(m, keyType, it.first);
891 if (r < 0)
892 {
893 return r;
894 }
895
896 r = convertJsonToDbus(m, valueType, it.second);
897 if (r < 0)
898 {
899 return r;
900 }
901 }
902 r = sd_bus_message_close_container(m);
903 }
904 else
905 {
906 return -2;
907 }
908 if (r < 0)
909 {
910 return r;
911 }
912
913 if (argTypes.size() > 1)
914 {
915 jIt++;
916 }
917 }
918
919 return r;
920 }
921
922 template <typename T>
readMessageItem(const std::string & typeCode,sdbusplus::message_t & m,nlohmann::json & data)923 int readMessageItem(const std::string& typeCode, sdbusplus::message_t& m,
924 nlohmann::json& data)
925 {
926 T value;
927 // When T == char*, this warning fires. Unclear how to resolve
928 // Given that sd-bus takes a void pointer to a char*, and that's
929 // Not something we can fix.
930 // NOLINTNEXTLINE(bugprone-multi-level-implicit-pointer-conversion)
931 int r = sd_bus_message_read_basic(m.get(), typeCode.front(), &value);
932 if (r < 0)
933 {
934 BMCWEB_LOG_ERROR("sd_bus_message_read_basic on type {} failed!",
935 typeCode);
936 return r;
937 }
938
939 data = value;
940 return 0;
941 }
942
943 int convertDBusToJSON(const std::string& returnType, sdbusplus::message_t& m,
944 nlohmann::json& response);
945
readDictEntryFromMessage(const std::string & typeCode,sdbusplus::message_t & m,nlohmann::json & object)946 inline int readDictEntryFromMessage(const std::string& typeCode,
947 sdbusplus::message_t& m,
948 nlohmann::json& object)
949 {
950 std::vector<std::string> types = dbusArgSplit(typeCode);
951 if (types.size() != 2)
952 {
953 BMCWEB_LOG_ERROR("wrong number contained types in dictionary: {}",
954 types.size());
955 return -1;
956 }
957
958 int r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_DICT_ENTRY,
959 typeCode.c_str());
960 if (r < 0)
961 {
962 BMCWEB_LOG_ERROR("sd_bus_message_enter_container with rc {}", r);
963 return r;
964 }
965
966 nlohmann::json key;
967 r = convertDBusToJSON(types[0], m, key);
968 if (r < 0)
969 {
970 return r;
971 }
972
973 const std::string* keyPtr = key.get_ptr<const std::string*>();
974 if (keyPtr == nullptr)
975 {
976 // json doesn't support non-string keys. If we hit this condition,
977 // convert the result to a string so we can proceed
978 key = key.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
979 keyPtr = key.get_ptr<const std::string*>();
980 // in theory this can't fail now, but lets be paranoid about it
981 // anyway
982 if (keyPtr == nullptr)
983 {
984 return -1;
985 }
986 }
987 nlohmann::json& value = object[*keyPtr];
988
989 r = convertDBusToJSON(types[1], m, value);
990 if (r < 0)
991 {
992 return r;
993 }
994
995 r = sd_bus_message_exit_container(m.get());
996 if (r < 0)
997 {
998 BMCWEB_LOG_ERROR("sd_bus_message_exit_container failed");
999 return r;
1000 }
1001
1002 return 0;
1003 }
1004
readArrayFromMessage(const std::string & typeCode,sdbusplus::message_t & m,nlohmann::json & data)1005 inline int readArrayFromMessage(const std::string& typeCode,
1006 sdbusplus::message_t& m, nlohmann::json& data)
1007 {
1008 if (typeCode.size() < 2)
1009 {
1010 BMCWEB_LOG_ERROR("Type code {} too small for an array", typeCode);
1011 return -1;
1012 }
1013
1014 std::string containedType = typeCode.substr(1);
1015
1016 int r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_ARRAY,
1017 containedType.c_str());
1018 if (r < 0)
1019 {
1020 BMCWEB_LOG_ERROR("sd_bus_message_enter_container failed with rc {}", r);
1021 return r;
1022 }
1023
1024 bool dict = containedType.starts_with("{") && containedType.ends_with("}");
1025
1026 if (dict)
1027 {
1028 // Remove the { }
1029 containedType = containedType.substr(1, containedType.size() - 2);
1030 data = nlohmann::json::object();
1031 }
1032 else
1033 {
1034 data = nlohmann::json::array();
1035 }
1036
1037 while (true)
1038 {
1039 r = sd_bus_message_at_end(m.get(), 0);
1040 if (r < 0)
1041 {
1042 BMCWEB_LOG_ERROR("sd_bus_message_at_end failed");
1043 return r;
1044 }
1045
1046 if (r > 0)
1047 {
1048 break;
1049 }
1050
1051 // Dictionaries are only ever seen in an array
1052 if (dict)
1053 {
1054 r = readDictEntryFromMessage(containedType, m, data);
1055 if (r < 0)
1056 {
1057 return r;
1058 }
1059 }
1060 else
1061 {
1062 data.push_back(nlohmann::json());
1063
1064 r = convertDBusToJSON(containedType, m, data.back());
1065 if (r < 0)
1066 {
1067 return r;
1068 }
1069 }
1070 }
1071
1072 r = sd_bus_message_exit_container(m.get());
1073 if (r < 0)
1074 {
1075 BMCWEB_LOG_ERROR("sd_bus_message_exit_container failed");
1076 return r;
1077 }
1078
1079 return 0;
1080 }
1081
readStructFromMessage(const std::string & typeCode,sdbusplus::message_t & m,nlohmann::json & data)1082 inline int readStructFromMessage(const std::string& typeCode,
1083 sdbusplus::message_t& m, nlohmann::json& data)
1084 {
1085 if (typeCode.size() < 3)
1086 {
1087 BMCWEB_LOG_ERROR("Type code {} too small for a struct", typeCode);
1088 return -1;
1089 }
1090
1091 std::string containedTypes = typeCode.substr(1, typeCode.size() - 2);
1092 std::vector<std::string> types = dbusArgSplit(containedTypes);
1093
1094 int r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_STRUCT,
1095 containedTypes.c_str());
1096 if (r < 0)
1097 {
1098 BMCWEB_LOG_ERROR("sd_bus_message_enter_container failed with rc {}", r);
1099 return r;
1100 }
1101
1102 for (const std::string& type : types)
1103 {
1104 data.push_back(nlohmann::json());
1105 r = convertDBusToJSON(type, m, data.back());
1106 if (r < 0)
1107 {
1108 return r;
1109 }
1110 }
1111
1112 r = sd_bus_message_exit_container(m.get());
1113 if (r < 0)
1114 {
1115 BMCWEB_LOG_ERROR("sd_bus_message_exit_container failed");
1116 return r;
1117 }
1118 return 0;
1119 }
1120
readVariantFromMessage(sdbusplus::message_t & m,nlohmann::json & data)1121 inline int readVariantFromMessage(sdbusplus::message_t& m, nlohmann::json& data)
1122 {
1123 const char* containerType = nullptr;
1124 int r = sd_bus_message_peek_type(m.get(), nullptr, &containerType);
1125 if (r < 0)
1126 {
1127 BMCWEB_LOG_ERROR("sd_bus_message_peek_type failed");
1128 return r;
1129 }
1130
1131 r = sd_bus_message_enter_container(m.get(), SD_BUS_TYPE_VARIANT,
1132 containerType);
1133 if (r < 0)
1134 {
1135 BMCWEB_LOG_ERROR("sd_bus_message_enter_container failed with rc {}", r);
1136 return r;
1137 }
1138
1139 r = convertDBusToJSON(containerType, m, data);
1140 if (r < 0)
1141 {
1142 return r;
1143 }
1144
1145 r = sd_bus_message_exit_container(m.get());
1146 if (r < 0)
1147 {
1148 BMCWEB_LOG_ERROR("sd_bus_message_enter_container failed");
1149 return r;
1150 }
1151
1152 return 0;
1153 }
1154
convertDBusToJSON(const std::string & returnType,sdbusplus::message_t & m,nlohmann::json & response)1155 inline int convertDBusToJSON(const std::string& returnType,
1156 sdbusplus::message_t& m, nlohmann::json& response)
1157 {
1158 int r = 0;
1159 const std::vector<std::string> returnTypes = dbusArgSplit(returnType);
1160
1161 for (const std::string& typeCode : returnTypes)
1162 {
1163 nlohmann::json* thisElement = &response;
1164 if (returnTypes.size() > 1)
1165 {
1166 response.push_back(nlohmann::json{});
1167 thisElement = &response.back();
1168 }
1169
1170 if (typeCode == "s" || typeCode == "g" || typeCode == "o")
1171 {
1172 r = readMessageItem<char*>(typeCode, m, *thisElement);
1173 if (r < 0)
1174 {
1175 return r;
1176 }
1177 }
1178 else if (typeCode == "b")
1179 {
1180 r = readMessageItem<int>(typeCode, m, *thisElement);
1181 if (r < 0)
1182 {
1183 return r;
1184 }
1185
1186 *thisElement = static_cast<bool>(thisElement->get<int>());
1187 }
1188 else if (typeCode == "u")
1189 {
1190 r = readMessageItem<uint32_t>(typeCode, m, *thisElement);
1191 if (r < 0)
1192 {
1193 return r;
1194 }
1195 }
1196 else if (typeCode == "i")
1197 {
1198 r = readMessageItem<int32_t>(typeCode, m, *thisElement);
1199 if (r < 0)
1200 {
1201 return r;
1202 }
1203 }
1204 else if (typeCode == "x")
1205 {
1206 r = readMessageItem<int64_t>(typeCode, m, *thisElement);
1207 if (r < 0)
1208 {
1209 return r;
1210 }
1211 }
1212 else if (typeCode == "t")
1213 {
1214 r = readMessageItem<uint64_t>(typeCode, m, *thisElement);
1215 if (r < 0)
1216 {
1217 return r;
1218 }
1219 }
1220 else if (typeCode == "n")
1221 {
1222 r = readMessageItem<int16_t>(typeCode, m, *thisElement);
1223 if (r < 0)
1224 {
1225 return r;
1226 }
1227 }
1228 else if (typeCode == "q")
1229 {
1230 r = readMessageItem<uint16_t>(typeCode, m, *thisElement);
1231 if (r < 0)
1232 {
1233 return r;
1234 }
1235 }
1236 else if (typeCode == "y")
1237 {
1238 r = readMessageItem<uint8_t>(typeCode, m, *thisElement);
1239 if (r < 0)
1240 {
1241 return r;
1242 }
1243 }
1244 else if (typeCode == "d")
1245 {
1246 r = readMessageItem<double>(typeCode, m, *thisElement);
1247 if (r < 0)
1248 {
1249 return r;
1250 }
1251 }
1252 else if (typeCode == "h")
1253 {
1254 r = readMessageItem<int>(typeCode, m, *thisElement);
1255 if (r < 0)
1256 {
1257 return r;
1258 }
1259 }
1260 else if (typeCode.starts_with("a"))
1261 {
1262 r = readArrayFromMessage(typeCode, m, *thisElement);
1263 if (r < 0)
1264 {
1265 return r;
1266 }
1267 }
1268 else if (typeCode.starts_with("(") && typeCode.ends_with(")"))
1269 {
1270 r = readStructFromMessage(typeCode, m, *thisElement);
1271 if (r < 0)
1272 {
1273 return r;
1274 }
1275 }
1276 else if (typeCode.starts_with("v"))
1277 {
1278 r = readVariantFromMessage(m, *thisElement);
1279 if (r < 0)
1280 {
1281 return r;
1282 }
1283 }
1284 else
1285 {
1286 BMCWEB_LOG_ERROR("Invalid D-Bus signature type {}", typeCode);
1287 return -2;
1288 }
1289 }
1290
1291 return 0;
1292 }
1293
handleMethodResponse(const std::shared_ptr<InProgressActionData> & transaction,sdbusplus::message_t & m,const std::string & returnType)1294 inline void handleMethodResponse(
1295 const std::shared_ptr<InProgressActionData>& transaction,
1296 sdbusplus::message_t& m, const std::string& returnType)
1297 {
1298 nlohmann::json data;
1299
1300 int r = convertDBusToJSON(returnType, m, data);
1301 if (r < 0)
1302 {
1303 transaction->outputFailed = true;
1304 return;
1305 }
1306
1307 if (data.is_null())
1308 {
1309 return;
1310 }
1311
1312 if (transaction->methodResponse.is_null())
1313 {
1314 transaction->methodResponse = std::move(data);
1315 return;
1316 }
1317
1318 // If they're both dictionaries or arrays, merge into one.
1319 // Otherwise, make the results an array with every result
1320 // an entry. Could also just fail in that case, but it
1321 // seems better to get the data back somehow.
1322 nlohmann::json::object_t* dataobj =
1323 data.get_ptr<nlohmann::json::object_t*>();
1324
1325 nlohmann::json::object_t* methodResponseObj =
1326 transaction->methodResponse.get_ptr<nlohmann::json::object_t*>();
1327 if (methodResponseObj != nullptr && dataobj != nullptr)
1328 {
1329 for (auto& obj : *dataobj)
1330 {
1331 // Note: Will overwrite the data for a duplicate key
1332 methodResponseObj->emplace(obj.first, std::move(obj.second));
1333 }
1334 return;
1335 }
1336
1337 nlohmann::json::array_t* dataarr = data.get_ptr<nlohmann::json::array_t*>();
1338 nlohmann::json::array_t* methodResponseArr =
1339 transaction->methodResponse.get_ptr<nlohmann::json::array_t*>();
1340 if (methodResponseArr != nullptr && dataarr != nullptr)
1341 {
1342 for (auto& obj : *dataarr)
1343 {
1344 methodResponseArr->emplace_back(std::move(obj));
1345 }
1346 return;
1347 }
1348
1349 if (!transaction->convertedToArray)
1350 {
1351 // They are different types. May as well turn them into an array
1352 nlohmann::json j = std::move(transaction->methodResponse);
1353 transaction->methodResponse = nlohmann::json::array();
1354 transaction->methodResponse.emplace_back(std::move(j));
1355 transaction->methodResponse.emplace_back(std::move(data));
1356 transaction->convertedToArray = true;
1357 }
1358 else
1359 {
1360 transaction->methodResponse.emplace_back(std::move(data));
1361 }
1362 }
1363
findActionOnInterface(const std::shared_ptr<InProgressActionData> & transaction,const std::string & connectionName)1364 inline void findActionOnInterface(
1365 const std::shared_ptr<InProgressActionData>& transaction,
1366 const std::string& connectionName)
1367 {
1368 BMCWEB_LOG_DEBUG("findActionOnInterface for connection {}", connectionName);
1369 dbus::utility::async_method_call(
1370 [transaction, connectionName{std::string(connectionName)}](
1371 const boost::system::error_code& ec,
1372 const std::string& introspectXml) {
1373 BMCWEB_LOG_DEBUG("got xml:\n {}", introspectXml);
1374 if (ec)
1375 {
1376 BMCWEB_LOG_ERROR(
1377 "Introspect call failed with error: {} on process: {}",
1378 ec.message(), connectionName);
1379 return;
1380 }
1381 tinyxml2::XMLDocument doc;
1382
1383 doc.Parse(introspectXml.data(), introspectXml.size());
1384 tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
1385 if (pRoot == nullptr)
1386 {
1387 BMCWEB_LOG_ERROR("XML document failed to parse {}",
1388 connectionName);
1389 return;
1390 }
1391 tinyxml2::XMLElement* interfaceNode =
1392 pRoot->FirstChildElement("interface");
1393 while (interfaceNode != nullptr)
1394 {
1395 const char* thisInterfaceName =
1396 interfaceNode->Attribute("name");
1397 if (thisInterfaceName != nullptr)
1398 {
1399 if (!transaction->interfaceName.empty() &&
1400 (transaction->interfaceName != thisInterfaceName))
1401 {
1402 interfaceNode =
1403 interfaceNode->NextSiblingElement("interface");
1404 continue;
1405 }
1406
1407 tinyxml2::XMLElement* methodNode =
1408 interfaceNode->FirstChildElement("method");
1409 while (methodNode != nullptr)
1410 {
1411 const char* thisMethodName =
1412 methodNode->Attribute("name");
1413 BMCWEB_LOG_DEBUG("Found method: {}", thisMethodName);
1414 if (thisMethodName != nullptr &&
1415 thisMethodName == transaction->methodName)
1416 {
1417 BMCWEB_LOG_DEBUG(
1418 "Found method named {} on interface {}",
1419 thisMethodName, thisInterfaceName);
1420 sdbusplus::message_t m =
1421 crow::connections::systemBus->new_method_call(
1422 connectionName.c_str(),
1423 transaction->path.c_str(),
1424 thisInterfaceName,
1425 transaction->methodName.c_str());
1426
1427 tinyxml2::XMLElement* argumentNode =
1428 methodNode->FirstChildElement("arg");
1429
1430 std::string returnType;
1431
1432 // Find the output type
1433 while (argumentNode != nullptr)
1434 {
1435 const char* argDirection =
1436 argumentNode->Attribute("direction");
1437 const char* argType =
1438 argumentNode->Attribute("type");
1439 if (argDirection != nullptr &&
1440 argType != nullptr &&
1441 std::string(argDirection) == "out")
1442 {
1443 returnType = argType;
1444 break;
1445 }
1446 argumentNode =
1447 argumentNode->NextSiblingElement("arg");
1448 }
1449
1450 auto argIt = transaction->arguments.begin();
1451
1452 argumentNode = methodNode->FirstChildElement("arg");
1453
1454 while (argumentNode != nullptr)
1455 {
1456 const char* argDirection =
1457 argumentNode->Attribute("direction");
1458 const char* argType =
1459 argumentNode->Attribute("type");
1460 if (argDirection != nullptr &&
1461 argType != nullptr &&
1462 std::string(argDirection) == "in")
1463 {
1464 if (argIt == transaction->arguments.end())
1465 {
1466 transaction->setErrorStatus(
1467 "Invalid method args");
1468 return;
1469 }
1470 if (convertJsonToDbus(m.get(),
1471 std::string(argType),
1472 *argIt) < 0)
1473 {
1474 transaction->setErrorStatus(
1475 "Invalid method arg type");
1476 return;
1477 }
1478
1479 argIt++;
1480 }
1481 argumentNode =
1482 argumentNode->NextSiblingElement("arg");
1483 }
1484
1485 crow::connections::systemBus->async_send(
1486 m, [transaction, returnType](
1487 const boost::system::error_code& ec2,
1488 sdbusplus::message_t& m2) {
1489 if (ec2)
1490 {
1491 transaction->methodFailed = true;
1492 const sd_bus_error* e = m2.get_error();
1493
1494 if (e != nullptr)
1495 {
1496 setErrorResponse(
1497 transaction->asyncResp->res,
1498 boost::beast::http::status::
1499 bad_request,
1500 e->name, e->message);
1501 }
1502 else
1503 {
1504 setErrorResponse(
1505 transaction->asyncResp->res,
1506 boost::beast::http::status::
1507 bad_request,
1508 "Method call failed",
1509 methodFailedMsg);
1510 }
1511 return;
1512 }
1513 transaction->methodPassed = true;
1514
1515 handleMethodResponse(transaction, m2,
1516 returnType);
1517 });
1518 break;
1519 }
1520 methodNode = methodNode->NextSiblingElement("method");
1521 }
1522 }
1523 interfaceNode = interfaceNode->NextSiblingElement("interface");
1524 }
1525 },
1526 connectionName, transaction->path,
1527 "org.freedesktop.DBus.Introspectable", "Introspect");
1528 }
1529
handleAction(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & objectPath,const std::string & methodName)1530 inline void handleAction(const crow::Request& req,
1531 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1532 const std::string& objectPath,
1533 const std::string& methodName)
1534 {
1535 BMCWEB_LOG_DEBUG("handleAction on path: {} and method {}", objectPath,
1536 methodName);
1537 nlohmann::json requestDbusData;
1538
1539 JsonParseResult ret = parseRequestAsJson(req, requestDbusData);
1540 if (ret == JsonParseResult::BadContentType)
1541 {
1542 setErrorResponse(asyncResp->res,
1543 boost::beast::http::status::unsupported_media_type,
1544 invalidContentType, unsupportedMediaMsg);
1545 return;
1546 }
1547 if (ret != JsonParseResult::Success)
1548 {
1549 setErrorResponse(asyncResp->res,
1550 boost::beast::http::status::bad_request, noJsonDesc,
1551 badReqMsg);
1552 return;
1553 }
1554 nlohmann::json::iterator data = requestDbusData.find("data");
1555 if (data == requestDbusData.end())
1556 {
1557 setErrorResponse(asyncResp->res,
1558 boost::beast::http::status::bad_request, noJsonDesc,
1559 badReqMsg);
1560 return;
1561 }
1562
1563 if (!data->is_array())
1564 {
1565 setErrorResponse(asyncResp->res,
1566 boost::beast::http::status::bad_request, noJsonDesc,
1567 badReqMsg);
1568 return;
1569 }
1570 auto transaction = std::make_shared<InProgressActionData>(asyncResp);
1571
1572 transaction->path = objectPath;
1573 transaction->methodName = methodName;
1574 transaction->arguments = std::move(*data);
1575 dbus::utility::getDbusObject(
1576 objectPath, {},
1577 [transaction](
1578 const boost::system::error_code& ec,
1579 const std::vector<std::pair<std::string, std::vector<std::string>>>&
1580 interfaceNames) {
1581 if (ec || interfaceNames.empty())
1582 {
1583 BMCWEB_LOG_ERROR("Can't find object");
1584 setErrorResponse(transaction->asyncResp->res,
1585 boost::beast::http::status::not_found,
1586 notFoundDesc, notFoundMsg);
1587 return;
1588 }
1589
1590 BMCWEB_LOG_DEBUG("GetObject returned {} object(s)",
1591 interfaceNames.size());
1592
1593 for (const std::pair<std::string, std::vector<std::string>>&
1594 object : interfaceNames)
1595 {
1596 findActionOnInterface(transaction, object.first);
1597 }
1598 });
1599 }
1600
handleDelete(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & objectPath)1601 inline void handleDelete(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1602 const std::string& objectPath)
1603 {
1604 BMCWEB_LOG_DEBUG("handleDelete on path: {}", objectPath);
1605
1606 dbus::utility::getDbusObject(
1607 objectPath, {},
1608 [asyncResp, objectPath](
1609 const boost::system::error_code& ec,
1610 const std::vector<std::pair<std::string, std::vector<std::string>>>&
1611 interfaceNames) {
1612 if (ec || interfaceNames.empty())
1613 {
1614 BMCWEB_LOG_ERROR("Can't find object");
1615 setErrorResponse(asyncResp->res,
1616 boost::beast::http::status::method_not_allowed,
1617 methodNotAllowedDesc, methodNotAllowedMsg);
1618 return;
1619 }
1620
1621 auto transaction =
1622 std::make_shared<InProgressActionData>(asyncResp);
1623 transaction->path = objectPath;
1624 transaction->methodName = "Delete";
1625 transaction->interfaceName = "xyz.openbmc_project.Object.Delete";
1626
1627 for (const std::pair<std::string, std::vector<std::string>>&
1628 object : interfaceNames)
1629 {
1630 findActionOnInterface(transaction, object.first);
1631 }
1632 });
1633 }
1634
handleList(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & objectPath,int32_t depth=0)1635 inline void handleList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1636 const std::string& objectPath, int32_t depth = 0)
1637 {
1638 dbus::utility::getSubTreePaths(
1639 objectPath, depth, {},
1640 [asyncResp](
1641 const boost::system::error_code& ec,
1642 const dbus::utility::MapperGetSubTreePathsResponse& objectPaths) {
1643 if (ec)
1644 {
1645 setErrorResponse(asyncResp->res,
1646 boost::beast::http::status::not_found,
1647 notFoundDesc, notFoundMsg);
1648 }
1649 else
1650 {
1651 asyncResp->res.jsonValue["status"] = "ok";
1652 asyncResp->res.jsonValue["message"] = "200 OK";
1653 asyncResp->res.jsonValue["data"] = objectPaths;
1654 }
1655 });
1656 }
1657
handleEnumerate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & objectPath)1658 inline void handleEnumerate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1659 const std::string& objectPath)
1660 {
1661 BMCWEB_LOG_DEBUG("Doing enumerate on {}", objectPath);
1662
1663 asyncResp->res.jsonValue["message"] = "200 OK";
1664 asyncResp->res.jsonValue["status"] = "ok";
1665 asyncResp->res.jsonValue["data"] = nlohmann::json::object();
1666
1667 dbus::utility::getSubTree(
1668 objectPath, 0, {},
1669 [objectPath, asyncResp](
1670 const boost::system::error_code& ec,
1671 const dbus::utility::MapperGetSubTreeResponse& objectNames) {
1672 auto transaction = std::make_shared<InProgressEnumerateData>(
1673 objectPath, asyncResp);
1674
1675 transaction->subtree =
1676 std::make_shared<dbus::utility::MapperGetSubTreeResponse>(
1677 objectNames);
1678
1679 if (ec)
1680 {
1681 BMCWEB_LOG_ERROR("GetSubTree failed on {}",
1682 transaction->objectPath);
1683 setErrorResponse(transaction->asyncResp->res,
1684 boost::beast::http::status::not_found,
1685 notFoundDesc, notFoundMsg);
1686 return;
1687 }
1688
1689 // Add the data for the path passed in to the results
1690 // as if GetSubTree returned it, and continue on enumerating
1691 getObjectAndEnumerate(transaction);
1692 });
1693 }
1694
handleGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string & objectPath,std::string & destProperty)1695 inline void handleGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1696 std::string& objectPath, std::string& destProperty)
1697 {
1698 BMCWEB_LOG_DEBUG("handleGet: {} prop:{}", objectPath, destProperty);
1699 std::shared_ptr<std::string> propertyName =
1700 std::make_shared<std::string>(std::move(destProperty));
1701
1702 std::shared_ptr<std::string> path =
1703 std::make_shared<std::string>(std::move(objectPath));
1704
1705 dbus::utility::getDbusObject(
1706 *path, {},
1707 [asyncResp, path,
1708 propertyName](const boost::system::error_code& ec,
1709 const dbus::utility::MapperGetObject& objectNames) {
1710 if (ec || objectNames.empty())
1711 {
1712 setErrorResponse(asyncResp->res,
1713 boost::beast::http::status::not_found,
1714 notFoundDesc, notFoundMsg);
1715 return;
1716 }
1717 std::shared_ptr<nlohmann::json> response =
1718 std::make_shared<nlohmann::json>(nlohmann::json::object());
1719 for (const std::pair<std::string, std::vector<std::string>>&
1720 connection : objectNames)
1721 {
1722 const std::vector<std::string>& interfaceNames =
1723 connection.second;
1724
1725 if (interfaceNames.empty())
1726 {
1727 // mapper allows empty interfaces in case an
1728 // object does not implement any interface.
1729 continue;
1730 }
1731
1732 for (const std::string& interface : interfaceNames)
1733 {
1734 sdbusplus::message_t m =
1735 crow::connections::systemBus->new_method_call(
1736 connection.first.c_str(), path->c_str(),
1737 "org.freedesktop.DBus.Properties", "GetAll");
1738 m.append(interface);
1739 crow::connections::systemBus->async_send(
1740 m, [asyncResp, response,
1741 propertyName](const boost::system::error_code& ec2,
1742 sdbusplus::message_t& msg) {
1743 if (ec2)
1744 {
1745 BMCWEB_LOG_ERROR("Bad dbus request error: {}",
1746 ec2);
1747 }
1748 else
1749 {
1750 nlohmann::json properties;
1751 int r =
1752 convertDBusToJSON("a{sv}", msg, properties);
1753 if (r < 0)
1754 {
1755 BMCWEB_LOG_ERROR(
1756 "convertDBusToJSON failed");
1757 }
1758 else
1759 {
1760 nlohmann::json::object_t* obj =
1761 properties.get_ptr<
1762 nlohmann::json::object_t*>();
1763 if (obj == nullptr)
1764 {
1765 return;
1766 }
1767 for (auto& prop : *obj)
1768 {
1769 // if property name is empty, or
1770 // matches our search query, add it
1771 // to the response json
1772
1773 if (propertyName->empty())
1774 {
1775 (*response)[prop.first] =
1776 std::move(prop.second);
1777 }
1778 else if (prop.first == *propertyName)
1779 {
1780 *response = std::move(prop.second);
1781 }
1782 }
1783 }
1784 }
1785 if (response.use_count() == 1)
1786 {
1787 if (!propertyName->empty() && response->empty())
1788 {
1789 setErrorResponse(
1790 asyncResp->res,
1791 boost::beast::http::status::not_found,
1792 propNotFoundDesc, notFoundMsg);
1793 }
1794 else
1795 {
1796 asyncResp->res.jsonValue["status"] = "ok";
1797 asyncResp->res.jsonValue["message"] =
1798 "200 OK";
1799 asyncResp->res.jsonValue["data"] =
1800 *response;
1801 }
1802 }
1803 });
1804 }
1805 }
1806 });
1807 }
1808
1809 struct AsyncPutRequest
1810 {
AsyncPutRequestcrow::openbmc_mapper::AsyncPutRequest1811 explicit AsyncPutRequest(const std::shared_ptr<bmcweb::AsyncResp>& resIn) :
1812 asyncResp(resIn)
1813 {}
~AsyncPutRequestcrow::openbmc_mapper::AsyncPutRequest1814 ~AsyncPutRequest()
1815 {
1816 if (asyncResp->res.jsonValue.empty())
1817 {
1818 setErrorResponse(asyncResp->res,
1819 boost::beast::http::status::forbidden,
1820 forbiddenMsg, forbiddenPropDesc);
1821 }
1822 }
1823
1824 AsyncPutRequest(const AsyncPutRequest&) = delete;
1825 AsyncPutRequest(AsyncPutRequest&&) = delete;
1826 AsyncPutRequest& operator=(const AsyncPutRequest&) = delete;
1827 AsyncPutRequest& operator=(AsyncPutRequest&&) = delete;
1828
setErrorStatuscrow::openbmc_mapper::AsyncPutRequest1829 void setErrorStatus(const std::string& desc)
1830 {
1831 setErrorResponse(asyncResp->res,
1832 boost::beast::http::status::internal_server_error,
1833 desc, badReqMsg);
1834 }
1835
1836 const std::shared_ptr<bmcweb::AsyncResp> asyncResp;
1837 std::string objectPath;
1838 std::string propertyName;
1839 nlohmann::json propertyValue;
1840 };
1841
handlePut(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & objectPath,const std::string & destProperty)1842 inline void handlePut(const crow::Request& req,
1843 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1844 const std::string& objectPath,
1845 const std::string& destProperty)
1846 {
1847 if (destProperty.empty())
1848 {
1849 setErrorResponse(asyncResp->res, boost::beast::http::status::forbidden,
1850 forbiddenResDesc, forbiddenMsg);
1851 return;
1852 }
1853 nlohmann::json requestDbusData;
1854
1855 JsonParseResult ret = parseRequestAsJson(req, requestDbusData);
1856 if (ret == JsonParseResult::BadContentType)
1857 {
1858 setErrorResponse(asyncResp->res,
1859 boost::beast::http::status::unsupported_media_type,
1860 invalidContentType, unsupportedMediaMsg);
1861 return;
1862 }
1863
1864 if (ret != JsonParseResult::Success)
1865 {
1866 setErrorResponse(asyncResp->res,
1867 boost::beast::http::status::bad_request, noJsonDesc,
1868 badReqMsg);
1869 return;
1870 }
1871
1872 auto propertyIt = requestDbusData.find("data");
1873 if (propertyIt == requestDbusData.end())
1874 {
1875 setErrorResponse(asyncResp->res,
1876 boost::beast::http::status::bad_request, noJsonDesc,
1877 badReqMsg);
1878 return;
1879 }
1880 const nlohmann::json& propertySetValue = *propertyIt;
1881 auto transaction = std::make_shared<AsyncPutRequest>(asyncResp);
1882 transaction->objectPath = objectPath;
1883 transaction->propertyName = destProperty;
1884 transaction->propertyValue = propertySetValue;
1885
1886 dbus::utility::getDbusObject(
1887 transaction->objectPath, {},
1888 [transaction](const boost::system::error_code& ec2,
1889 const dbus::utility::MapperGetObject& objectNames) {
1890 if (!ec2 && objectNames.empty())
1891 {
1892 setErrorResponse(transaction->asyncResp->res,
1893 boost::beast::http::status::not_found,
1894 propNotFoundDesc, notFoundMsg);
1895 return;
1896 }
1897
1898 for (const std::pair<std::string, std::vector<std::string>>&
1899 connection : objectNames)
1900 {
1901 const std::string& connectionName = connection.first;
1902
1903 dbus::utility::async_method_call(
1904 [connectionName{std::string(connectionName)},
1905 transaction](const boost::system::error_code& ec3,
1906 const std::string& introspectXml) {
1907 if (ec3)
1908 {
1909 BMCWEB_LOG_ERROR(
1910 "Introspect call failed with error: {} on process: {}",
1911 ec3.message(), connectionName);
1912 transaction->setErrorStatus("Unexpected Error");
1913 return;
1914 }
1915 tinyxml2::XMLDocument doc;
1916
1917 doc.Parse(introspectXml.c_str());
1918 tinyxml2::XMLNode* pRoot =
1919 doc.FirstChildElement("node");
1920 if (pRoot == nullptr)
1921 {
1922 BMCWEB_LOG_ERROR("XML document failed to parse: {}",
1923 introspectXml);
1924 transaction->setErrorStatus("Unexpected Error");
1925 return;
1926 }
1927 tinyxml2::XMLElement* ifaceNode =
1928 pRoot->FirstChildElement("interface");
1929 while (ifaceNode != nullptr)
1930 {
1931 const char* interfaceName =
1932 ifaceNode->Attribute("name");
1933 BMCWEB_LOG_DEBUG("found interface {}",
1934 interfaceName);
1935 tinyxml2::XMLElement* propNode =
1936 ifaceNode->FirstChildElement("property");
1937 while (propNode != nullptr)
1938 {
1939 const char* propertyName =
1940 propNode->Attribute("name");
1941 if (propertyName == nullptr)
1942 {
1943 BMCWEB_LOG_DEBUG(
1944 "Couldn't find name property");
1945 continue;
1946 }
1947 BMCWEB_LOG_DEBUG("Found property {}",
1948 propertyName);
1949 if (propertyName == transaction->propertyName)
1950 {
1951 const char* argType =
1952 propNode->Attribute("type");
1953 if (argType != nullptr)
1954 {
1955 sdbusplus::message_t m =
1956 crow::connections::systemBus
1957 ->new_method_call(
1958 connectionName.c_str(),
1959 transaction->objectPath
1960 .c_str(),
1961 "org.freedesktop.DBus."
1962 "Properties",
1963 "Set");
1964 m.append(interfaceName,
1965 transaction->propertyName);
1966 int r = sd_bus_message_open_container(
1967 m.get(), SD_BUS_TYPE_VARIANT,
1968 argType);
1969 if (r < 0)
1970 {
1971 transaction->setErrorStatus(
1972 "Unexpected Error");
1973 return;
1974 }
1975 r = convertJsonToDbus(
1976 m.get(), argType,
1977 transaction->propertyValue);
1978 if (r < 0)
1979 {
1980 if (r == -ERANGE)
1981 {
1982 transaction->setErrorStatus(
1983 "Provided property value "
1984 "is out of range for the "
1985 "property type");
1986 }
1987 else
1988 {
1989 transaction->setErrorStatus(
1990 "Invalid arg type");
1991 }
1992 return;
1993 }
1994 r = sd_bus_message_close_container(
1995 m.get());
1996 if (r < 0)
1997 {
1998 transaction->setErrorStatus(
1999 "Unexpected Error");
2000 return;
2001 }
2002 crow::connections::systemBus
2003 ->async_send(
2004 m,
2005 [transaction](
2006 const boost::system::
2007 error_code& ec,
2008 sdbusplus::message_t& m2) {
2009 BMCWEB_LOG_DEBUG("sent");
2010 if (ec)
2011 {
2012 const sd_bus_error* e =
2013 m2.get_error();
2014 setErrorResponse(
2015 transaction
2016 ->asyncResp
2017 ->res,
2018 boost::beast::http::
2019 status::
2020 forbidden,
2021 (e) != nullptr
2022 ? e->name
2023 : ec.category()
2024 .name(),
2025 (e) != nullptr
2026 ? e->message
2027 : ec.message());
2028 }
2029 else
2030 {
2031 transaction->asyncResp
2032 ->res.jsonValue
2033 ["status"] =
2034 "ok";
2035 transaction->asyncResp
2036 ->res.jsonValue
2037 ["message"] =
2038 "200 OK";
2039 transaction->asyncResp
2040 ->res
2041 .jsonValue["data"] =
2042 nullptr;
2043 }
2044 });
2045 }
2046 }
2047 propNode =
2048 propNode->NextSiblingElement("property");
2049 }
2050 ifaceNode =
2051 ifaceNode->NextSiblingElement("interface");
2052 }
2053 },
2054 connectionName, transaction->objectPath,
2055 "org.freedesktop.DBus.Introspectable", "Introspect");
2056 }
2057 });
2058 }
2059
handleDBusUrl(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string & objectPath)2060 inline void handleDBusUrl(const crow::Request& req,
2061 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2062 std::string& objectPath)
2063 {
2064 // If accessing a single attribute, fill in and update objectPath,
2065 // otherwise leave destProperty blank
2066 std::string destProperty;
2067 const char* attrSeperator = "/attr/";
2068 size_t attrPosition = objectPath.find(attrSeperator);
2069 if (attrPosition != std::string::npos)
2070 {
2071 destProperty = objectPath.substr(attrPosition + strlen(attrSeperator),
2072 objectPath.length());
2073 objectPath.resize(attrPosition);
2074 }
2075
2076 if (req.method() == boost::beast::http::verb::post)
2077 {
2078 constexpr const char* actionSeperator = "/action/";
2079 size_t actionPosition = objectPath.find(actionSeperator);
2080 if (actionPosition != std::string::npos)
2081 {
2082 std::string postProperty =
2083 objectPath.substr((actionPosition + strlen(actionSeperator)),
2084 objectPath.length());
2085 objectPath.resize(actionPosition);
2086 handleAction(req, asyncResp, objectPath, postProperty);
2087 return;
2088 }
2089 }
2090 else if (req.method() == boost::beast::http::verb::get)
2091 {
2092 if (objectPath.ends_with("/enumerate"))
2093 {
2094 objectPath.erase(objectPath.end() - sizeof("enumerate"),
2095 objectPath.end());
2096 handleEnumerate(asyncResp, objectPath);
2097 }
2098 else if (objectPath.ends_with("/list"))
2099 {
2100 objectPath.erase(objectPath.end() - sizeof("list"),
2101 objectPath.end());
2102 handleList(asyncResp, objectPath);
2103 }
2104 else
2105 {
2106 // Trim any trailing "/" at the end
2107 if (objectPath.ends_with("/"))
2108 {
2109 objectPath.pop_back();
2110 handleList(asyncResp, objectPath, 1);
2111 }
2112 else
2113 {
2114 handleGet(asyncResp, objectPath, destProperty);
2115 }
2116 }
2117 return;
2118 }
2119 else if (req.method() == boost::beast::http::verb::put)
2120 {
2121 handlePut(req, asyncResp, objectPath, destProperty);
2122 return;
2123 }
2124 else if (req.method() == boost::beast::http::verb::delete_)
2125 {
2126 handleDelete(asyncResp, objectPath);
2127 return;
2128 }
2129
2130 setErrorResponse(asyncResp->res,
2131 boost::beast::http::status::method_not_allowed,
2132 methodNotAllowedDesc, methodNotAllowedMsg);
2133 }
2134
handleBusSystemPost(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & processName,const std::string & requestedPath)2135 inline void handleBusSystemPost(
2136 const crow::Request& req,
2137 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2138 const std::string& processName, const std::string& requestedPath)
2139 {
2140 std::vector<std::string> strs;
2141
2142 bmcweb::split(strs, requestedPath, '/');
2143 std::string objectPath;
2144 std::string interfaceName;
2145 std::string methodName;
2146 auto it = strs.begin();
2147 if (it == strs.end())
2148 {
2149 objectPath = "/";
2150 }
2151 while (it != strs.end())
2152 {
2153 // Check if segment contains ".". If it does, it must be an
2154 // interface
2155 if (it->find(".") != std::string::npos)
2156 {
2157 break;
2158 // This check is necessary as the trailing slash gets
2159 // parsed as part of our <path> specifier above, which
2160 // causes the normal trailing backslash redirector to
2161 // fail.
2162 }
2163 if (!it->empty())
2164 {
2165 objectPath += "/" + *it;
2166 }
2167 it++;
2168 }
2169 if (it != strs.end())
2170 {
2171 interfaceName = *it;
2172 it++;
2173
2174 // after interface, we might have a method name
2175 if (it != strs.end())
2176 {
2177 methodName = *it;
2178 it++;
2179 }
2180 }
2181 if (it != strs.end())
2182 {
2183 // if there is more levels past the method name, something
2184 // went wrong, return not found
2185 asyncResp->res.result(boost::beast::http::status::not_found);
2186 return;
2187 }
2188 if (interfaceName.empty())
2189 {
2190 dbus::utility::async_method_call(
2191 [asyncResp, processName,
2192 objectPath](const boost::system::error_code& ec,
2193 const std::string& introspectXml) {
2194 if (ec)
2195 {
2196 BMCWEB_LOG_ERROR(
2197 "Introspect call failed with error: {} on process: {} path: {}",
2198 ec.message(), processName, objectPath);
2199 return;
2200 }
2201 tinyxml2::XMLDocument doc;
2202
2203 doc.Parse(introspectXml.c_str());
2204 tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
2205 if (pRoot == nullptr)
2206 {
2207 BMCWEB_LOG_ERROR("XML document failed to parse {} {}",
2208 processName, objectPath);
2209 asyncResp->res.jsonValue["status"] = "XML parse error";
2210 asyncResp->res.result(
2211 boost::beast::http::status::internal_server_error);
2212 return;
2213 }
2214
2215 BMCWEB_LOG_DEBUG("{}", introspectXml);
2216 asyncResp->res.jsonValue["status"] = "ok";
2217 asyncResp->res.jsonValue["bus_name"] = processName;
2218 asyncResp->res.jsonValue["object_path"] = objectPath;
2219
2220 nlohmann::json& interfacesArray =
2221 asyncResp->res.jsonValue["interfaces"];
2222 interfacesArray = nlohmann::json::array();
2223 tinyxml2::XMLElement* interface =
2224 pRoot->FirstChildElement("interface");
2225
2226 while (interface != nullptr)
2227 {
2228 const char* ifaceName = interface->Attribute("name");
2229 if (ifaceName != nullptr)
2230 {
2231 nlohmann::json::object_t interfaceObj;
2232 interfaceObj["name"] = ifaceName;
2233 interfacesArray.emplace_back(std::move(interfaceObj));
2234 }
2235
2236 interface = interface->NextSiblingElement("interface");
2237 }
2238 },
2239 processName, objectPath, "org.freedesktop.DBus.Introspectable",
2240 "Introspect");
2241 }
2242 else if (methodName.empty())
2243 {
2244 dbus::utility::async_method_call(
2245 [asyncResp, processName, objectPath,
2246 interfaceName](const boost::system::error_code& ec,
2247 const std::string& introspectXml) {
2248 if (ec)
2249 {
2250 BMCWEB_LOG_ERROR(
2251 "Introspect call failed with error: {} on process: {} path: {}",
2252 ec.message(), processName, objectPath);
2253 return;
2254 }
2255 tinyxml2::XMLDocument doc;
2256
2257 doc.Parse(introspectXml.data(), introspectXml.size());
2258 tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
2259 if (pRoot == nullptr)
2260 {
2261 BMCWEB_LOG_ERROR("XML document failed to parse {} {}",
2262 processName, objectPath);
2263 asyncResp->res.result(
2264 boost::beast::http::status::internal_server_error);
2265 return;
2266 }
2267
2268 asyncResp->res.jsonValue["status"] = "ok";
2269 asyncResp->res.jsonValue["bus_name"] = processName;
2270 asyncResp->res.jsonValue["interface"] = interfaceName;
2271 asyncResp->res.jsonValue["object_path"] = objectPath;
2272
2273 nlohmann::json& methodsArray =
2274 asyncResp->res.jsonValue["methods"];
2275 methodsArray = nlohmann::json::array();
2276
2277 nlohmann::json& signalsArray =
2278 asyncResp->res.jsonValue["signals"];
2279 signalsArray = nlohmann::json::array();
2280
2281 nlohmann::json& propertiesObj =
2282 asyncResp->res.jsonValue["properties"];
2283 propertiesObj = nlohmann::json::object();
2284
2285 // if we know we're the only call, build the
2286 // json directly
2287 tinyxml2::XMLElement* interface =
2288 pRoot->FirstChildElement("interface");
2289 while (interface != nullptr)
2290 {
2291 const char* ifaceName = interface->Attribute("name");
2292
2293 if (ifaceName != nullptr && ifaceName == interfaceName)
2294 {
2295 break;
2296 }
2297
2298 interface = interface->NextSiblingElement("interface");
2299 }
2300 if (interface == nullptr)
2301 {
2302 // if we got to the end of the list and
2303 // never found a match, throw 404
2304 asyncResp->res.result(
2305 boost::beast::http::status::not_found);
2306 return;
2307 }
2308
2309 tinyxml2::XMLElement* methods =
2310 interface->FirstChildElement("method");
2311 while (methods != nullptr)
2312 {
2313 nlohmann::json argsArray = nlohmann::json::array();
2314 tinyxml2::XMLElement* arg =
2315 methods->FirstChildElement("arg");
2316 while (arg != nullptr)
2317 {
2318 nlohmann::json thisArg;
2319 for (const char* fieldName : std::array<const char*, 3>{
2320 "name", "direction", "type"})
2321 {
2322 const char* fieldValue = arg->Attribute(fieldName);
2323 if (fieldValue != nullptr)
2324 {
2325 thisArg[fieldName] = fieldValue;
2326 }
2327 }
2328 argsArray.emplace_back(std::move(thisArg));
2329 arg = arg->NextSiblingElement("arg");
2330 }
2331
2332 const char* name = methods->Attribute("name");
2333 if (name != nullptr)
2334 {
2335 std::string uri;
2336 uri.reserve(14 + processName.size() +
2337 objectPath.size() + interfaceName.size() +
2338 strlen(name));
2339 uri += "/bus/system/";
2340 uri += processName;
2341 uri += objectPath;
2342 uri += "/";
2343 uri += interfaceName;
2344 uri += "/";
2345 uri += name;
2346
2347 nlohmann::json::object_t object;
2348 object["name"] = name;
2349 object["uri"] = std::move(uri);
2350 object["args"] = argsArray;
2351
2352 methodsArray.emplace_back(std::move(object));
2353 }
2354 methods = methods->NextSiblingElement("method");
2355 }
2356 tinyxml2::XMLElement* signals =
2357 interface->FirstChildElement("signal");
2358 while (signals != nullptr)
2359 {
2360 nlohmann::json argsArray = nlohmann::json::array();
2361
2362 tinyxml2::XMLElement* arg =
2363 signals->FirstChildElement("arg");
2364 while (arg != nullptr)
2365 {
2366 const char* name = arg->Attribute("name");
2367 const char* type = arg->Attribute("type");
2368 if (name != nullptr && type != nullptr)
2369 {
2370 nlohmann::json::object_t params;
2371 params["name"] = name;
2372 params["type"] = type;
2373 argsArray.push_back(std::move(params));
2374 }
2375 arg = arg->NextSiblingElement("arg");
2376 }
2377 const char* name = signals->Attribute("name");
2378 if (name != nullptr)
2379 {
2380 nlohmann::json::object_t object;
2381 object["name"] = name;
2382 object["args"] = argsArray;
2383 signalsArray.emplace_back(std::move(object));
2384 }
2385
2386 signals = signals->NextSiblingElement("signal");
2387 }
2388
2389 tinyxml2::XMLElement* property =
2390 interface->FirstChildElement("property");
2391 while (property != nullptr)
2392 {
2393 const char* name = property->Attribute("name");
2394 const char* type = property->Attribute("type");
2395 if (type != nullptr && name != nullptr)
2396 {
2397 sdbusplus::message_t m =
2398 crow::connections::systemBus->new_method_call(
2399 processName.c_str(), objectPath.c_str(),
2400 "org.freedesktop."
2401 "DBus."
2402 "Properties",
2403 "Get");
2404 m.append(interfaceName, name);
2405 nlohmann::json& propertyItem = propertiesObj[name];
2406 crow::connections::systemBus->async_send(
2407 m, [&propertyItem,
2408 asyncResp](const boost::system::error_code& ec2,
2409 sdbusplus::message_t& msg) {
2410 if (ec2)
2411 {
2412 return;
2413 }
2414
2415 int r =
2416 convertDBusToJSON("v", msg, propertyItem);
2417 if (r < 0)
2418 {
2419 BMCWEB_LOG_ERROR(
2420 "Couldn't convert vector to json");
2421 }
2422 });
2423 }
2424 property = property->NextSiblingElement("property");
2425 }
2426 },
2427 processName, objectPath, "org.freedesktop.DBus.Introspectable",
2428 "Introspect");
2429 }
2430 else
2431 {
2432 if (req.method() != boost::beast::http::verb::post)
2433 {
2434 asyncResp->res.result(boost::beast::http::status::not_found);
2435 return;
2436 }
2437
2438 nlohmann::json requestDbusData;
2439 JsonParseResult ret = parseRequestAsJson(req, requestDbusData);
2440 if (ret == JsonParseResult::BadContentType)
2441 {
2442 setErrorResponse(asyncResp->res,
2443 boost::beast::http::status::unsupported_media_type,
2444 invalidContentType, unsupportedMediaMsg);
2445 return;
2446 }
2447 if (ret != JsonParseResult::Success)
2448 {
2449 setErrorResponse(asyncResp->res,
2450 boost::beast::http::status::bad_request,
2451 noJsonDesc, badReqMsg);
2452 return;
2453 }
2454
2455 if (!requestDbusData.is_array())
2456 {
2457 asyncResp->res.result(boost::beast::http::status::bad_request);
2458 return;
2459 }
2460 auto transaction = std::make_shared<InProgressActionData>(asyncResp);
2461
2462 transaction->path = objectPath;
2463 transaction->methodName = methodName;
2464 transaction->arguments = std::move(requestDbusData);
2465
2466 findActionOnInterface(transaction, processName);
2467 }
2468 }
2469
requestRoutes(App & app)2470 inline void requestRoutes(App& app)
2471 {
2472 BMCWEB_ROUTE(app, "/bus/")
2473 .privileges({{"Login"}})
2474 .methods(boost::beast::http::verb::get)(
2475 [](const crow::Request&,
2476 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2477 nlohmann::json::array_t buses;
2478 nlohmann::json& bus = buses.emplace_back();
2479 bus["name"] = "system";
2480 asyncResp->res.jsonValue["busses"] = std::move(buses);
2481 asyncResp->res.jsonValue["status"] = "ok";
2482 });
2483
2484 BMCWEB_ROUTE(app, "/bus/system/")
2485 .privileges({{"Login"}})
2486 .methods(boost::beast::http::verb::get)(
2487 [](const crow::Request&,
2488 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2489 auto myCallback = [asyncResp](
2490 const boost::system::error_code& ec,
2491 std::vector<std::string>& names) {
2492 if (ec)
2493 {
2494 BMCWEB_LOG_ERROR("Dbus call failed with code {}", ec);
2495 asyncResp->res.result(
2496 boost::beast::http::status::internal_server_error);
2497 }
2498 else
2499 {
2500 std::ranges::sort(names);
2501 asyncResp->res.jsonValue["status"] = "ok";
2502 auto& objectsSub = asyncResp->res.jsonValue["objects"];
2503 for (const auto& name : names)
2504 {
2505 nlohmann::json::object_t object;
2506 object["name"] = name;
2507 objectsSub.emplace_back(std::move(object));
2508 }
2509 }
2510 };
2511 dbus::utility::async_method_call(
2512 std::move(myCallback), "org.freedesktop.DBus", "/",
2513 "org.freedesktop.DBus", "ListNames");
2514 });
2515
2516 BMCWEB_ROUTE(app, "/list/")
2517 .privileges({{"Login"}})
2518 .methods(boost::beast::http::verb::get)(
2519 [](const crow::Request&,
2520 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2521 handleList(asyncResp, "/");
2522 });
2523
2524 BMCWEB_ROUTE(app, "/xyz/<path>")
2525 .privileges({{"Login"}})
2526 .methods(boost::beast::http::verb::get)(
2527 [](const crow::Request& req,
2528 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2529 const std::string& path) {
2530 std::string objectPath = "/xyz/" + path;
2531 handleDBusUrl(req, asyncResp, objectPath);
2532 });
2533
2534 BMCWEB_ROUTE(app, "/xyz/<path>")
2535 .privileges({{"ConfigureComponents", "ConfigureManager"}})
2536 .methods(boost::beast::http::verb::put, boost::beast::http::verb::post,
2537 boost::beast::http::verb::delete_)(
2538 [](const crow::Request& req,
2539 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2540 const std::string& path) {
2541 std::string objectPath = "/xyz/" + path;
2542 handleDBusUrl(req, asyncResp, objectPath);
2543 });
2544
2545 BMCWEB_ROUTE(app, "/org/<path>")
2546 .privileges({{"Login"}})
2547 .methods(boost::beast::http::verb::get)(
2548 [](const crow::Request& req,
2549 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2550 const std::string& path) {
2551 std::string objectPath = "/org/" + path;
2552 handleDBusUrl(req, asyncResp, objectPath);
2553 });
2554
2555 BMCWEB_ROUTE(app, "/org/<path>")
2556 .privileges({{"ConfigureComponents", "ConfigureManager"}})
2557 .methods(boost::beast::http::verb::put, boost::beast::http::verb::post,
2558 boost::beast::http::verb::delete_)(
2559 [](const crow::Request& req,
2560 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2561 const std::string& path) {
2562 std::string objectPath = "/org/" + path;
2563 handleDBusUrl(req, asyncResp, objectPath);
2564 });
2565
2566 BMCWEB_ROUTE(app, "/download/dump/<str>/")
2567 .privileges({{"ConfigureManager"}})
2568 .methods(boost::beast::http::verb::get)(
2569 [](const crow::Request&,
2570 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2571 const std::string& dumpId) {
2572 if (!validateFilename(dumpId))
2573 {
2574 asyncResp->res.result(
2575 boost::beast::http::status::bad_request);
2576 return;
2577 }
2578 std::filesystem::path loc(
2579 "/var/lib/phosphor-debug-collector/dumps");
2580
2581 loc /= dumpId;
2582
2583 if (!std::filesystem::exists(loc) ||
2584 !std::filesystem::is_directory(loc))
2585 {
2586 BMCWEB_LOG_ERROR("{}Not found", loc.string());
2587 asyncResp->res.result(
2588 boost::beast::http::status::not_found);
2589 return;
2590 }
2591 std::filesystem::directory_iterator files(loc);
2592
2593 for (const auto& file : files)
2594 {
2595 if (asyncResp->res.openFile(file) !=
2596 crow::OpenCode::Success)
2597 {
2598 continue;
2599 }
2600
2601 asyncResp->res.addHeader(
2602 boost::beast::http::field::content_type,
2603 "application/octet-stream");
2604
2605 // Assuming only one dump file will be present in the dump
2606 // id directory
2607 std::string dumpFileName = file.path().filename().string();
2608
2609 // Filename should be in alphanumeric, dot and underscore
2610 // Its based on phosphor-debug-collector application
2611 // dumpfile format
2612 static std::regex dumpFileRegex("[a-zA-Z0-9\\._]+");
2613 if (!std::regex_match(dumpFileName, dumpFileRegex))
2614 {
2615 BMCWEB_LOG_ERROR("Invalid dump filename {}",
2616 dumpFileName);
2617 asyncResp->res.result(
2618 boost::beast::http::status::not_found);
2619 return;
2620 }
2621 std::string contentDispositionParam =
2622 "attachment; filename=\"" + dumpFileName + "\"";
2623
2624 asyncResp->res.addHeader(
2625 boost::beast::http::field::content_disposition,
2626 contentDispositionParam);
2627
2628 return;
2629 }
2630 asyncResp->res.result(boost::beast::http::status::not_found);
2631 return;
2632 });
2633
2634 BMCWEB_ROUTE(app, "/bus/system/<str>/")
2635 .privileges({{"Login"}})
2636
2637 .methods(boost::beast::http::verb::get)(
2638 [](const crow::Request&,
2639 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2640 const std::string& connection) {
2641 introspectObjects(connection, "/", asyncResp);
2642 });
2643
2644 BMCWEB_ROUTE(app, "/bus/system/<str>/<path>")
2645 .privileges({{"ConfigureComponents", "ConfigureManager"}})
2646 .methods(boost::beast::http::verb::get,
2647 boost::beast::http::verb::post)(handleBusSystemPost);
2648 }
2649 } // namespace openbmc_mapper
2650 } // namespace crow
2651