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 "error_messages.hpp"
19 #include "openbmc_dbus_rest.hpp"
20 #include "redfish_util.hpp"
21 
22 #include <app.hpp>
23 #include <dbus_utility.hpp>
24 #include <query.hpp>
25 #include <registries/privilege_registry.hpp>
26 #include <sdbusplus/asio/property.hpp>
27 #include <utils/json_utils.hpp>
28 #include <utils/stl_utils.hpp>
29 
30 #include <optional>
31 #include <variant>
32 
33 namespace redfish
34 {
35 
36 void getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp);
37 std::string getHostName();
38 
39 static constexpr const char* sshServiceName = "dropbear";
40 static constexpr const char* httpsServiceName = "bmcweb";
41 static constexpr const char* ipmiServiceName = "phosphor-ipmi-net";
42 static constexpr std::array<std::pair<const char*, const char*>, 3>
43     protocolToService = {{{"SSH", sshServiceName},
44                           {"HTTPS", httpsServiceName},
45                           {"IPMI", ipmiServiceName}}};
46 
47 inline void extractNTPServersAndDomainNamesData(
48     const dbus::utility::ManagedObjectType& dbusData,
49     std::vector<std::string>& ntpData, std::vector<std::string>& dnData)
50 {
51     for (const auto& obj : dbusData)
52     {
53         for (const auto& ifacePair : obj.second)
54         {
55             if (ifacePair.first !=
56                 "xyz.openbmc_project.Network.EthernetInterface")
57             {
58                 continue;
59             }
60 
61             for (const auto& propertyPair : ifacePair.second)
62             {
63                 if (propertyPair.first == "NTPServers")
64                 {
65                     const std::vector<std::string>* ntpServers =
66                         std::get_if<std::vector<std::string>>(
67                             &propertyPair.second);
68                     if (ntpServers != nullptr)
69                     {
70                         ntpData = *ntpServers;
71                     }
72                 }
73                 else if (propertyPair.first == "DomainName")
74                 {
75                     const std::vector<std::string>* domainNames =
76                         std::get_if<std::vector<std::string>>(
77                             &propertyPair.second);
78                     if (domainNames != nullptr)
79                     {
80                         dnData = *domainNames;
81                     }
82                 }
83             }
84         }
85     }
86 }
87 
88 template <typename CallbackFunc>
89 void getEthernetIfaceData(CallbackFunc&& callback)
90 {
91     crow::connections::systemBus->async_method_call(
92         [callback{std::forward<CallbackFunc>(callback)}](
93             const boost::system::error_code errorCode,
94             const dbus::utility::ManagedObjectType& dbusData) {
95         std::vector<std::string> ntpServers;
96         std::vector<std::string> domainNames;
97 
98         if (errorCode)
99         {
100             callback(false, ntpServers, domainNames);
101             return;
102         }
103 
104         extractNTPServersAndDomainNamesData(dbusData, ntpServers, domainNames);
105 
106         callback(true, ntpServers, domainNames);
107         },
108         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
109         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
110 }
111 
112 inline void getNetworkData(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
113                            const crow::Request& req)
114 {
115     asyncResp->res.jsonValue["@odata.type"] =
116         "#ManagerNetworkProtocol.v1_5_0.ManagerNetworkProtocol";
117     asyncResp->res.jsonValue["@odata.id"] =
118         "/redfish/v1/Managers/bmc/NetworkProtocol";
119     asyncResp->res.jsonValue["Id"] = "NetworkProtocol";
120     asyncResp->res.jsonValue["Name"] = "Manager Network Protocol";
121     asyncResp->res.jsonValue["Description"] = "Manager Network Service";
122     asyncResp->res.jsonValue["Status"]["Health"] = "OK";
123     asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
124     asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
125 
126     // HTTP is Mandatory attribute as per OCP Baseline Profile - v1.0.0,
127     // but from security perspective it is not recommended to use.
128     // Hence using protocolEnabled as false to make it OCP and security-wise
129     // compliant
130     asyncResp->res.jsonValue["HTTP"]["Port"] = 0;
131     asyncResp->res.jsonValue["HTTP"]["ProtocolEnabled"] = false;
132 
133     std::string hostName = getHostName();
134 
135     asyncResp->res.jsonValue["HostName"] = hostName;
136 
137     getNTPProtocolEnabled(asyncResp);
138 
139     getEthernetIfaceData(
140         [hostName, asyncResp](const bool& success,
141                               std::vector<std::string>& ntpServers,
142                               const std::vector<std::string>& domainNames) {
143         if (!success)
144         {
145             messages::resourceNotFound(asyncResp->res, "ManagerNetworkProtocol",
146                                        "NetworkProtocol");
147             return;
148         }
149         stl_utils::removeDuplicate(ntpServers);
150         asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers;
151         if (!hostName.empty())
152         {
153             std::string fqdn = hostName;
154             if (!domainNames.empty())
155             {
156                 fqdn += ".";
157                 fqdn += domainNames[0];
158             }
159             asyncResp->res.jsonValue["FQDN"] = std::move(fqdn);
160         }
161     });
162 
163     Privileges effectiveUserPrivileges =
164         redfish::getUserPrivileges(req.userRole);
165 
166     // /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates is
167     // something only ConfigureManager can access then only display when
168     // the user has permissions ConfigureManager
169     if (isOperationAllowedWithPrivileges({{"ConfigureManager"}},
170                                          effectiveUserPrivileges))
171     {
172         asyncResp->res.jsonValue["HTTPS"]["Certificates"]["@odata.id"] =
173             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates";
174     }
175 
176     for (const auto& protocol : protocolToService)
177     {
178         const std::string& protocolName = protocol.first;
179         const std::string& serviceName = protocol.second;
180         getPortStatusAndPath(
181             serviceName,
182             [asyncResp, protocolName](const boost::system::error_code ec,
183                                       const std::string& socketPath,
184                                       bool isProtocolEnabled) {
185             // If the service is not installed, that is not an error
186             if (ec == boost::system::errc::no_such_process)
187             {
188                 asyncResp->res.jsonValue[protocolName]["Port"] =
189                     nlohmann::detail::value_t::null;
190                 asyncResp->res.jsonValue[protocolName]["ProtocolEnabled"] =
191                     false;
192                 return;
193             }
194             if (ec)
195             {
196                 messages::internalError(asyncResp->res);
197                 return;
198             }
199             asyncResp->res.jsonValue[protocolName]["ProtocolEnabled"] =
200                 isProtocolEnabled;
201             getPortNumber(socketPath, [asyncResp, protocolName](
202                                           const boost::system::error_code ec2,
203                                           int portNumber) {
204                 if (ec2)
205                 {
206                     messages::internalError(asyncResp->res);
207                     return;
208                 }
209                 asyncResp->res.jsonValue[protocolName]["Port"] = portNumber;
210             });
211             });
212     }
213 } // namespace redfish
214 
215 inline void handleNTPProtocolEnabled(
216     const bool& ntpEnabled, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
217 {
218     std::string timeSyncMethod;
219     if (ntpEnabled)
220     {
221         timeSyncMethod = "xyz.openbmc_project.Time.Synchronization.Method.NTP";
222     }
223     else
224     {
225         timeSyncMethod =
226             "xyz.openbmc_project.Time.Synchronization.Method.Manual";
227     }
228 
229     crow::connections::systemBus->async_method_call(
230         [asyncResp](const boost::system::error_code errorCode) {
231         if (errorCode)
232         {
233             messages::internalError(asyncResp->res);
234         }
235         },
236         "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method",
237         "org.freedesktop.DBus.Properties", "Set",
238         "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod",
239         dbus::utility::DbusVariantType{timeSyncMethod});
240 }
241 
242 inline void
243     handleNTPServersPatch(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
244                           std::vector<std::string>& ntpServers)
245 {
246     auto iter = stl_utils::firstDuplicate(ntpServers.begin(), ntpServers.end());
247     if (iter != ntpServers.end())
248     {
249         std::string pointer =
250             "NTPServers/" +
251             std::to_string(std::distance(ntpServers.begin(), iter));
252         messages::propertyValueIncorrect(asyncResp->res, pointer, *iter);
253         return;
254     }
255 
256     crow::connections::systemBus->async_method_call(
257         [asyncResp,
258          ntpServers](boost::system::error_code ec,
259                      const dbus::utility::MapperGetSubTreeResponse& subtree) {
260         if (ec)
261         {
262             BMCWEB_LOG_WARNING << "D-Bus error: " << ec << ", " << ec.message();
263             messages::internalError(asyncResp->res);
264             return;
265         }
266 
267         for (const auto& [objectPath, serviceMap] : subtree)
268         {
269             for (const auto& [service, interfaces] : serviceMap)
270             {
271                 for (const auto& interface : interfaces)
272                 {
273                     if (interface !=
274                         "xyz.openbmc_project.Network.EthernetInterface")
275                     {
276                         continue;
277                     }
278 
279                     crow::connections::systemBus->async_method_call(
280                         [asyncResp](const boost::system::error_code ec2) {
281                         if (ec2)
282                         {
283                             messages::internalError(asyncResp->res);
284                             return;
285                         }
286                         },
287                         service, objectPath, "org.freedesktop.DBus.Properties",
288                         "Set", interface, "NTPServers",
289                         dbus::utility::DbusVariantType{ntpServers});
290                 }
291             }
292         }
293         },
294         "xyz.openbmc_project.ObjectMapper",
295         "/xyz/openbmc_project/object_mapper",
296         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
297         "/xyz/openbmc_project", 0,
298         std::array<const char*, 1>{
299             "xyz.openbmc_project.Network.EthernetInterface"});
300 }
301 
302 inline void
303     handleProtocolEnabled(const bool protocolEnabled,
304                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
305                           const std::string& netBasePath)
306 {
307     crow::connections::systemBus->async_method_call(
308         [protocolEnabled, asyncResp,
309          netBasePath](const boost::system::error_code ec,
310                       const dbus::utility::MapperGetSubTreeResponse& subtree) {
311         if (ec)
312         {
313             messages::internalError(asyncResp->res);
314             return;
315         }
316 
317         for (const auto& entry : subtree)
318         {
319             if (boost::algorithm::starts_with(entry.first, netBasePath))
320             {
321                 crow::connections::systemBus->async_method_call(
322                     [asyncResp](const boost::system::error_code ec2) {
323                     if (ec2)
324                     {
325                         messages::internalError(asyncResp->res);
326                         return;
327                     }
328                     },
329                     entry.second.begin()->first, entry.first,
330                     "org.freedesktop.DBus.Properties", "Set",
331                     "xyz.openbmc_project.Control.Service.Attributes", "Running",
332                     dbus::utility::DbusVariantType{protocolEnabled});
333 
334                 crow::connections::systemBus->async_method_call(
335                     [asyncResp](const boost::system::error_code ec2) {
336                     if (ec2)
337                     {
338                         messages::internalError(asyncResp->res);
339                         return;
340                     }
341                     },
342                     entry.second.begin()->first, entry.first,
343                     "org.freedesktop.DBus.Properties", "Set",
344                     "xyz.openbmc_project.Control.Service.Attributes", "Enabled",
345                     dbus::utility::DbusVariantType{protocolEnabled});
346             }
347         }
348         },
349         "xyz.openbmc_project.ObjectMapper",
350         "/xyz/openbmc_project/object_mapper",
351         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
352         "/xyz/openbmc_project/control/service", 0,
353         std::array<const char*, 1>{
354             "xyz.openbmc_project.Control.Service.Attributes"});
355 }
356 
357 inline std::string getHostName()
358 {
359     std::string hostName;
360 
361     std::array<char, HOST_NAME_MAX> hostNameCStr{};
362     if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0)
363     {
364         hostName = hostNameCStr.data();
365     }
366     return hostName;
367 }
368 
369 inline void
370     getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
371 {
372     sdbusplus::asio::getProperty<std::string>(
373         *crow::connections::systemBus, "xyz.openbmc_project.Settings",
374         "/xyz/openbmc_project/time/sync_method",
375         "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod",
376         [asyncResp](const boost::system::error_code errorCode,
377                     const std::string& timeSyncMethod) {
378         if (errorCode)
379         {
380             return;
381         }
382 
383         if (timeSyncMethod ==
384             "xyz.openbmc_project.Time.Synchronization.Method.NTP")
385         {
386             asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true;
387         }
388         else if (timeSyncMethod == "xyz.openbmc_project.Time.Synchronization."
389                                    "Method.Manual")
390         {
391             asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false;
392         }
393         });
394 }
395 
396 inline std::string encodeServiceObjectPath(const std::string& serviceName)
397 {
398     sdbusplus::message::object_path objPath(
399         "/xyz/openbmc_project/control/service");
400     objPath /= serviceName;
401     return objPath.str;
402 }
403 
404 inline void requestRoutesNetworkProtocol(App& app)
405 {
406     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
407         .privileges(redfish::privileges::patchManagerNetworkProtocol)
408         .methods(boost::beast::http::verb::patch)(
409             [&app](const crow::Request& req,
410                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
411         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
412         {
413             return;
414         }
415         std::optional<std::string> newHostName;
416         std::optional<std::vector<std::string>> ntpServers;
417         std::optional<bool> ntpEnabled;
418         std::optional<bool> ipmiEnabled;
419         std::optional<bool> sshEnabled;
420 
421         // clang-format off
422         if (!json_util::readJsonPatch(
423                 req, asyncResp->res,
424                 "HostName", newHostName,
425                 "NTP/NTPServers", ntpServers,
426                 "NTP/ProtocolEnabled", ntpEnabled,
427                 "IPMI/ProtocolEnabled", ipmiEnabled,
428                 "SSH/ProtocolEnabled", sshEnabled))
429         {
430             return;
431         }
432         // clang-format on
433 
434         asyncResp->res.result(boost::beast::http::status::no_content);
435         if (newHostName)
436         {
437             messages::propertyNotWritable(asyncResp->res, "HostName");
438             return;
439         }
440 
441         if (ntpEnabled)
442         {
443             handleNTPProtocolEnabled(*ntpEnabled, asyncResp);
444         }
445         if (ntpServers)
446         {
447             stl_utils::removeDuplicate(*ntpServers);
448             handleNTPServersPatch(asyncResp, *ntpServers);
449         }
450 
451         if (ipmiEnabled)
452         {
453             handleProtocolEnabled(
454                 *ipmiEnabled, asyncResp,
455                 encodeServiceObjectPath(std::string(ipmiServiceName) + '@'));
456         }
457 
458         if (sshEnabled)
459         {
460             handleProtocolEnabled(*sshEnabled, asyncResp,
461                                   encodeServiceObjectPath(sshServiceName));
462         }
463         });
464 
465     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
466         .privileges(redfish::privileges::getManagerNetworkProtocol)
467         .methods(boost::beast::http::verb::get)(
468             [&app](const crow::Request& req,
469                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
470         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
471         {
472             return;
473         }
474         getNetworkData(asyncResp, req);
475         });
476 }
477 
478 } // namespace redfish
479