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 
26 #include <optional>
27 #include <variant>
28 namespace redfish
29 {
30 
31 void getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp);
32 std::string getHostName();
33 
34 const static std::array<std::pair<std::string, std::string>, 3> protocolToDBus{
35     {{"SSH", "dropbear"}, {"HTTPS", "bmcweb"}, {"IPMI", "phosphor-ipmi-net"}}};
36 
37 inline void
38     extractNTPServersAndDomainNamesData(const GetManagedObjects& dbusData,
39                                         std::vector<std::string>& ntpData,
40                                         std::vector<std::string>& dnData)
41 {
42     for (const auto& obj : dbusData)
43     {
44         for (const auto& ifacePair : obj.second)
45         {
46             if (obj.first == "/xyz/openbmc_project/network/eth0")
47             {
48                 if (ifacePair.first ==
49                     "xyz.openbmc_project.Network.EthernetInterface")
50                 {
51                     for (const auto& propertyPair : ifacePair.second)
52                     {
53                         if (propertyPair.first == "NTPServers")
54                         {
55                             const std::vector<std::string>* ntpServers =
56                                 std::get_if<std::vector<std::string>>(
57                                     &propertyPair.second);
58                             if (ntpServers != nullptr)
59                             {
60                                 ntpData = *ntpServers;
61                             }
62                         }
63                         else if (propertyPair.first == "DomainName")
64                         {
65                             const std::vector<std::string>* domainNames =
66                                 std::get_if<std::vector<std::string>>(
67                                     &propertyPair.second);
68                             if (domainNames != nullptr)
69                             {
70                                 dnData = *domainNames;
71                             }
72                         }
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     for (auto& protocol : protocolToDBus)
127     {
128         asyncResp->res.jsonValue[protocol.first]["Port"] =
129             nlohmann::detail::value_t::null;
130         asyncResp->res.jsonValue[protocol.first]["ProtocolEnabled"] = false;
131     }
132 
133     std::string hostName = getHostName();
134 
135     asyncResp->res.jsonValue["HostName"] = hostName;
136 
137     getNTPProtocolEnabled(asyncResp);
138 
139     // TODO Get eth0 interface data, and call the below callback for JSON
140     // preparation
141     getEthernetIfaceData(
142         [hostName, asyncResp](const bool& success,
143                               const std::vector<std::string>& ntpServers,
144                               const std::vector<std::string>& domainNames) {
145             if (!success)
146             {
147                 messages::resourceNotFound(asyncResp->res, "EthernetInterface",
148                                            "eth0");
149                 return;
150             }
151             asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers;
152             if (hostName.empty() == false)
153             {
154                 std::string fqdn = hostName;
155                 if (domainNames.empty() == false)
156                 {
157                     fqdn += ".";
158                     fqdn += domainNames[0];
159                 }
160                 asyncResp->res.jsonValue["FQDN"] = std::move(fqdn);
161             }
162         });
163 
164     Privileges effectiveUserPrivileges =
165         redfish::getUserPrivileges(req.userRole);
166 
167     // /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates is
168     // something only ConfigureManager can access then only display when
169     // the user has permissions ConfigureManager
170     if (isOperationAllowedWithPrivileges({{"ConfigureManager"}},
171                                          effectiveUserPrivileges))
172     {
173         asyncResp->res.jsonValue["HTTPS"]["Certificates"] = {
174             {"@odata.id",
175              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"}};
176     }
177 
178     for (const auto& protocol : protocolToDBus)
179     {
180         const std::string& protocolName = protocol.first;
181         const std::string& serviceName = protocol.second;
182         getPortStatusAndPath(
183             serviceName,
184             [asyncResp, protocolName](const boost::system::error_code ec,
185                                       const std::string& socketPath,
186                                       bool isProtocolEnabled) {
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 #ifdef BMCWEB_ALLOW_DEPRECATED_HOSTNAME_PATCH
211 inline void
212     handleHostnamePatch(const std::string& hostName,
213                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
214 {
215     crow::connections::systemBus->async_method_call(
216         [asyncResp](const boost::system::error_code ec) {
217             if (ec)
218             {
219                 messages::internalError(asyncResp->res);
220                 return;
221             }
222         },
223         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/config",
224         "org.freedesktop.DBus.Properties", "Set",
225         "xyz.openbmc_project.Network.SystemConfiguration", "HostName",
226         std::variant<std::string>(hostName));
227 }
228 #endif
229 
230 inline void handleNTPProtocolEnabled(
231     const bool& ntpEnabled, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
232 {
233     std::string timeSyncMethod;
234     if (ntpEnabled)
235     {
236         timeSyncMethod = "xyz.openbmc_project.Time.Synchronization.Method.NTP";
237     }
238     else
239     {
240         timeSyncMethod =
241             "xyz.openbmc_project.Time.Synchronization.Method.Manual";
242     }
243 
244     crow::connections::systemBus->async_method_call(
245         [asyncResp](const boost::system::error_code errorCode) {
246             if (errorCode)
247             {
248                 messages::internalError(asyncResp->res);
249             }
250         },
251         "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method",
252         "org.freedesktop.DBus.Properties", "Set",
253         "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod",
254         std::variant<std::string>{timeSyncMethod});
255 }
256 
257 inline void
258     handleNTPServersPatch(const std::vector<std::string>& ntpServers,
259                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
260 {
261     crow::connections::systemBus->async_method_call(
262         [asyncResp](const boost::system::error_code ec) {
263             if (ec)
264             {
265                 messages::internalError(asyncResp->res);
266                 return;
267             }
268         },
269         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/eth0",
270         "org.freedesktop.DBus.Properties", "Set",
271         "xyz.openbmc_project.Network.EthernetInterface", "NTPServers",
272         std::variant<std::vector<std::string>>{ntpServers});
273 }
274 
275 inline void
276     handleProtocolEnabled(const bool protocolEnabled,
277                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
278                           const std::string_view netBasePath)
279 {
280     crow::connections::systemBus->async_method_call(
281         [protocolEnabled, asyncResp,
282          netBasePath](const boost::system::error_code ec,
283                       const crow::openbmc_mapper::GetSubTreeType& subtree) {
284             if (ec)
285             {
286                 messages::internalError(asyncResp->res);
287                 return;
288             }
289 
290             for (const auto& entry : subtree)
291             {
292                 if (boost::algorithm::starts_with(entry.first, netBasePath))
293                 {
294                     crow::connections::systemBus->async_method_call(
295                         [asyncResp](const boost::system::error_code ec2) {
296                             if (ec2)
297                             {
298                                 messages::internalError(asyncResp->res);
299                                 return;
300                             }
301                         },
302                         entry.second.begin()->first, entry.first,
303                         "org.freedesktop.DBus.Properties", "Set",
304                         "xyz.openbmc_project.Control.Service.Attributes",
305                         "Running", std::variant<bool>{protocolEnabled});
306 
307                     crow::connections::systemBus->async_method_call(
308                         [asyncResp](const boost::system::error_code ec2) {
309                             if (ec2)
310                             {
311                                 messages::internalError(asyncResp->res);
312                                 return;
313                             }
314                         },
315                         entry.second.begin()->first, entry.first,
316                         "org.freedesktop.DBus.Properties", "Set",
317                         "xyz.openbmc_project.Control.Service.Attributes",
318                         "Enabled", std::variant<bool>{protocolEnabled});
319                 }
320             }
321         },
322         "xyz.openbmc_project.ObjectMapper",
323         "/xyz/openbmc_project/object_mapper",
324         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
325         "/xyz/openbmc_project/control/service", 0,
326         std::array<const char*, 1>{
327             "xyz.openbmc_project.Control.Service.Attributes"});
328 }
329 
330 inline std::string getHostName()
331 {
332     std::string hostName;
333 
334     std::array<char, HOST_NAME_MAX> hostNameCStr;
335     if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0)
336     {
337         hostName = hostNameCStr.data();
338     }
339     return hostName;
340 }
341 
342 inline void
343     getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
344 {
345     crow::connections::systemBus->async_method_call(
346         [asyncResp](const boost::system::error_code errorCode,
347                     const std::variant<std::string>& timeSyncMethod) {
348             if (errorCode)
349             {
350                 return;
351             }
352 
353             const std::string* s = std::get_if<std::string>(&timeSyncMethod);
354 
355             if (*s == "xyz.openbmc_project.Time.Synchronization.Method.NTP")
356             {
357                 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true;
358             }
359             else if (*s == "xyz.openbmc_project.Time.Synchronization."
360                            "Method.Manual")
361             {
362                 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false;
363             }
364         },
365         "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method",
366         "org.freedesktop.DBus.Properties", "Get",
367         "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod");
368 }
369 
370 inline void requestRoutesNetworkProtocol(App& app)
371 {
372     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
373         .privileges(redfish::privileges::patchManagerNetworkProtocol)
374         .methods(boost::beast::http::verb::patch)(
375             [](const crow::Request& req,
376                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
377                 std::optional<std::string> newHostName;
378                 std::optional<nlohmann::json> ntp;
379                 std::optional<nlohmann::json> ipmi;
380                 std::optional<nlohmann::json> ssh;
381 
382                 if (!json_util::readJson(req, asyncResp->res, "NTP", ntp,
383                                          "HostName", newHostName, "IPMI", ipmi,
384                                          "SSH", ssh))
385                 {
386                     return;
387                 }
388 
389                 asyncResp->res.result(boost::beast::http::status::no_content);
390                 if (newHostName)
391                 {
392 #ifdef BMCWEB_ALLOW_DEPRECATED_HOSTNAME_PATCH
393                     handleHostnamePatch(*newHostName, asyncResp);
394 #else
395                     messages::propertyNotWritable(asyncResp->res, "HostName");
396 #endif
397                 }
398 
399                 if (ntp)
400                 {
401                     std::optional<std::vector<std::string>> ntpServers;
402                     std::optional<bool> ntpEnabled;
403                     if (!json_util::readJson(*ntp, asyncResp->res, "NTPServers",
404                                              ntpServers, "ProtocolEnabled",
405                                              ntpEnabled))
406                     {
407                         return;
408                     }
409 
410                     if (ntpEnabled)
411                     {
412                         handleNTPProtocolEnabled(*ntpEnabled, asyncResp);
413                     }
414 
415                     if (ntpServers)
416                     {
417                         std::sort((*ntpServers).begin(), (*ntpServers).end());
418                         (*ntpServers)
419                             .erase(std::unique((*ntpServers).begin(),
420                                                (*ntpServers).end()),
421                                    (*ntpServers).end());
422                         handleNTPServersPatch(*ntpServers, asyncResp);
423                     }
424                 }
425 
426                 if (ipmi)
427                 {
428                     std::optional<bool> ipmiProtocolEnabled;
429                     if (!json_util::readJson(*ipmi, asyncResp->res,
430                                              "ProtocolEnabled",
431                                              ipmiProtocolEnabled))
432                     {
433                         return;
434                     }
435 
436                     if (ipmiProtocolEnabled)
437                     {
438                         handleProtocolEnabled(
439                             *ipmiProtocolEnabled, asyncResp,
440                             "/xyz/openbmc_project/control/service/"
441                             "phosphor_2dipmi_2dnet_40");
442                     }
443                 }
444 
445                 if (ssh)
446                 {
447                     std::optional<bool> sshProtocolEnabled;
448                     if (!json_util::readJson(*ssh, asyncResp->res,
449                                              "ProtocolEnabled",
450                                              sshProtocolEnabled))
451                     {
452                         return;
453                     }
454 
455                     if (sshProtocolEnabled)
456                     {
457                         handleProtocolEnabled(
458                             *sshProtocolEnabled, asyncResp,
459                             "/xyz/openbmc_project/control/service/dropbear");
460                     }
461                 }
462             });
463 
464     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
465         .privileges(redfish::privileges::getManagerNetworkProtocol)
466         .methods(boost::beast::http::verb::get)(
467             [](const crow::Request& req,
468                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
469                 getNetworkData(asyncResp, req);
470             });
471 }
472 
473 } // namespace redfish
474