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