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