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