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