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 "openbmc_dbus_rest.hpp"
22 #include "query.hpp"
23 #include "redfish_util.hpp"
24 #include "registries/privilege_registry.hpp"
25 #include "utils/json_utils.hpp"
26 #include "utils/stl_utils.hpp"
27 
28 #include <boost/system/error_code.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 
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>
99 void getEthernetIfaceData(CallbackFunc&& callback)
100 {
101     crow::connections::systemBus->async_method_call(
102         [callback{std::forward<CallbackFunc>(callback)}](
103             const boost::system::error_code& errorCode,
104             const dbus::utility::ManagedObjectType& dbusData) {
105         std::vector<std::string> ntpServers;
106         std::vector<std::string> domainNames;
107 
108         if (errorCode)
109         {
110             callback(false, ntpServers, domainNames);
111             return;
112         }
113 
114         extractNTPServersAndDomainNamesData(dbusData, ntpServers, domainNames);
115 
116         callback(true, ntpServers, domainNames);
117         },
118         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
119         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
120 }
121 
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 
154 inline void getNetworkData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
155                            const crow::Request& req)
156 {
157     asyncResp->res.addHeader(
158         boost::beast::http::field::link,
159         "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/NetworkProtocol.json>; rel=describedby");
160     asyncResp->res.jsonValue["@odata.type"] =
161         "#ManagerNetworkProtocol.v1_5_0.ManagerNetworkProtocol";
162     asyncResp->res.jsonValue["@odata.id"] =
163         "/redfish/v1/Managers/bmc/NetworkProtocol";
164     asyncResp->res.jsonValue["Id"] = "NetworkProtocol";
165     asyncResp->res.jsonValue["Name"] = "Manager Network Protocol";
166     asyncResp->res.jsonValue["Description"] = "Manager Network Service";
167     asyncResp->res.jsonValue["Status"]["Health"] = "OK";
168     asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
169     asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
170 
171     // HTTP is Mandatory attribute as per OCP Baseline Profile - v1.0.0,
172     // but from security perspective it is not recommended to use.
173     // Hence using protocolEnabled as false to make it OCP and security-wise
174     // compliant
175     asyncResp->res.jsonValue["HTTP"]["Port"] = nullptr;
176     asyncResp->res.jsonValue["HTTP"]["ProtocolEnabled"] = false;
177 
178     std::string hostName = getHostName();
179 
180     asyncResp->res.jsonValue["HostName"] = hostName;
181 
182     getNTPProtocolEnabled(asyncResp);
183 
184     getEthernetIfaceData(
185         [hostName, asyncResp](const bool& success,
186                               const std::vector<std::string>& ntpServers,
187                               const std::vector<std::string>& domainNames) {
188         if (!success)
189         {
190             messages::resourceNotFound(asyncResp->res, "ManagerNetworkProtocol",
191                                        "NetworkProtocol");
192             return;
193         }
194         asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers;
195         if (!hostName.empty())
196         {
197             std::string fqdn = hostName;
198             if (!domainNames.empty())
199             {
200                 fqdn += ".";
201                 fqdn += domainNames[0];
202             }
203             asyncResp->res.jsonValue["FQDN"] = std::move(fqdn);
204         }
205     });
206 
207     Privileges effectiveUserPrivileges =
208         redfish::getUserPrivileges(req.userRole);
209 
210     // /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates is
211     // something only ConfigureManager can access then only display when
212     // the user has permissions ConfigureManager
213     if (isOperationAllowedWithPrivileges({{"ConfigureManager"}},
214                                          effectiveUserPrivileges))
215     {
216         asyncResp->res.jsonValue["HTTPS"]["Certificates"]["@odata.id"] =
217             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates";
218     }
219 
220     getPortStatusAndPath(std::span(networkProtocolToDbus),
221                          std::bind_front(afterNetworkPortRequest, asyncResp));
222 } // namespace redfish
223 
224 inline void handleNTPProtocolEnabled(
225     const bool& ntpEnabled, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
226 {
227     std::string timeSyncMethod;
228     if (ntpEnabled)
229     {
230         timeSyncMethod = "xyz.openbmc_project.Time.Synchronization.Method.NTP";
231     }
232     else
233     {
234         timeSyncMethod =
235             "xyz.openbmc_project.Time.Synchronization.Method.Manual";
236     }
237 
238     crow::connections::systemBus->async_method_call(
239         [asyncResp](const boost::system::error_code& errorCode) {
240         if (errorCode)
241         {
242             messages::internalError(asyncResp->res);
243         }
244         },
245         "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method",
246         "org.freedesktop.DBus.Properties", "Set",
247         "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod",
248         dbus::utility::DbusVariantType{timeSyncMethod});
249 }
250 
251 inline void
252     handleNTPServersPatch(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
253                           const std::vector<nlohmann::json>& ntpServerObjects,
254                           std::vector<std::string> currentNtpServers)
255 {
256     std::vector<std::string>::iterator currentNtpServer =
257         currentNtpServers.begin();
258     for (size_t index = 0; index < ntpServerObjects.size(); index++)
259     {
260         const nlohmann::json& ntpServer = ntpServerObjects[index];
261         if (ntpServer.is_null())
262         {
263             // Can't delete an item that doesn't exist
264             if (currentNtpServer == currentNtpServers.end())
265             {
266                 messages::propertyValueNotInList(asyncResp->res, "null",
267                                                  "NTP/NTPServers/" +
268                                                      std::to_string(index));
269 
270                 return;
271             }
272             currentNtpServer = currentNtpServers.erase(currentNtpServer);
273             continue;
274         }
275         const nlohmann::json::object_t* ntpServerObject =
276             ntpServer.get_ptr<const nlohmann::json::object_t*>();
277         if (ntpServerObject != nullptr)
278         {
279             if (!ntpServerObject->empty())
280             {
281                 messages::propertyValueNotInList(
282                     asyncResp->res,
283                     ntpServer.dump(2, ' ', true,
284                                    nlohmann::json::error_handler_t::replace),
285                     "NTP/NTPServers/" + std::to_string(index));
286                 return;
287             }
288             // Can't retain an item that doesn't exist
289             if (currentNtpServer == currentNtpServers.end())
290             {
291                 messages::propertyValueOutOfRange(
292                     asyncResp->res,
293                     ntpServer.dump(2, ' ', true,
294                                    nlohmann::json::error_handler_t::replace),
295                     "NTP/NTPServers/" + std::to_string(index));
296 
297                 return;
298             }
299             // empty objects should leave the NtpServer unmodified
300             currentNtpServer++;
301             continue;
302         }
303 
304         const std::string* ntpServerStr =
305             ntpServer.get_ptr<const std::string*>();
306         if (ntpServerStr == nullptr)
307         {
308             messages::propertyValueTypeError(
309                 asyncResp->res,
310                 ntpServer.dump(2, ' ', true,
311                                nlohmann::json::error_handler_t::replace),
312                 "NTP/NTPServers/" + std::to_string(index));
313             return;
314         }
315         if (currentNtpServer == currentNtpServers.end())
316         {
317             // if we're at the end of the list, append to the end
318             currentNtpServers.push_back(*ntpServerStr);
319             currentNtpServer = currentNtpServers.end();
320             continue;
321         }
322         *currentNtpServer = *ntpServerStr;
323         currentNtpServer++;
324     }
325 
326     // Any remaining array elements should be removed
327     currentNtpServers.erase(currentNtpServer, currentNtpServers.end());
328 
329     constexpr std::array<std::string_view, 1> ethInterfaces = {
330         "xyz.openbmc_project.Network.EthernetInterface"};
331     dbus::utility::getSubTree(
332         "/xyz/openbmc_project", 0, ethInterfaces,
333         [asyncResp, currentNtpServers](
334             const boost::system::error_code& ec,
335             const dbus::utility::MapperGetSubTreeResponse& subtree) {
336         if (ec)
337         {
338             BMCWEB_LOG_WARNING << "D-Bus error: " << ec << ", " << ec.message();
339             messages::internalError(asyncResp->res);
340             return;
341         }
342 
343         for (const auto& [objectPath, serviceMap] : subtree)
344         {
345             for (const auto& [service, interfaces] : serviceMap)
346             {
347                 for (const auto& interface : interfaces)
348                 {
349                     if (interface !=
350                         "xyz.openbmc_project.Network.EthernetInterface")
351                     {
352                         continue;
353                     }
354 
355                     crow::connections::systemBus->async_method_call(
356                         [asyncResp](const boost::system::error_code& ec2) {
357                         if (ec2)
358                         {
359                             messages::internalError(asyncResp->res);
360                             return;
361                         }
362                         },
363                         service, objectPath, "org.freedesktop.DBus.Properties",
364                         "Set", interface, "StaticNTPServers",
365                         dbus::utility::DbusVariantType{currentNtpServers});
366                 }
367             }
368         }
369         });
370 }
371 
372 inline void
373     handleProtocolEnabled(const bool protocolEnabled,
374                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
375                           const std::string& netBasePath)
376 {
377     constexpr std::array<std::string_view, 1> interfaces = {
378         "xyz.openbmc_project.Control.Service.Attributes"};
379     dbus::utility::getSubTree(
380         "/xyz/openbmc_project/control/service", 0, interfaces,
381         [protocolEnabled, asyncResp,
382          netBasePath](const boost::system::error_code& ec,
383                       const dbus::utility::MapperGetSubTreeResponse& subtree) {
384         if (ec)
385         {
386             messages::internalError(asyncResp->res);
387             return;
388         }
389 
390         for (const auto& entry : subtree)
391         {
392             if (boost::algorithm::starts_with(entry.first, netBasePath))
393             {
394                 crow::connections::systemBus->async_method_call(
395                     [asyncResp](const boost::system::error_code& ec2) {
396                     if (ec2)
397                     {
398                         messages::internalError(asyncResp->res);
399                         return;
400                     }
401                     },
402                     entry.second.begin()->first, entry.first,
403                     "org.freedesktop.DBus.Properties", "Set",
404                     "xyz.openbmc_project.Control.Service.Attributes", "Running",
405                     dbus::utility::DbusVariantType{protocolEnabled});
406 
407                 crow::connections::systemBus->async_method_call(
408                     [asyncResp](const boost::system::error_code& ec2) {
409                     if (ec2)
410                     {
411                         messages::internalError(asyncResp->res);
412                         return;
413                     }
414                     },
415                     entry.second.begin()->first, entry.first,
416                     "org.freedesktop.DBus.Properties", "Set",
417                     "xyz.openbmc_project.Control.Service.Attributes", "Enabled",
418                     dbus::utility::DbusVariantType{protocolEnabled});
419             }
420         }
421         });
422 }
423 
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
437     getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
438 {
439     sdbusplus::asio::getProperty<std::string>(
440         *crow::connections::systemBus, "xyz.openbmc_project.Settings",
441         "/xyz/openbmc_project/time/sync_method",
442         "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod",
443         [asyncResp](const boost::system::error_code& errorCode,
444                     const std::string& timeSyncMethod) {
445         if (errorCode)
446         {
447             return;
448         }
449 
450         if (timeSyncMethod ==
451             "xyz.openbmc_project.Time.Synchronization.Method.NTP")
452         {
453             asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true;
454         }
455         else if (timeSyncMethod == "xyz.openbmc_project.Time.Synchronization."
456                                    "Method.Manual")
457         {
458             asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false;
459         }
460         });
461 }
462 
463 inline std::string encodeServiceObjectPath(std::string_view serviceName)
464 {
465     sdbusplus::message::object_path objPath(
466         "/xyz/openbmc_project/control/service");
467     objPath /= serviceName;
468     return objPath.str;
469 }
470 
471 inline void handleBmcNetworkProtocolHead(
472     crow::App& app, const crow::Request& req,
473     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
474 {
475     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
476     {
477         return;
478     }
479     asyncResp->res.addHeader(
480         boost::beast::http::field::link,
481         "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby");
482 }
483 
484 inline void handleManagersNetworkProtocolPatch(
485     App& app, const crow::Request& req,
486     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
487 {
488     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
489     {
490         return;
491     }
492     std::optional<std::string> newHostName;
493     std::optional<std::vector<nlohmann::json>> ntpServerObjects;
494     std::optional<bool> ntpEnabled;
495     std::optional<bool> ipmiEnabled;
496     std::optional<bool> sshEnabled;
497 
498     // clang-format off
499         if (!json_util::readJsonPatch(
500                 req, asyncResp->res,
501                 "HostName", newHostName,
502                 "NTP/NTPServers", ntpServerObjects,
503                 "NTP/ProtocolEnabled", ntpEnabled,
504                 "IPMI/ProtocolEnabled", ipmiEnabled,
505                 "SSH/ProtocolEnabled", sshEnabled))
506         {
507             return;
508         }
509     // clang-format on
510 
511     asyncResp->res.result(boost::beast::http::status::no_content);
512     if (newHostName)
513     {
514         messages::propertyNotWritable(asyncResp->res, "HostName");
515         return;
516     }
517 
518     if (ntpEnabled)
519     {
520         handleNTPProtocolEnabled(*ntpEnabled, asyncResp);
521     }
522     if (ntpServerObjects)
523     {
524         getEthernetIfaceData(
525             [asyncResp, ntpServerObjects](
526                 const bool success, std::vector<std::string>& currentNtpServers,
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 
552 inline void handleManagersNetworkProtocolHead(
553     App& app, const crow::Request& req,
554     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
555 {
556     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
557     {
558         return;
559     }
560     asyncResp->res.addHeader(
561         boost::beast::http::field::link,
562         "</redfish/v1/JsonSchemas/ManagerNetworkProtocol/ManagerNetworkProtocol.json>; rel=describedby");
563 }
564 
565 inline void handleManagersNetworkProtocolGet(
566     App& app, const crow::Request& req,
567     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
568 {
569     handleManagersNetworkProtocolHead(app, req, asyncResp);
570     getNetworkData(asyncResp, req);
571 }
572 
573 inline void requestRoutesNetworkProtocol(App& app)
574 {
575     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
576         .privileges(redfish::privileges::patchManagerNetworkProtocol)
577         .methods(boost::beast::http::verb::patch)(
578             std::bind_front(handleManagersNetworkProtocolPatch, std::ref(app)));
579 
580     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
581         .privileges(redfish::privileges::headManagerNetworkProtocol)
582         .methods(boost::beast::http::verb::head)(
583             std::bind_front(handleManagersNetworkProtocolHead, std::ref(app)));
584 
585     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
586         .privileges(redfish::privileges::getManagerNetworkProtocol)
587         .methods(boost::beast::http::verb::get)(
588             std::bind_front(handleManagersNetworkProtocolGet, std::ref(app)));
589 }
590 
591 } // namespace redfish
592