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 } // namespace redfish
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 asyncResp->res.result(boost::beast::http::status::no_content);
277 }
278
handleNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,bool ntpEnabled)279 inline void handleNTPProtocolEnabled(
280 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, bool ntpEnabled)
281 {
282 bool interactive = false;
283 auto callback = [asyncResp](const boost::system::error_code& ec) {
284 afterSetNTP(asyncResp, ec);
285 };
286 dbus::utility::async_method_call(
287 asyncResp, std::move(callback), "org.freedesktop.timedate1",
288 "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "SetNTP",
289 ntpEnabled, interactive);
290 }
291
292 // Redfish states that ip addresses can be
293 // string, to set a value
294 // null, to delete the value
295 // object_t, empty json object, to ignore the value
296 using IpAddress =
297 std::variant<std::string, nlohmann::json::object_t, std::nullptr_t>;
298
handleNTPServersPatch(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::vector<IpAddress> & ntpServerObjects,std::vector<std::string> currentNtpServers)299 inline void handleNTPServersPatch(
300 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
301 const std::vector<IpAddress>& ntpServerObjects,
302 std::vector<std::string> currentNtpServers)
303 {
304 std::vector<std::string>::iterator currentNtpServer =
305 currentNtpServers.begin();
306 for (size_t index = 0; index < ntpServerObjects.size(); index++)
307 {
308 const IpAddress& ntpServer = ntpServerObjects[index];
309 if (std::holds_alternative<std::nullptr_t>(ntpServer))
310 {
311 // Can't delete an item that doesn't exist
312 if (currentNtpServer == currentNtpServers.end())
313 {
314 messages::propertyValueNotInList(
315 asyncResp->res, "null",
316 "NTP/NTPServers/" + std::to_string(index));
317
318 return;
319 }
320 currentNtpServer = currentNtpServers.erase(currentNtpServer);
321 continue;
322 }
323 const nlohmann::json::object_t* ntpServerObject =
324 std::get_if<nlohmann::json::object_t>(&ntpServer);
325 if (ntpServerObject != nullptr)
326 {
327 if (!ntpServerObject->empty())
328 {
329 messages::propertyValueNotInList(
330 asyncResp->res, *ntpServerObject,
331 "NTP/NTPServers/" + std::to_string(index));
332 return;
333 }
334 // Can't retain an item that doesn't exist
335 if (currentNtpServer == currentNtpServers.end())
336 {
337 messages::propertyValueOutOfRange(
338 asyncResp->res, *ntpServerObject,
339 "NTP/NTPServers/" + std::to_string(index));
340
341 return;
342 }
343 // empty objects should leave the NtpServer unmodified
344 currentNtpServer++;
345 continue;
346 }
347
348 const std::string* ntpServerStr = std::get_if<std::string>(&ntpServer);
349 if (ntpServerStr == nullptr)
350 {
351 messages::internalError(asyncResp->res);
352 return;
353 }
354 if (currentNtpServer == currentNtpServers.end())
355 {
356 // if we're at the end of the list, append to the end
357 currentNtpServers.push_back(*ntpServerStr);
358 currentNtpServer = currentNtpServers.end();
359 continue;
360 }
361 *currentNtpServer = *ntpServerStr;
362 currentNtpServer++;
363 }
364
365 // Any remaining array elements should be removed
366 currentNtpServers.erase(currentNtpServer, currentNtpServers.end());
367
368 constexpr std::array<std::string_view, 1> ethInterfaces = {
369 "xyz.openbmc_project.Network.EthernetInterface"};
370 dbus::utility::getSubTree(
371 "/xyz/openbmc_project", 0, ethInterfaces,
372 [asyncResp, currentNtpServers](
373 const boost::system::error_code& ec,
374 const dbus::utility::MapperGetSubTreeResponse& subtree) {
375 if (ec)
376 {
377 BMCWEB_LOG_WARNING("D-Bus error: {}, {}", ec, ec.message());
378 messages::internalError(asyncResp->res);
379 return;
380 }
381
382 for (const auto& [objectPath, serviceMap] : subtree)
383 {
384 for (const auto& [service, interfaces] : serviceMap)
385 {
386 for (const auto& interface : interfaces)
387 {
388 if (interface !=
389 "xyz.openbmc_project.Network.EthernetInterface")
390 {
391 continue;
392 }
393
394 setDbusProperty(asyncResp, "NTP/NTPServers/", service,
395 objectPath, interface,
396 "StaticNTPServers", currentNtpServers);
397 }
398 }
399 }
400 });
401 }
402
handleProtocolEnabled(const bool protocolEnabled,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & netBasePath)403 inline void handleProtocolEnabled(
404 const bool protocolEnabled,
405 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
406 const std::string& netBasePath)
407 {
408 constexpr std::array<std::string_view, 1> interfaces = {
409 "xyz.openbmc_project.Control.Service.Attributes"};
410 dbus::utility::getSubTree(
411 "/xyz/openbmc_project/control/service", 0, interfaces,
412 [protocolEnabled, asyncResp,
413 netBasePath](const boost::system::error_code& ec,
414 const dbus::utility::MapperGetSubTreeResponse& subtree) {
415 if (ec)
416 {
417 messages::internalError(asyncResp->res);
418 return;
419 }
420
421 for (const auto& entry : subtree)
422 {
423 if (entry.first.starts_with(netBasePath))
424 {
425 setDbusProperty(
426 asyncResp, "IPMI/ProtocolEnabled",
427 entry.second.begin()->first, entry.first,
428 "xyz.openbmc_project.Control.Service.Attributes",
429 "Running", protocolEnabled);
430 setDbusProperty(
431 asyncResp, "IPMI/ProtocolEnabled",
432 entry.second.begin()->first, entry.first,
433 "xyz.openbmc_project.Control.Service.Attributes",
434 "Enabled", protocolEnabled);
435 }
436 }
437 });
438 }
439
getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)440 inline void getNTPProtocolEnabled(
441 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
442 {
443 dbus::utility::getProperty<bool>(
444 "org.freedesktop.timedate1", "/org/freedesktop/timedate1",
445 "org.freedesktop.timedate1", "NTP",
446 [asyncResp](const boost::system::error_code& ec, bool enabled) {
447 if (ec)
448 {
449 BMCWEB_LOG_WARNING(
450 "Failed to get NTP status, assuming not supported");
451 return;
452 }
453
454 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = enabled;
455 });
456 }
457
encodeServiceObjectPath(std::string_view serviceName)458 inline std::string encodeServiceObjectPath(std::string_view serviceName)
459 {
460 sdbusplus::message::object_path objPath(
461 "/xyz/openbmc_project/control/service");
462 objPath /= serviceName;
463 return objPath.str;
464 }
465
handleBmcNetworkProtocolHead(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)466 inline void handleBmcNetworkProtocolHead(
467 crow::App& app, const crow::Request& req,
468 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
469 {
470 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
471 {
472 return;
473 }
474 asyncResp->res.addHeader(
475 boost::beast::http::field::link,
476 "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby");
477 }
478
handleManagersNetworkProtocolPatch(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)479 inline void handleManagersNetworkProtocolPatch(
480 App& app, const crow::Request& req,
481 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
482 const std::string& managerId)
483 {
484 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
485 {
486 return;
487 }
488
489 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
490 {
491 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
492 return;
493 }
494
495 std::optional<std::string> newHostName;
496
497 std::optional<std::vector<IpAddress>> ntpServerObjects;
498 std::optional<bool> ntpEnabled;
499 std::optional<bool> ipmiEnabled;
500 std::optional<bool> sshEnabled;
501
502 if (!json_util::readJsonPatch(
503 req, asyncResp->res, //
504 "HostName", newHostName, //
505 "NTP/NTPServers", ntpServerObjects, //
506 "NTP/ProtocolEnabled", ntpEnabled, //
507 "IPMI/ProtocolEnabled", ipmiEnabled, //
508 "SSH/ProtocolEnabled", sshEnabled //
509 ))
510 {
511 return;
512 }
513
514 asyncResp->res.result(boost::beast::http::status::no_content);
515 if (newHostName)
516 {
517 messages::propertyNotWritable(asyncResp->res, "HostName");
518 return;
519 }
520
521 if (ntpEnabled)
522 {
523 handleNTPProtocolEnabled(asyncResp, *ntpEnabled);
524 }
525 if (ntpServerObjects)
526 {
527 getEthernetIfaceData(
528 [asyncResp, ntpServerObjects](
529 const bool success, std::vector<std::string>& currentNtpServers,
530 const std::vector<std::string>& /*dynamicNtpServers*/,
531 const std::vector<std::string>& /*domainNames*/) {
532 if (!success)
533 {
534 messages::internalError(asyncResp->res);
535 return;
536 }
537 handleNTPServersPatch(asyncResp, *ntpServerObjects,
538 std::move(currentNtpServers));
539 });
540 }
541
542 if (ipmiEnabled)
543 {
544 handleProtocolEnabled(
545 *ipmiEnabled, asyncResp,
546 encodeServiceObjectPath(std::string(ipmiServiceName) + '@'));
547 }
548
549 if (sshEnabled)
550 {
551 handleProtocolEnabled(*sshEnabled, asyncResp,
552 encodeServiceObjectPath(sshServiceName));
553 }
554 }
555
handleManagersNetworkProtocolHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)556 inline void handleManagersNetworkProtocolHead(
557 App& app, const crow::Request& req,
558 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
559 const std::string& managerId)
560 {
561 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
562 {
563 return;
564 }
565 asyncResp->res.addHeader(
566 boost::beast::http::field::link,
567 "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby");
568 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
569 {
570 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
571 return;
572 }
573 }
574
handleManagersNetworkProtocolGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)575 inline void handleManagersNetworkProtocolGet(
576 App& app, const crow::Request& req,
577 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
578 const std::string& managerId)
579 {
580 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
581 {
582 return;
583 }
584 asyncResp->res.addHeader(
585 boost::beast::http::field::link,
586 "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby");
587 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
588 {
589 messages::resourceNotFound(asyncResp->res, "Manager", managerId);
590 return;
591 }
592
593 getNetworkData(asyncResp, req);
594 }
595
requestRoutesNetworkProtocol(App & app)596 inline void requestRoutesNetworkProtocol(App& app)
597 {
598 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/")
599 .privileges(redfish::privileges::patchManagerNetworkProtocol)
600 .methods(boost::beast::http::verb::patch)(
601 std::bind_front(handleManagersNetworkProtocolPatch, std::ref(app)));
602
603 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/")
604 .privileges(redfish::privileges::headManagerNetworkProtocol)
605 .methods(boost::beast::http::verb::head)(
606 std::bind_front(handleManagersNetworkProtocolHead, std::ref(app)));
607
608 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/NetworkProtocol/")
609 .privileges(redfish::privileges::getManagerNetworkProtocol)
610 .methods(boost::beast::http::verb::get)(
611 std::bind_front(handleManagersNetworkProtocolGet, std::ref(app)));
612 }
613
614 } // namespace redfish
615