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