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 (obj.first == "/xyz/openbmc_project/network/eth0")
48             {
49                 if (ifacePair.first ==
50                     "xyz.openbmc_project.Network.EthernetInterface")
51                 {
52                     for (const auto& propertyPair : ifacePair.second)
53                     {
54                         if (propertyPair.first == "NTPServers")
55                         {
56                             const std::vector<std::string>* ntpServers =
57                                 std::get_if<std::vector<std::string>>(
58                                     &propertyPair.second);
59                             if (ntpServers != nullptr)
60                             {
61                                 ntpData = *ntpServers;
62                             }
63                         }
64                         else if (propertyPair.first == "DomainName")
65                         {
66                             const std::vector<std::string>* domainNames =
67                                 std::get_if<std::vector<std::string>>(
68                                     &propertyPair.second);
69                             if (domainNames != nullptr)
70                             {
71                                 dnData = *domainNames;
72                             }
73                         }
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::move(callback)}](
86             const boost::system::error_code errorCode,
87             const GetManagedObjects& 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     // TODO Get eth0 interface data, and call the below callback for JSON
134     // preparation
135     getEthernetIfaceData(
136         [hostName, asyncResp](const bool& success,
137                               const std::vector<std::string>& ntpServers,
138                               const std::vector<std::string>& domainNames) {
139             if (!success)
140             {
141                 messages::resourceNotFound(asyncResp->res, "EthernetInterface",
142                                            "eth0");
143                 return;
144             }
145             asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers;
146             if (hostName.empty() == false)
147             {
148                 std::string fqdn = hostName;
149                 if (domainNames.empty() == false)
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"] = {
168             {"@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         std::variant<std::string>{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](const boost::system::error_code ec) {
256             if (ec)
257             {
258                 messages::internalError(asyncResp->res);
259                 return;
260             }
261         },
262         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/eth0",
263         "org.freedesktop.DBus.Properties", "Set",
264         "xyz.openbmc_project.Network.EthernetInterface", "NTPServers",
265         std::variant<std::vector<std::string>>{ntpServers});
266 }
267 
268 inline void
269     handleProtocolEnabled(const bool protocolEnabled,
270                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
271                           const std::string_view netBasePath)
272 {
273     crow::connections::systemBus->async_method_call(
274         [protocolEnabled, asyncResp,
275          netBasePath](const boost::system::error_code ec,
276                       const crow::openbmc_mapper::GetSubTreeType& subtree) {
277             if (ec)
278             {
279                 messages::internalError(asyncResp->res);
280                 return;
281             }
282 
283             for (const auto& entry : subtree)
284             {
285                 if (boost::algorithm::starts_with(entry.first, netBasePath))
286                 {
287                     crow::connections::systemBus->async_method_call(
288                         [asyncResp](const boost::system::error_code ec2) {
289                             if (ec2)
290                             {
291                                 messages::internalError(asyncResp->res);
292                                 return;
293                             }
294                         },
295                         entry.second.begin()->first, entry.first,
296                         "org.freedesktop.DBus.Properties", "Set",
297                         "xyz.openbmc_project.Control.Service.Attributes",
298                         "Running", std::variant<bool>{protocolEnabled});
299 
300                     crow::connections::systemBus->async_method_call(
301                         [asyncResp](const boost::system::error_code ec2) {
302                             if (ec2)
303                             {
304                                 messages::internalError(asyncResp->res);
305                                 return;
306                             }
307                         },
308                         entry.second.begin()->first, entry.first,
309                         "org.freedesktop.DBus.Properties", "Set",
310                         "xyz.openbmc_project.Control.Service.Attributes",
311                         "Enabled", std::variant<bool>{protocolEnabled});
312                 }
313             }
314         },
315         "xyz.openbmc_project.ObjectMapper",
316         "/xyz/openbmc_project/object_mapper",
317         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
318         "/xyz/openbmc_project/control/service", 0,
319         std::array<const char*, 1>{
320             "xyz.openbmc_project.Control.Service.Attributes"});
321 }
322 
323 inline std::string getHostName()
324 {
325     std::string hostName;
326 
327     std::array<char, HOST_NAME_MAX> hostNameCStr;
328     if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0)
329     {
330         hostName = hostNameCStr.data();
331     }
332     return hostName;
333 }
334 
335 inline void
336     getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
337 {
338     crow::connections::systemBus->async_method_call(
339         [asyncResp](const boost::system::error_code errorCode,
340                     const std::variant<std::string>& timeSyncMethod) {
341             if (errorCode)
342             {
343                 return;
344             }
345 
346             const std::string* s = std::get_if<std::string>(&timeSyncMethod);
347 
348             if (*s == "xyz.openbmc_project.Time.Synchronization.Method.NTP")
349             {
350                 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true;
351             }
352             else if (*s == "xyz.openbmc_project.Time.Synchronization."
353                            "Method.Manual")
354             {
355                 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false;
356             }
357         },
358         "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method",
359         "org.freedesktop.DBus.Properties", "Get",
360         "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod");
361 }
362 
363 inline void requestRoutesNetworkProtocol(App& app)
364 {
365     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
366         .privileges(redfish::privileges::patchManagerNetworkProtocol)
367         .methods(boost::beast::http::verb::patch)(
368             [](const crow::Request& req,
369                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
370                 std::optional<std::string> newHostName;
371                 std::optional<nlohmann::json> ntp;
372                 std::optional<nlohmann::json> ipmi;
373                 std::optional<nlohmann::json> ssh;
374 
375                 if (!json_util::readJson(req, asyncResp->res, "NTP", ntp,
376                                          "HostName", newHostName, "IPMI", ipmi,
377                                          "SSH", ssh))
378                 {
379                     return;
380                 }
381 
382                 asyncResp->res.result(boost::beast::http::status::no_content);
383                 if (newHostName)
384                 {
385                     messages::propertyNotWritable(asyncResp->res, "HostName");
386                     return;
387                 }
388 
389                 if (ntp)
390                 {
391                     std::optional<std::vector<std::string>> ntpServers;
392                     std::optional<bool> ntpEnabled;
393                     if (!json_util::readJson(*ntp, asyncResp->res, "NTPServers",
394                                              ntpServers, "ProtocolEnabled",
395                                              ntpEnabled))
396                     {
397                         return;
398                     }
399 
400                     if (ntpEnabled)
401                     {
402                         handleNTPProtocolEnabled(*ntpEnabled, asyncResp);
403                     }
404 
405                     if (ntpServers)
406                     {
407                         stl_utils::removeDuplicate(*ntpServers);
408                         handleNTPServersPatch(asyncResp, *ntpServers);
409                     }
410                 }
411 
412                 if (ipmi)
413                 {
414                     std::optional<bool> ipmiProtocolEnabled;
415                     if (!json_util::readJson(*ipmi, asyncResp->res,
416                                              "ProtocolEnabled",
417                                              ipmiProtocolEnabled))
418                     {
419                         return;
420                     }
421 
422                     if (ipmiProtocolEnabled)
423                     {
424                         handleProtocolEnabled(
425                             *ipmiProtocolEnabled, asyncResp,
426                             "/xyz/openbmc_project/control/service/"
427                             "phosphor_2dipmi_2dnet_40");
428                     }
429                 }
430 
431                 if (ssh)
432                 {
433                     std::optional<bool> sshProtocolEnabled;
434                     if (!json_util::readJson(*ssh, asyncResp->res,
435                                              "ProtocolEnabled",
436                                              sshProtocolEnabled))
437                     {
438                         return;
439                     }
440 
441                     if (sshProtocolEnabled)
442                     {
443                         handleProtocolEnabled(
444                             *sshProtocolEnabled, asyncResp,
445                             "/xyz/openbmc_project/control/service/dropbear");
446                     }
447                 }
448             });
449 
450     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
451         .privileges(redfish::privileges::getManagerNetworkProtocol)
452         .methods(boost::beast::http::verb::get)(
453             [](const crow::Request& req,
454                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
455                 getNetworkData(asyncResp, req);
456             });
457 }
458 
459 } // namespace redfish
460