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 "node.hpp"
20 
21 #include <optional>
22 #include <utils/json_utils.hpp>
23 #include <variant>
24 namespace redfish
25 {
26 
27 enum NetworkProtocolUnitStructFields
28 {
29     NET_PROTO_UNIT_NAME,
30     NET_PROTO_UNIT_DESC,
31     NET_PROTO_UNIT_LOAD_STATE,
32     NET_PROTO_UNIT_ACTIVE_STATE,
33     NET_PROTO_UNIT_SUB_STATE,
34     NET_PROTO_UNIT_DEVICE,
35     NET_PROTO_UNIT_OBJ_PATH,
36     NET_PROTO_UNIT_ALWAYS_0,
37     NET_PROTO_UNIT_ALWAYS_EMPTY,
38     NET_PROTO_UNIT_ALWAYS_ROOT_PATH
39 };
40 
41 enum NetworkProtocolListenResponseElements
42 {
43     NET_PROTO_LISTEN_TYPE,
44     NET_PROTO_LISTEN_STREAM
45 };
46 
47 /**
48  * @brief D-Bus Unit structure returned in array from ListUnits Method
49  */
50 using UnitStruct =
51     std::tuple<std::string, std::string, std::string, std::string, std::string,
52                std::string, sdbusplus::message::object_path, uint32_t,
53                std::string, sdbusplus::message::object_path>;
54 
55 const static boost::container::flat_map<const char*, std::string>
56     protocolToDBus{{"SSH", "dropbear"},
57                    {"HTTPS", "bmcweb"},
58                    {"IPMI", "phosphor-ipmi-net"}};
59 
60 inline void
61     extractNTPServersAndDomainNamesData(const GetManagedObjects& dbus_data,
62                                         std::vector<std::string>& ntpData,
63                                         std::vector<std::string>& dnData)
64 {
65     for (const auto& obj : dbus_data)
66     {
67         for (const auto& ifacePair : obj.second)
68         {
69             if (obj.first == "/xyz/openbmc_project/network/eth0")
70             {
71                 if (ifacePair.first ==
72                     "xyz.openbmc_project.Network.EthernetInterface")
73                 {
74                     for (const auto& propertyPair : ifacePair.second)
75                     {
76                         if (propertyPair.first == "NTPServers")
77                         {
78                             const std::vector<std::string>* ntpServers =
79                                 std::get_if<std::vector<std::string>>(
80                                     &propertyPair.second);
81                             if (ntpServers != nullptr)
82                             {
83                                 ntpData = std::move(*ntpServers);
84                             }
85                         }
86                         else if (propertyPair.first == "DomainName")
87                         {
88                             const std::vector<std::string>* domainNames =
89                                 std::get_if<std::vector<std::string>>(
90                                     &propertyPair.second);
91                             if (domainNames != nullptr)
92                             {
93                                 dnData = std::move(*domainNames);
94                             }
95                         }
96                     }
97                 }
98             }
99         }
100     }
101 }
102 
103 template <typename CallbackFunc>
104 void getEthernetIfaceData(CallbackFunc&& callback)
105 {
106     crow::connections::systemBus->async_method_call(
107         [callback{std::move(callback)}](
108             const boost::system::error_code error_code,
109             const GetManagedObjects& dbus_data) {
110             std::vector<std::string> ntpServers;
111             std::vector<std::string> domainNames;
112 
113             if (error_code)
114             {
115                 callback(false, ntpServers, domainNames);
116                 return;
117             }
118 
119             extractNTPServersAndDomainNamesData(dbus_data, ntpServers,
120                                                 domainNames);
121 
122             callback(true, ntpServers, domainNames);
123         },
124         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
125         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
126 }
127 
128 class NetworkProtocol : public Node
129 {
130   public:
131     NetworkProtocol(CrowApp& app) :
132         Node(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
133     {
134         entityPrivileges = {
135             {boost::beast::http::verb::get, {{"Login"}}},
136             {boost::beast::http::verb::head, {{"Login"}}},
137             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
138             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
139             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
140             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
141     }
142 
143   private:
144     void doGet(crow::Response& res, const crow::Request& req,
145                const std::vector<std::string>& params) override
146     {
147         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
148 
149         getData(asyncResp);
150     }
151 
152     std::string getHostName() const
153     {
154         std::string hostName;
155 
156         std::array<char, HOST_NAME_MAX> hostNameCStr;
157         if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0)
158         {
159             hostName = hostNameCStr.data();
160         }
161         return hostName;
162     }
163 
164     void getNTPProtocolEnabled(const std::shared_ptr<AsyncResp>& asyncResp)
165     {
166         crow::connections::systemBus->async_method_call(
167             [asyncResp](const boost::system::error_code error_code,
168                         const std::variant<std::string>& timeSyncMethod) {
169                 const std::string* s =
170                     std::get_if<std::string>(&timeSyncMethod);
171 
172                 if (*s == "xyz.openbmc_project.Time.Synchronization.Method.NTP")
173                 {
174                     asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true;
175                 }
176                 else if (*s == "xyz.openbmc_project.Time.Synchronization."
177                                "Method.Manual")
178                 {
179                     asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false;
180                 }
181             },
182             "xyz.openbmc_project.Settings",
183             "/xyz/openbmc_project/time/sync_method",
184             "org.freedesktop.DBus.Properties", "Get",
185             "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod");
186     }
187 
188     void getData(const std::shared_ptr<AsyncResp>& asyncResp)
189     {
190         asyncResp->res.jsonValue["@odata.type"] =
191             "#ManagerNetworkProtocol.v1_5_0.ManagerNetworkProtocol";
192         asyncResp->res.jsonValue["@odata.id"] =
193             "/redfish/v1/Managers/bmc/NetworkProtocol";
194         asyncResp->res.jsonValue["Id"] = "NetworkProtocol";
195         asyncResp->res.jsonValue["Name"] = "Manager Network Protocol";
196         asyncResp->res.jsonValue["Description"] = "Manager Network Service";
197         asyncResp->res.jsonValue["Status"]["Health"] = "OK";
198         asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
199         asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
200         asyncResp->res.jsonValue["SNMP"]["ProtocolEnabled"] = true;
201         asyncResp->res.jsonValue["SNMP"]["Port"] = 161;
202         asyncResp->res.jsonValue["SNMP"]["AuthenticationProtocol"] =
203             "CommunityString";
204         asyncResp->res.jsonValue["SNMP"]["CommunityAccessMode"] = "Full";
205         asyncResp->res.jsonValue["SNMP"]["HideCommunityStrings"] = true;
206         asyncResp->res
207             .jsonValue["SNMP"]["EngineId"]["EnterpriseSpecificMethod"] =
208             nullptr;
209         asyncResp->res.jsonValue["SNMP"]["EngineId"]["PrivateEnterpriseId"] =
210             nullptr;
211         asyncResp->res.jsonValue["SNMP"]["EnableSNMPv1"] = false;
212         asyncResp->res.jsonValue["SNMP"]["EnableSNMPv2c"] = true;
213         asyncResp->res.jsonValue["SNMP"]["EnableSNMPv3"] = false;
214         asyncResp->res.jsonValue["SNMP"]["EncryptionProtocol"] = "None";
215         nlohmann::json& memberArray =
216             asyncResp->res.jsonValue["SNMP"]["CommunityStrings"];
217         memberArray = nlohmann::json::array();
218         memberArray.push_back({{"AccessMode", "Full"}});
219         memberArray.push_back({{"CommunityString", ""}});
220         memberArray.push_back({{"Name", ""}});
221 
222         // HTTP is Mandatory attribute as per OCP Baseline Profile - v1.0.0,
223         // but from security perspective it is not recommended to use.
224         // Hence using protocolEnabled as false to make it OCP and security-wise
225         // compliant
226         asyncResp->res.jsonValue["HTTP"]["Port"] = 0;
227         asyncResp->res.jsonValue["HTTP"]["ProtocolEnabled"] = false;
228 
229         for (auto& protocol : protocolToDBus)
230         {
231             asyncResp->res.jsonValue[protocol.first]["ProtocolEnabled"] = false;
232         }
233 
234         std::string hostName = getHostName();
235 
236         asyncResp->res.jsonValue["HostName"] = hostName;
237 
238         getNTPProtocolEnabled(asyncResp);
239 
240         // TODO Get eth0 interface data, and call the below callback for JSON
241         // preparation
242         getEthernetIfaceData(
243             [hostName, asyncResp](const bool& success,
244                                   const std::vector<std::string>& ntpServers,
245                                   const std::vector<std::string>& domainNames) {
246                 if (!success)
247                 {
248                     messages::resourceNotFound(asyncResp->res,
249                                                "EthernetInterface", "eth0");
250                     return;
251                 }
252                 asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers;
253                 if (hostName.empty() == false)
254                 {
255                     std::string FQDN = std::move(hostName);
256                     if (domainNames.empty() == false)
257                     {
258                         FQDN += "." + domainNames[0];
259                     }
260                     asyncResp->res.jsonValue["FQDN"] = std::move(FQDN);
261                 }
262             });
263 
264         crow::connections::systemBus->async_method_call(
265             [asyncResp](const boost::system::error_code e,
266                         const std::vector<UnitStruct>& r) {
267                 if (e)
268                 {
269                     asyncResp->res.jsonValue = nlohmann::json::object();
270                     messages::internalError(asyncResp->res);
271                     return;
272                 }
273                 asyncResp->res.jsonValue["HTTPS"]["Certificates"] = {
274                     {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol/"
275                                   "HTTPS/Certificates"}};
276 
277                 for (auto& unit : r)
278                 {
279                     /* Only traverse through <xyz>.socket units */
280                     std::string unitName = std::get<NET_PROTO_UNIT_NAME>(unit);
281                     if (!boost::ends_with(unitName, ".socket"))
282                     {
283                         continue;
284                     }
285 
286                     for (auto& kv : protocolToDBus)
287                     {
288                         // We are interested in services, which starts with
289                         // mapped service name
290                         if (!boost::starts_with(unitName, kv.second))
291                         {
292                             continue;
293                         }
294                         const char* rfServiceKey = kv.first;
295                         std::string socketPath =
296                             std::get<NET_PROTO_UNIT_OBJ_PATH>(unit);
297                         std::string unitState =
298                             std::get<NET_PROTO_UNIT_SUB_STATE>(unit);
299 
300                         asyncResp->res
301                             .jsonValue[rfServiceKey]["ProtocolEnabled"] =
302                             (unitState == "running") ||
303                             (unitState == "listening");
304 
305                         crow::connections::systemBus->async_method_call(
306                             [asyncResp,
307                              rfServiceKey{std::string(rfServiceKey)}](
308                                 const boost::system::error_code ec,
309                                 const std::variant<std::vector<std::tuple<
310                                     std::string, std::string>>>& resp) {
311                                 if (ec)
312                                 {
313                                     messages::internalError(asyncResp->res);
314                                     return;
315                                 }
316                                 const std::vector<
317                                     std::tuple<std::string, std::string>>*
318                                     responsePtr = std::get_if<std::vector<
319                                         std::tuple<std::string, std::string>>>(
320                                         &resp);
321                                 if (responsePtr == nullptr ||
322                                     responsePtr->size() < 1)
323                                 {
324                                     return;
325                                 }
326 
327                                 const std::string& listenStream =
328                                     std::get<NET_PROTO_LISTEN_STREAM>(
329                                         (*responsePtr)[0]);
330                                 std::size_t lastColonPos =
331                                     listenStream.rfind(":");
332                                 if (lastColonPos == std::string::npos)
333                                 {
334                                     // Not a port
335                                     return;
336                                 }
337                                 std::string portStr =
338                                     listenStream.substr(lastColonPos + 1);
339                                 if (portStr.empty())
340                                 {
341                                     return;
342                                 }
343                                 char* endPtr = nullptr;
344                                 errno = 0;
345                                 // Use strtol instead of stroi to avoid
346                                 // exceptions
347                                 long port =
348                                     std::strtol(portStr.c_str(), &endPtr, 10);
349                                 if ((errno == 0) && (*endPtr == '\0'))
350                                 {
351                                     asyncResp->res
352                                         .jsonValue[rfServiceKey]["Port"] = port;
353                                 }
354                                 return;
355                             },
356                             "org.freedesktop.systemd1", socketPath,
357                             "org.freedesktop.DBus.Properties", "Get",
358                             "org.freedesktop.systemd1.Socket", "Listen");
359 
360                         // We found service, break the inner loop.
361                         break;
362                     }
363                 }
364             },
365             "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
366             "org.freedesktop.systemd1.Manager", "ListUnits");
367     }
368 
369     void handleHostnamePatch(const std::string& hostName,
370                              const std::shared_ptr<AsyncResp>& asyncResp)
371     {
372         crow::connections::systemBus->async_method_call(
373             [asyncResp](const boost::system::error_code ec) {
374                 if (ec)
375                 {
376                     messages::internalError(asyncResp->res);
377                     return;
378                 }
379             },
380             "xyz.openbmc_project.Network",
381             "/xyz/openbmc_project/network/config",
382             "org.freedesktop.DBus.Properties", "Set",
383             "xyz.openbmc_project.Network.SystemConfiguration", "HostName",
384             std::variant<std::string>(hostName));
385     }
386 
387     void handleNTPProtocolEnabled(const bool& ntpEnabled,
388                                   const std::shared_ptr<AsyncResp>& asyncResp)
389     {
390         std::string timeSyncMethod;
391         if (ntpEnabled)
392         {
393             timeSyncMethod =
394                 "xyz.openbmc_project.Time.Synchronization.Method.NTP";
395         }
396         else
397         {
398             timeSyncMethod =
399                 "xyz.openbmc_project.Time.Synchronization.Method.Manual";
400         }
401 
402         crow::connections::systemBus->async_method_call(
403             [asyncResp](const boost::system::error_code error_code) {},
404             "xyz.openbmc_project.Settings",
405             "/xyz/openbmc_project/time/sync_method",
406             "org.freedesktop.DBus.Properties", "Set",
407             "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod",
408             std::variant<std::string>{timeSyncMethod});
409     }
410 
411     void handleNTPServersPatch(const std::vector<std::string>& ntpServers,
412                                const std::shared_ptr<AsyncResp>& asyncResp)
413     {
414         crow::connections::systemBus->async_method_call(
415             [asyncResp](const boost::system::error_code ec) {
416                 if (ec)
417                 {
418                     messages::internalError(asyncResp->res);
419                     return;
420                 }
421             },
422             "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/eth0",
423             "org.freedesktop.DBus.Properties", "Set",
424             "xyz.openbmc_project.Network.EthernetInterface", "NTPServers",
425             std::variant<std::vector<std::string>>{ntpServers});
426     }
427 
428     void doPatch(crow::Response& res, const crow::Request& req,
429                  const std::vector<std::string>& params) override
430     {
431         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
432         std::optional<std::string> newHostName;
433         std::optional<nlohmann::json> ntp;
434 
435         if (!json_util::readJson(req, res, "HostName", newHostName, "NTP", ntp))
436         {
437             return;
438         }
439 
440         res.result(boost::beast::http::status::no_content);
441         if (newHostName)
442         {
443             handleHostnamePatch(*newHostName, asyncResp);
444         }
445 
446         if (ntp)
447         {
448             std::optional<std::vector<std::string>> ntpServers;
449             std::optional<bool> ntpEnabled;
450             if (!json_util::readJson(*ntp, res, "NTPServers", ntpServers,
451                                      "ProtocolEnabled", ntpEnabled))
452             {
453                 return;
454             }
455 
456             if (ntpEnabled)
457             {
458                 handleNTPProtocolEnabled(*ntpEnabled, asyncResp);
459             }
460 
461             if (ntpServers)
462             {
463                 handleNTPServersPatch(*ntpServers, asyncResp);
464             }
465         }
466     }
467 };
468 
469 } // namespace redfish
470