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                                 sdbusplus::message::variant_ns::get_if<
80                                     std::vector<std::string>>(
81                                     &propertyPair.second);
82                             if (ntpServers != nullptr)
83                             {
84                                 ntpData = std::move(*ntpServers);
85                             }
86                         }
87                         else if (propertyPair.first == "DomainName")
88                         {
89                             const std::vector<std::string>* domainNames =
90                                 sdbusplus::message::variant_ns::get_if<
91                                     std::vector<std::string>>(
92                                     &propertyPair.second);
93                             if (domainNames != nullptr)
94                             {
95                                 dnData = std::move(*domainNames);
96                             }
97                         }
98                     }
99                 }
100             }
101         }
102     }
103 }
104 
105 template <typename CallbackFunc>
106 void getEthernetIfaceData(CallbackFunc&& callback)
107 {
108     crow::connections::systemBus->async_method_call(
109         [callback{std::move(callback)}](
110             const boost::system::error_code error_code,
111             const GetManagedObjects& dbus_data) {
112             std::vector<std::string> ntpServers;
113             std::vector<std::string> domainNames;
114 
115             if (error_code)
116             {
117                 callback(false, ntpServers, domainNames);
118                 return;
119             }
120 
121             extractNTPServersAndDomainNamesData(dbus_data, ntpServers,
122                                                 domainNames);
123 
124             callback(true, ntpServers, domainNames);
125         },
126         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
127         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
128 }
129 
130 class NetworkProtocol : public Node
131 {
132   public:
133     NetworkProtocol(CrowApp& app) :
134         Node(app, "/redfish/v1/Managers/bmc/NetworkProtocol")
135     {
136         entityPrivileges = {
137             {boost::beast::http::verb::get, {{"Login"}}},
138             {boost::beast::http::verb::head, {{"Login"}}},
139             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
140             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
141             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
142             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
143     }
144 
145   private:
146     void doGet(crow::Response& res, const crow::Request& req,
147                const std::vector<std::string>& params) override
148     {
149         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
150 
151         getData(asyncResp);
152     }
153 
154     std::string getHostName() const
155     {
156         std::string hostName;
157 
158         std::array<char, HOST_NAME_MAX> hostNameCStr;
159         if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0)
160         {
161             hostName = hostNameCStr.data();
162         }
163         return hostName;
164     }
165 
166     void getNTPProtocolEnabled(const std::shared_ptr<AsyncResp>& asyncResp)
167     {
168         crow::connections::systemBus->async_method_call(
169             [asyncResp](const boost::system::error_code error_code,
170                         const std::variant<std::string>& timeSyncMethod) {
171                 const std::string* s =
172                     std::get_if<std::string>(&timeSyncMethod);
173 
174                 if (*s == "xyz.openbmc_project.Time.Synchronization.Method.NTP")
175                 {
176                     asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true;
177                 }
178                 else if (*s == "xyz.openbmc_project.Time.Synchronization."
179                                "Method.Manual")
180                 {
181                     asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false;
182                 }
183             },
184             "xyz.openbmc_project.Settings",
185             "/xyz/openbmc_project/time/sync_method",
186             "org.freedesktop.DBus.Properties", "Get",
187             "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod");
188     }
189 
190     void getData(const std::shared_ptr<AsyncResp>& asyncResp)
191     {
192         asyncResp->res.jsonValue["@odata.type"] =
193             "#ManagerNetworkProtocol.v1_4_0.ManagerNetworkProtocol";
194         asyncResp->res.jsonValue["@odata.id"] =
195             "/redfish/v1/Managers/bmc/NetworkProtocol";
196         asyncResp->res.jsonValue["@odata.context"] =
197             "/redfish/v1/"
198             "$metadata#ManagerNetworkProtocol.ManagerNetworkProtocol";
199         asyncResp->res.jsonValue["Id"] = "NetworkProtocol";
200         asyncResp->res.jsonValue["Name"] = "Manager Network Protocol";
201         asyncResp->res.jsonValue["Description"] = "Manager Network Service";
202         asyncResp->res.jsonValue["Status"]["Health"] = "OK";
203         asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
204         asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
205 
206         for (auto& protocol : protocolToDBus)
207         {
208             asyncResp->res.jsonValue[protocol.first]["ProtocolEnabled"] = false;
209         }
210 
211         std::string hostName = getHostName();
212 
213         asyncResp->res.jsonValue["HostName"] = hostName;
214 
215         getNTPProtocolEnabled(asyncResp);
216 
217         // TODO Get eth0 interface data, and call the below callback for JSON
218         // preparation
219         getEthernetIfaceData(
220             [hostName, asyncResp](const bool& success,
221                                   const std::vector<std::string>& ntpServers,
222                                   const std::vector<std::string>& domainNames) {
223                 if (!success)
224                 {
225                     messages::resourceNotFound(asyncResp->res,
226                                                "EthernetInterface", "eth0");
227                     return;
228                 }
229                 asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers;
230                 if (hostName.empty() == false)
231                 {
232                     std::string FQDN = std::move(hostName);
233                     if (domainNames.empty() == false)
234                     {
235                         FQDN += "." + domainNames[0];
236                     }
237                     asyncResp->res.jsonValue["FQDN"] = std::move(FQDN);
238                 }
239             });
240 
241         crow::connections::systemBus->async_method_call(
242             [asyncResp](const boost::system::error_code e,
243                         const std::vector<UnitStruct>& r) {
244                 if (e)
245                 {
246                     asyncResp->res.jsonValue = nlohmann::json::object();
247                     messages::internalError(asyncResp->res);
248                     return;
249                 }
250                 asyncResp->res.jsonValue["HTTPS"]["Certificates"] = {
251                     {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol/"
252                                   "HTTPS/Certificates"}};
253 
254                 for (auto& unit : r)
255                 {
256                     /* Only traverse through <xyz>.socket units */
257                     std::string unitName = std::get<NET_PROTO_UNIT_NAME>(unit);
258                     if (!boost::ends_with(unitName, ".socket"))
259                     {
260                         continue;
261                     }
262 
263                     for (auto& kv : protocolToDBus)
264                     {
265                         // We are interested in services, which starts with
266                         // mapped service name
267                         if (!boost::starts_with(unitName, kv.second))
268                         {
269                             continue;
270                         }
271                         const char* rfServiceKey = kv.first;
272                         std::string socketPath =
273                             std::get<NET_PROTO_UNIT_OBJ_PATH>(unit);
274                         std::string unitState =
275                             std::get<NET_PROTO_UNIT_SUB_STATE>(unit);
276 
277                         asyncResp->res
278                             .jsonValue[rfServiceKey]["ProtocolEnabled"] =
279                             (unitState == "running") ||
280                             (unitState == "listening");
281 
282                         crow::connections::systemBus->async_method_call(
283                             [asyncResp,
284                              rfServiceKey{std::string(rfServiceKey)}](
285                                 const boost::system::error_code ec,
286                                 const std::variant<std::vector<std::tuple<
287                                     std::string, std::string>>>& resp) {
288                                 if (ec)
289                                 {
290                                     messages::internalError(asyncResp->res);
291                                     return;
292                                 }
293                                 const std::vector<
294                                     std::tuple<std::string, std::string>>*
295                                     responsePtr = std::get_if<std::vector<
296                                         std::tuple<std::string, std::string>>>(
297                                         &resp);
298                                 if (responsePtr == nullptr ||
299                                     responsePtr->size() < 1)
300                                 {
301                                     return;
302                                 }
303 
304                                 const std::string& listenStream =
305                                     std::get<NET_PROTO_LISTEN_STREAM>(
306                                         (*responsePtr)[0]);
307                                 std::size_t lastColonPos =
308                                     listenStream.rfind(":");
309                                 if (lastColonPos == std::string::npos)
310                                 {
311                                     // Not a port
312                                     return;
313                                 }
314                                 std::string portStr =
315                                     listenStream.substr(lastColonPos + 1);
316                                 if (portStr.empty())
317                                 {
318                                     return;
319                                 }
320                                 char* endPtr = nullptr;
321                                 errno = 0;
322                                 // Use strtol instead of stroi to avoid
323                                 // exceptions
324                                 long port =
325                                     std::strtol(portStr.c_str(), &endPtr, 10);
326                                 if ((errno == 0) && (*endPtr == '\0'))
327                                 {
328                                     asyncResp->res
329                                         .jsonValue[rfServiceKey]["Port"] = port;
330                                 }
331                                 return;
332                             },
333                             "org.freedesktop.systemd1", socketPath,
334                             "org.freedesktop.DBus.Properties", "Get",
335                             "org.freedesktop.systemd1.Socket", "Listen");
336 
337                         // We found service, break the inner loop.
338                         break;
339                     }
340                 }
341             },
342             "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
343             "org.freedesktop.systemd1.Manager", "ListUnits");
344     }
345 
346     void handleHostnamePatch(const std::string& hostName,
347                              const std::shared_ptr<AsyncResp>& asyncResp)
348     {
349         crow::connections::systemBus->async_method_call(
350             [asyncResp](const boost::system::error_code ec) {
351                 if (ec)
352                 {
353                     messages::internalError(asyncResp->res);
354                     return;
355                 }
356             },
357             "xyz.openbmc_project.Network",
358             "/xyz/openbmc_project/network/config",
359             "org.freedesktop.DBus.Properties", "Set",
360             "xyz.openbmc_project.Network.SystemConfiguration", "HostName",
361             std::variant<std::string>(hostName));
362     }
363 
364     void handleNTPProtocolEnabled(const bool& ntpEnabled,
365                                   const std::shared_ptr<AsyncResp>& asyncResp)
366     {
367         std::string timeSyncMethod;
368         if (ntpEnabled)
369         {
370             timeSyncMethod =
371                 "xyz.openbmc_project.Time.Synchronization.Method.NTP";
372         }
373         else
374         {
375             timeSyncMethod =
376                 "xyz.openbmc_project.Time.Synchronization.Method.Manual";
377         }
378 
379         crow::connections::systemBus->async_method_call(
380             [asyncResp](const boost::system::error_code error_code) {},
381             "xyz.openbmc_project.Settings",
382             "/xyz/openbmc_project/time/sync_method",
383             "org.freedesktop.DBus.Properties", "Set",
384             "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod",
385             std::variant<std::string>{timeSyncMethod});
386     }
387 
388     void handleNTPServersPatch(const std::vector<std::string>& ntpServers,
389                                const std::shared_ptr<AsyncResp>& asyncResp)
390     {
391         crow::connections::systemBus->async_method_call(
392             [asyncResp](const boost::system::error_code ec) {
393                 if (ec)
394                 {
395                     messages::internalError(asyncResp->res);
396                     return;
397                 }
398             },
399             "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/eth0",
400             "org.freedesktop.DBus.Properties", "Set",
401             "xyz.openbmc_project.Network.EthernetInterface", "NTPServers",
402             std::variant<std::vector<std::string>>{ntpServers});
403     }
404 
405     void doPatch(crow::Response& res, const crow::Request& req,
406                  const std::vector<std::string>& params) override
407     {
408         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
409         std::optional<std::string> newHostName;
410         std::optional<nlohmann::json> ntp;
411 
412         if (!json_util::readJson(req, res, "HostName", newHostName, "NTP", ntp))
413         {
414             return;
415         }
416 
417         res.result(boost::beast::http::status::no_content);
418         if (newHostName)
419         {
420             handleHostnamePatch(*newHostName, asyncResp);
421         }
422 
423         if (ntp)
424         {
425             std::optional<std::vector<std::string>> ntpServers;
426             std::optional<bool> ntpEnabled;
427             if (!json_util::readJson(*ntp, res, "NTPServers", ntpServers,
428                                      "ProtocolEnabled", ntpEnabled))
429             {
430                 return;
431             }
432 
433             if (ntpEnabled)
434             {
435                 handleNTPProtocolEnabled(*ntpEnabled, asyncResp);
436             }
437 
438             if (ntpServers)
439             {
440                 handleNTPServersPatch(*ntpServers, asyncResp);
441             }
442         }
443     }
444 };
445 
446 } // namespace redfish
447