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