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