1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
4 #pragma once
5
6 #include "bmcweb_config.h"
7
8 #include "app.hpp"
9 #include "async_resp.hpp"
10 #include "dbus_singleton.hpp"
11 #include "dbus_utility.hpp"
12 #include "error_messages.hpp"
13 #include "generated/enums/resource.hpp"
14 #include "http_request.hpp"
15 #include "identity.hpp"
16 #include "logging.hpp"
17 #include "privileges.hpp"
18 #include "query.hpp"
19 #include "redfish_util.hpp"
20 #include "registries/privilege_registry.hpp"
21 #include "utils/dbus_utils.hpp"
22 #include "utils/json_utils.hpp"
23 #include "utils/stl_utils.hpp"
24
25 #include <boost/beast/http/field.hpp>
26 #include <boost/beast/http/status.hpp>
27 #include <boost/beast/http/verb.hpp>
28 #include <boost/system/error_code.hpp>
29 #include <boost/url/format.hpp>
30 #include <sdbusplus/message/native_types.hpp>
31
32 #include <array>
33 #include <cstddef>
34 #include <functional>
35 #include <memory>
36 #include <optional>
37 #include <string>
38 #include <string_view>
39 #include <tuple>
40 #include <utility>
41 #include <variant>
42 #include <vector>
43
44 namespace redfish
45 {
46
47 void getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp);
48
49 static constexpr std::string_view sshServiceName = "dropbear";
50 static constexpr std::string_view httpsServiceName = "bmcweb";
51 static constexpr std::string_view ipmiServiceName = "phosphor-ipmi-net";
52
53 // Mapping from Redfish NetworkProtocol key name to backend service that hosts
54 // that protocol.
55 static constexpr std::array<std::pair<std::string_view, std::string_view>, 3>
56 networkProtocolToDbus = {{{"SSH", sshServiceName},
57 {"HTTPS", httpsServiceName},
58 {"IPMI", ipmiServiceName}}};
59
extractNTPServersAndDomainNamesData(const dbus::utility::ManagedObjectType & dbusData,std::vector<std::string> & ntpData,std::vector<std::string> & dynamicNtpData,std::vector<std::string> & dnData)60 inline void extractNTPServersAndDomainNamesData(
61 const dbus::utility::ManagedObjectType& dbusData,
62 std::vector<std::string>& ntpData, std::vector<std::string>& dynamicNtpData,
63 std::vector<std::string>& dnData)
64 {
65 for (const auto& obj : dbusData)
66 {
67 for (const auto& ifacePair : obj.second)
68 {
69 if (ifacePair.first !=
70 "xyz.openbmc_project.Network.EthernetInterface")
71 {
72 continue;
73 }
74
75 for (const auto& propertyPair : ifacePair.second)
76 {
77 if (propertyPair.first == "StaticNTPServers")
78 {
79 const std::vector<std::string>* ntpServers =
80 std::get_if<std::vector<std::string>>(
81 &propertyPair.second);
82 if (ntpServers != nullptr)
83 {
84 ntpData.insert(ntpData.end(), ntpServers->begin(),
85 ntpServers->end());
86 }
87 }
88 else if (propertyPair.first == "NTPServers")
89 {
90 const std::vector<std::string>* dynamicNtpServers =
91 std::get_if<std::vector<std::string>>(
92 &propertyPair.second);
93 if (dynamicNtpServers != nullptr)
94 {
95 dynamicNtpData = *dynamicNtpServers;
96 }
97 }
98 else if (propertyPair.first == "DomainName")
99 {
100 const std::vector<std::string>* domainNames =
101 std::get_if<std::vector<std::string>>(
102 &propertyPair.second);
103 if (domainNames != nullptr)
104 {
105 dnData.insert(dnData.end(), domainNames->begin(),
106 domainNames->end());
107 }
108 }
109 }
110 }
111 }
112 stl_utils::removeDuplicate(ntpData);
113 stl_utils::removeDuplicate(dnData);
114 }
115
116 template <typename CallbackFunc>
getEthernetIfaceData(CallbackFunc && callback)117 void getEthernetIfaceData(CallbackFunc&& callback)
118 {
119 sdbusplus::message::object_path path("/xyz/openbmc_project/network");
120 dbus::utility::getManagedObjects(
121 "xyz.openbmc_project.Network", path,
122 [callback = std::forward<CallbackFunc>(callback)](
123 const boost::system::error_code& ec,
124 const dbus::utility::ManagedObjectType& dbusData) {
125 std::vector<std::string> ntpServers;
126 std::vector<std::string> dynamicNtpServers;
127 std::vector<std::string> domainNames;
128
129 if (ec)
130 {
131 callback(false, ntpServers, dynamicNtpServers, domainNames);
132 return;
133 }
134
135 extractNTPServersAndDomainNamesData(dbusData, ntpServers,
136 dynamicNtpServers, domainNames);
137
138 callback(true, ntpServers, dynamicNtpServers, domainNames);
139 });
140 }
141
afterNetworkPortRequest(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec,const std::vector<std::tuple<std::string,std::string,bool>> & socketData)142 inline void afterNetworkPortRequest(
143 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
144 const boost::system::error_code& ec,
145 const std::vector<std::tuple<std::string, std::string, bool>>& socketData)
146 {
147 if (ec)
148 {
149 messages::internalError(asyncResp->res);
150 return;
151 }
152 for (const auto& data : socketData)
153 {
154 const std::string& socketPath = get<0>(data);
155 const std::string& protocolName = get<1>(data);
156 bool isProtocolEnabled = get<2>(data);
157
158 asyncResp->res.jsonValue[protocolName]["ProtocolEnabled"] =
159 isProtocolEnabled;
160 asyncResp->res.jsonValue[protocolName]["Port"] = nullptr;
161 getPortNumber(socketPath, [asyncResp, protocolName](
162 const boost::system::error_code& ec2,
163 int portNumber) {
164 if (ec2)
165 {
166 messages::internalError(asyncResp->res);
167 return;
168 }
169 asyncResp->res.jsonValue[protocolName]["Port"] = portNumber;
170 });
171 }
172 }
173
getNetworkData(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const crow::Request & req)174 inline void getNetworkData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
175 const crow::Request& req)
176 {
177 if (req.session == nullptr)
178 {
179 messages::internalError(asyncResp->res);
180 return;
181 }
182
183 asyncResp->res.addHeader(
184 boost::beast::http::field::link,
185 "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/NetworkProtocol.json>; rel=describedby");
186 asyncResp->res.jsonValue["@odata.type"] =
187 "#ManagerNetworkProtocol.v1_9_0.ManagerNetworkProtocol";
188 asyncResp->res.jsonValue["@odata.id"] =
189 boost::urls::format("/redfish/v1/Managers/{}/NetworkProtocol",
190 BMCWEB_REDFISH_MANAGER_URI_NAME);
191 asyncResp->res.jsonValue["Id"] = "NetworkProtocol";
192 asyncResp->res.jsonValue["Name"] = "Manager Network Protocol";
193 asyncResp->res.jsonValue["Description"] = "Manager Network Service";
194 asyncResp->res.jsonValue["Status"]["Health"] = resource::Health::OK;
195 asyncResp->res.jsonValue["Status"]["HealthRollup"] = resource::Health::OK;
196 asyncResp->res.jsonValue["Status"]["State"] = resource::State::Enabled;
197
198 // HTTP is Mandatory attribute as per OCP Baseline Profile - v1.0.0,
199 // but from security perspective it is not recommended to use.
200 // Hence using protocolEnabled as false to make it OCP and security-wise
201 // compliant
202 asyncResp->res.jsonValue["HTTP"]["Port"] = nullptr;
203 asyncResp->res.jsonValue["HTTP"]["ProtocolEnabled"] = false;
204
205 // The ProtocolEnabled of the following protocols is determined by
206 // inspecting the state of associated systemd sockets. If these protocols
207 // have been disabled, then the systemd socket unit files will not be found
208 // and the protocols will not be returned in this Redfish query. Set some
209 // defaults to ensure something is always returned.
210 for (const auto& nwkProtocol : networkProtocolToDbus)
211 {
212 asyncResp->res.jsonValue[nwkProtocol.first]["Port"] = nullptr;
213 asyncResp->res.jsonValue[nwkProtocol.first]["ProtocolEnabled"] = false;
214 }
215
216 std::string hostName = getHostName();
217
218 asyncResp->res.jsonValue["HostName"] = hostName;
219
220 getNTPProtocolEnabled(asyncResp);
221
222 getEthernetIfaceData([hostName, asyncResp](
223 const bool& success,
224 const std::vector<std::string>& ntpServers,
225 const std::vector<std::string>& dynamicNtpServers,
226 const std::vector<std::string>& domainNames) {
227 if (!success)
228 {
229 messages::resourceNotFound(asyncResp->res, "ManagerNetworkProtocol",
230 "NetworkProtocol");
231 return;
232 }
233 asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers;
234 asyncResp->res.jsonValue["NTP"]["NetworkSuppliedServers"] =
235 dynamicNtpServers;
236 if (!hostName.empty())
237 {
238 std::string fqdn = hostName;
239 if (!domainNames.empty())
240 {
241 fqdn += ".";
242 fqdn += domainNames[0];
243 }
244 asyncResp->res.jsonValue["FQDN"] = std::move(fqdn);
245 }
246 });
247
248 Privileges effectiveUserPrivileges =
249 redfish::getUserPrivileges(*req.session);
250
251 // /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates is
252 // something only ConfigureManager can access then only display when
253 // the user has permissions ConfigureManager
254 if (isOperationAllowedWithPrivileges({{"ConfigureManager"}},
255 effectiveUserPrivileges))
256 {
257 asyncResp->res.jsonValue["HTTPS"]["Certificates"]["@odata.id"] =
258 boost::urls::format(
259 "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates",
260 BMCWEB_REDFISH_MANAGER_URI_NAME);
261 }
262
263 getPortStatusAndPath(std::span(networkProtocolToDbus),
264 std::bind_front(afterNetworkPortRequest, asyncResp));
265 } // namespace redfish
266
afterSetNTP(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec)267 inline void afterSetNTP(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
268 const boost::system::error_code& ec)
269 {
270 if (ec)
271 {
272 BMCWEB_LOG_DEBUG("Failed to set elapsed time. DBUS response error {}",
273 ec);
274 messages::internalError(asyncResp->res);
275 return;
276 }
277 asyncResp->res.result(boost::beast::http::status::no_content);
278 }
279
handleNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,bool ntpEnabled)280 inline void handleNTPProtocolEnabled(
281 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, bool ntpEnabled)
282 {
283 bool interactive = false;
284 auto callback = [asyncResp](const boost::system::error_code& ec) {
285 afterSetNTP(asyncResp, ec);
286 };
287 crow::connections::systemBus->async_method_call(
288 std::move(callback), "org.freedesktop.timedate1",
289 "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "SetNTP",
290 ntpEnabled, interactive);
291 }
292
293 // Redfish states that ip addresses can be
294 // string, to set a value
295 // null, to delete the value
296 // object_t, empty json object, to ignore the value
297 using IpAddress =
298 std::variant<std::string, nlohmann::json::object_t, std::nullptr_t>;
299
handleNTPServersPatch(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::vector<IpAddress> & ntpServerObjects,std::vector<std::string> currentNtpServers)300 inline void handleNTPServersPatch(
301 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
302 const std::vector<IpAddress>& ntpServerObjects,
303 std::vector<std::string> currentNtpServers)
304 {
305 std::vector<std::string>::iterator currentNtpServer =
306 currentNtpServers.begin();
307 for (size_t index = 0; index < ntpServerObjects.size(); index++)
308 {
309 const IpAddress& ntpServer = ntpServerObjects[index];
310 if (std::holds_alternative<std::nullptr_t>(ntpServer))
311 {
312 // Can't delete an item that doesn't exist
313 if (currentNtpServer == currentNtpServers.end())
314 {
315 messages::propertyValueNotInList(
316 asyncResp->res, "null",
317 "NTP/NTPServers/" + std::to_string(index));
318
319 return;
320 }
321 currentNtpServer = currentNtpServers.erase(currentNtpServer);
322 continue;
323 }
324 const nlohmann::json::object_t* ntpServerObject =
325 std::get_if<nlohmann::json::object_t>(&ntpServer);
326 if (ntpServerObject != nullptr)
327 {
328 if (!ntpServerObject->empty())
329 {
330 messages::propertyValueNotInList(
331 asyncResp->res, *ntpServerObject,
332 "NTP/NTPServers/" + std::to_string(index));
333 return;
334 }
335 // Can't retain an item that doesn't exist
336 if (currentNtpServer == currentNtpServers.end())
337 {
338 messages::propertyValueOutOfRange(
339 asyncResp->res, *ntpServerObject,
340 "NTP/NTPServers/" + std::to_string(index));
341
342 return;
343 }
344 // empty objects should leave the NtpServer unmodified
345 currentNtpServer++;
346 continue;
347 }
348
349 const std::string* ntpServerStr = std::get_if<std::string>(&ntpServer);
350 if (ntpServerStr == nullptr)
351 {
352 messages::internalError(asyncResp->res);
353 return;
354 }
355 if (currentNtpServer == currentNtpServers.end())
356 {
357 // if we're at the end of the list, append to the end
358 currentNtpServers.push_back(*ntpServerStr);
359 currentNtpServer = currentNtpServers.end();
360 continue;
361 }
362 *currentNtpServer = *ntpServerStr;
363 currentNtpServer++;
364 }
365
366 // Any remaining array elements should be removed
367 currentNtpServers.erase(currentNtpServer, currentNtpServers.end());
368
369 constexpr std::array<std::string_view, 1> ethInterfaces = {
370 "xyz.openbmc_project.Network.EthernetInterface"};
371 dbus::utility::getSubTree(
372 "/xyz/openbmc_project", 0, ethInterfaces,
373 [asyncResp, currentNtpServers](
374 const boost::system::error_code& ec,
375 const dbus::utility::MapperGetSubTreeResponse& subtree) {
376 if (ec)
377 {
378 BMCWEB_LOG_WARNING("D-Bus error: {}, {}", ec, ec.message());
379 messages::internalError(asyncResp->res);
380 return;
381 }
382
383 for (const auto& [objectPath, serviceMap] : subtree)
384 {
385 for (const auto& [service, interfaces] : serviceMap)
386 {
387 for (const auto& interface : interfaces)
388 {
389 if (interface !=
390 "xyz.openbmc_project.Network.EthernetInterface")
391 {
392 continue;
393 }
394
395 setDbusProperty(asyncResp, "NTP/NTPServers/", service,
396 objectPath, interface,
397 "StaticNTPServers", currentNtpServers);
398 }
399 }
400 }
401 });
402 }
403
handleProtocolEnabled(const bool protocolEnabled,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & netBasePath)404 inline void handleProtocolEnabled(
405 const bool protocolEnabled,
406 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
407 const std::string& netBasePath)
408 {
409 constexpr std::array<std::string_view, 1> interfaces = {
410 "xyz.openbmc_project.Control.Service.Attributes"};
411 dbus::utility::getSubTree(
412 "/xyz/openbmc_project/control/service", 0, interfaces,
413 [protocolEnabled, asyncResp,
414 netBasePath](const boost::system::error_code& ec,
415 const dbus::utility::MapperGetSubTreeResponse& subtree) {
416 if (ec)
417 {
418 messages::internalError(asyncResp->res);
419 return;
420 }
421
422 for (const auto& entry : subtree)
423 {
424 if (entry.first.starts_with(netBasePath))
425 {
426 setDbusProperty(
427 asyncResp, "IPMI/ProtocolEnabled",
428 entry.second.begin()->first, entry.first,
429 "xyz.openbmc_project.Control.Service.Attributes",
430 "Running", protocolEnabled);
431 setDbusProperty(
432 asyncResp, "IPMI/ProtocolEnabled",
433 entry.second.begin()->first, entry.first,
434 "xyz.openbmc_project.Control.Service.Attributes",
435 "Enabled", protocolEnabled);
436 }
437 }
438 });
439 }
440
getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)441 inline void getNTPProtocolEnabled(
442 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
443 {
444 dbus::utility::getProperty<bool>(
445 "org.freedesktop.timedate1", "/org/freedesktop/timedate1",
446 "org.freedesktop.timedate1", "NTP",
447 [asyncResp](const boost::system::error_code& ec, bool enabled) {
448 if (ec)
449 {
450 BMCWEB_LOG_WARNING(
451 "Failed to get NTP status, assuming not supported");
452 return;
453 }
454
455 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = enabled;
456 });
457 }
458
encodeServiceObjectPath(std::string_view serviceName)459 inline std::string encodeServiceObjectPath(std::string_view serviceName)
460 {
461 sdbusplus::message::object_path objPath(
462 "/xyz/openbmc_project/control/service");
463 objPath /= serviceName;
464 return objPath.str;
465 }
466
handleBmcNetworkProtocolHead(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)467 inline void handleBmcNetworkProtocolHead(
468 crow::App& app, const crow::Request& req,
469 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
470 {
471 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
472 {
473 return;
474 }
475 asyncResp->res.addHeader(
476 boost::beast::http::field::link,
477 "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby");
478 }
479
handleManagersNetworkProtocolPatch(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)480 inline void handleManagersNetworkProtocolPatch(
481 App& app, const crow::Request& req,
482 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
483 const std::string& managerId)
484 {
485 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
486 {
487 return;
488 }
489
490 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
491 {
492 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
493 return;
494 }
495
496 std::optional<std::string> newHostName;
497
498 std::optional<std::vector<IpAddress>> ntpServerObjects;
499 std::optional<bool> ntpEnabled;
500 std::optional<bool> ipmiEnabled;
501 std::optional<bool> sshEnabled;
502
503 if (!json_util::readJsonPatch(
504 req, asyncResp->res, //
505 "HostName", newHostName, //
506 "NTP/NTPServers", ntpServerObjects, //
507 "NTP/ProtocolEnabled", ntpEnabled, //
508 "IPMI/ProtocolEnabled", ipmiEnabled, //
509 "SSH/ProtocolEnabled", sshEnabled //
510 ))
511 {
512 return;
513 }
514
515 asyncResp->res.result(boost::beast::http::status::no_content);
516 if (newHostName)
517 {
518 messages::propertyNotWritable(asyncResp->res, "HostName");
519 return;
520 }
521
522 if (ntpEnabled)
523 {
524 handleNTPProtocolEnabled(asyncResp, *ntpEnabled);
525 }
526 if (ntpServerObjects)
527 {
528 getEthernetIfaceData(
529 [asyncResp, ntpServerObjects](
530 const bool success, std::vector<std::string>& currentNtpServers,
531 const std::vector<std::string>& /*dynamicNtpServers*/,
532 const std::vector<std::string>& /*domainNames*/) {
533 if (!success)
534 {
535 messages::internalError(asyncResp->res);
536 return;
537 }
538 handleNTPServersPatch(asyncResp, *ntpServerObjects,
539 std::move(currentNtpServers));
540 });
541 }
542
543 if (ipmiEnabled)
544 {
545 handleProtocolEnabled(
546 *ipmiEnabled, asyncResp,
547 encodeServiceObjectPath(std::string(ipmiServiceName) + '@'));
548 }
549
550 if (sshEnabled)
551 {
552 handleProtocolEnabled(*sshEnabled, asyncResp,
553 encodeServiceObjectPath(sshServiceName));
554 }
555 }
556
handleManagersNetworkProtocolHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)557 inline void handleManagersNetworkProtocolHead(
558 App& app, const crow::Request& req,
559 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
560 const std::string& managerId)
561 {
562 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
563 {
564 return;
565 }
566 asyncResp->res.addHeader(
567 boost::beast::http::field::link,
568 "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby");
569 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
570 {
571 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
572 return;
573 }
574 }
575
handleManagersNetworkProtocolGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)576 inline void handleManagersNetworkProtocolGet(
577 App& app, const crow::Request& req,
578 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
579 const std::string& managerId)
580 {
581 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
582 {
583 return;
584 }
585 asyncResp->res.addHeader(
586 boost::beast::http::field::link,
587 "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby");
588 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
589 {
590 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
591 return;
592 }
593
594 getNetworkData(asyncResp, req);
595 }
596
requestRoutesNetworkProtocol(App & app)597 inline void requestRoutesNetworkProtocol(App& app)
598 {
599 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/")
600 .privileges(redfish::privileges::patchManagerNetworkProtocol)
601 .methods(boost::beast::http::verb::patch)(
602 std::bind_front(handleManagersNetworkProtocolPatch, std::ref(app)));
603
604 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/")
605 .privileges(redfish::privileges::headManagerNetworkProtocol)
606 .methods(boost::beast::http::verb::head)(
607 std::bind_front(handleManagersNetworkProtocolHead, std::ref(app)));
608
609 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/")
610 .privileges(redfish::privileges::getManagerNetworkProtocol)
611 .methods(boost::beast::http::verb::get)(
612 std::bind_front(handleManagersNetworkProtocolGet, std::ref(app)));
613 }
614
615 } // namespace redfish
616