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