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