xref: /openbmc/bmcweb/features/redfish/lib/network_protocol.hpp (revision 7af9151495a18c805b45764b4bba6302ec214efb)
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["Id"] = "NetworkProtocol";
197         asyncResp->res.jsonValue["Name"] = "Manager Network Protocol";
198         asyncResp->res.jsonValue["Description"] = "Manager Network Service";
199         asyncResp->res.jsonValue["Status"]["Health"] = "OK";
200         asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
201         asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
202 
203         // HTTP is Mandatory attribute as per OCP Baseline Profile � v1.0.0,
204         // but from security perspective it is not recommended to use.
205         // Hence using protocolEnabled as false to make it OCP and security-wise
206         // compliant
207         asyncResp->res.jsonValue["HTTP"]["Port"] = 0;
208         asyncResp->res.jsonValue["HTTP"]["ProtocolEnabled"] = false;
209 
210         for (auto& protocol : protocolToDBus)
211         {
212             asyncResp->res.jsonValue[protocol.first]["ProtocolEnabled"] = false;
213         }
214 
215         std::string hostName = getHostName();
216 
217         asyncResp->res.jsonValue["HostName"] = hostName;
218 
219         getNTPProtocolEnabled(asyncResp);
220 
221         // TODO Get eth0 interface data, and call the below callback for JSON
222         // preparation
223         getEthernetIfaceData(
224             [hostName, asyncResp](const bool& success,
225                                   const std::vector<std::string>& ntpServers,
226                                   const std::vector<std::string>& domainNames) {
227                 if (!success)
228                 {
229                     messages::resourceNotFound(asyncResp->res,
230                                                "EthernetInterface", "eth0");
231                     return;
232                 }
233                 asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers;
234                 if (hostName.empty() == false)
235                 {
236                     std::string FQDN = std::move(hostName);
237                     if (domainNames.empty() == false)
238                     {
239                         FQDN += "." + domainNames[0];
240                     }
241                     asyncResp->res.jsonValue["FQDN"] = std::move(FQDN);
242                 }
243             });
244 
245         crow::connections::systemBus->async_method_call(
246             [asyncResp](const boost::system::error_code e,
247                         const std::vector<UnitStruct>& r) {
248                 if (e)
249                 {
250                     asyncResp->res.jsonValue = nlohmann::json::object();
251                     messages::internalError(asyncResp->res);
252                     return;
253                 }
254                 asyncResp->res.jsonValue["HTTPS"]["Certificates"] = {
255                     {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol/"
256                                   "HTTPS/Certificates"}};
257 
258                 for (auto& unit : r)
259                 {
260                     /* Only traverse through <xyz>.socket units */
261                     std::string unitName = std::get<NET_PROTO_UNIT_NAME>(unit);
262                     if (!boost::ends_with(unitName, ".socket"))
263                     {
264                         continue;
265                     }
266 
267                     for (auto& kv : protocolToDBus)
268                     {
269                         // We are interested in services, which starts with
270                         // mapped service name
271                         if (!boost::starts_with(unitName, kv.second))
272                         {
273                             continue;
274                         }
275                         const char* rfServiceKey = kv.first;
276                         std::string socketPath =
277                             std::get<NET_PROTO_UNIT_OBJ_PATH>(unit);
278                         std::string unitState =
279                             std::get<NET_PROTO_UNIT_SUB_STATE>(unit);
280 
281                         asyncResp->res
282                             .jsonValue[rfServiceKey]["ProtocolEnabled"] =
283                             (unitState == "running") ||
284                             (unitState == "listening");
285 
286                         crow::connections::systemBus->async_method_call(
287                             [asyncResp,
288                              rfServiceKey{std::string(rfServiceKey)}](
289                                 const boost::system::error_code ec,
290                                 const std::variant<std::vector<std::tuple<
291                                     std::string, std::string>>>& resp) {
292                                 if (ec)
293                                 {
294                                     messages::internalError(asyncResp->res);
295                                     return;
296                                 }
297                                 const std::vector<
298                                     std::tuple<std::string, std::string>>*
299                                     responsePtr = std::get_if<std::vector<
300                                         std::tuple<std::string, std::string>>>(
301                                         &resp);
302                                 if (responsePtr == nullptr ||
303                                     responsePtr->size() < 1)
304                                 {
305                                     return;
306                                 }
307 
308                                 const std::string& listenStream =
309                                     std::get<NET_PROTO_LISTEN_STREAM>(
310                                         (*responsePtr)[0]);
311                                 std::size_t lastColonPos =
312                                     listenStream.rfind(":");
313                                 if (lastColonPos == std::string::npos)
314                                 {
315                                     // Not a port
316                                     return;
317                                 }
318                                 std::string portStr =
319                                     listenStream.substr(lastColonPos + 1);
320                                 if (portStr.empty())
321                                 {
322                                     return;
323                                 }
324                                 char* endPtr = nullptr;
325                                 errno = 0;
326                                 // Use strtol instead of stroi to avoid
327                                 // exceptions
328                                 long port =
329                                     std::strtol(portStr.c_str(), &endPtr, 10);
330                                 if ((errno == 0) && (*endPtr == '\0'))
331                                 {
332                                     asyncResp->res
333                                         .jsonValue[rfServiceKey]["Port"] = port;
334                                 }
335                                 return;
336                             },
337                             "org.freedesktop.systemd1", socketPath,
338                             "org.freedesktop.DBus.Properties", "Get",
339                             "org.freedesktop.systemd1.Socket", "Listen");
340 
341                         // We found service, break the inner loop.
342                         break;
343                     }
344                 }
345             },
346             "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
347             "org.freedesktop.systemd1.Manager", "ListUnits");
348     }
349 
350     void handleHostnamePatch(const std::string& hostName,
351                              const std::shared_ptr<AsyncResp>& asyncResp)
352     {
353         crow::connections::systemBus->async_method_call(
354             [asyncResp](const boost::system::error_code ec) {
355                 if (ec)
356                 {
357                     messages::internalError(asyncResp->res);
358                     return;
359                 }
360             },
361             "xyz.openbmc_project.Network",
362             "/xyz/openbmc_project/network/config",
363             "org.freedesktop.DBus.Properties", "Set",
364             "xyz.openbmc_project.Network.SystemConfiguration", "HostName",
365             std::variant<std::string>(hostName));
366     }
367 
368     void handleNTPProtocolEnabled(const bool& ntpEnabled,
369                                   const std::shared_ptr<AsyncResp>& asyncResp)
370     {
371         std::string timeSyncMethod;
372         if (ntpEnabled)
373         {
374             timeSyncMethod =
375                 "xyz.openbmc_project.Time.Synchronization.Method.NTP";
376         }
377         else
378         {
379             timeSyncMethod =
380                 "xyz.openbmc_project.Time.Synchronization.Method.Manual";
381         }
382 
383         crow::connections::systemBus->async_method_call(
384             [asyncResp](const boost::system::error_code error_code) {},
385             "xyz.openbmc_project.Settings",
386             "/xyz/openbmc_project/time/sync_method",
387             "org.freedesktop.DBus.Properties", "Set",
388             "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod",
389             std::variant<std::string>{timeSyncMethod});
390     }
391 
392     void handleNTPServersPatch(const std::vector<std::string>& ntpServers,
393                                const std::shared_ptr<AsyncResp>& asyncResp)
394     {
395         crow::connections::systemBus->async_method_call(
396             [asyncResp](const boost::system::error_code ec) {
397                 if (ec)
398                 {
399                     messages::internalError(asyncResp->res);
400                     return;
401                 }
402             },
403             "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/eth0",
404             "org.freedesktop.DBus.Properties", "Set",
405             "xyz.openbmc_project.Network.EthernetInterface", "NTPServers",
406             std::variant<std::vector<std::string>>{ntpServers});
407     }
408 
409     void doPatch(crow::Response& res, const crow::Request& req,
410                  const std::vector<std::string>& params) override
411     {
412         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
413         std::optional<std::string> newHostName;
414         std::optional<nlohmann::json> ntp;
415 
416         if (!json_util::readJson(req, res, "HostName", newHostName, "NTP", ntp))
417         {
418             return;
419         }
420 
421         res.result(boost::beast::http::status::no_content);
422         if (newHostName)
423         {
424             handleHostnamePatch(*newHostName, asyncResp);
425         }
426 
427         if (ntp)
428         {
429             std::optional<std::vector<std::string>> ntpServers;
430             std::optional<bool> ntpEnabled;
431             if (!json_util::readJson(*ntp, res, "NTPServers", ntpServers,
432                                      "ProtocolEnabled", ntpEnabled))
433             {
434                 return;
435             }
436 
437             if (ntpEnabled)
438             {
439                 handleNTPProtocolEnabled(*ntpEnabled, asyncResp);
440             }
441 
442             if (ntpServers)
443             {
444                 handleNTPServersPatch(*ntpServers, asyncResp);
445             }
446         }
447     }
448 };
449 
450 } // namespace redfish
451