xref: /openbmc/bmcweb/redfish-core/lib/network_protocol.hpp (revision cb13a39253848ece442971301ade9c09d98bf08e)
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 #include "openbmc_dbus_rest.hpp"
21 
22 #include <utils/json_utils.hpp>
23 
24 #include <optional>
25 #include <variant>
26 namespace redfish
27 {
28 
29 enum NetworkProtocolUnitStructFields
30 {
31     NET_PROTO_UNIT_NAME,
32     NET_PROTO_UNIT_DESC,
33     NET_PROTO_UNIT_LOAD_STATE,
34     NET_PROTO_UNIT_ACTIVE_STATE,
35     NET_PROTO_UNIT_SUB_STATE,
36     NET_PROTO_UNIT_DEVICE,
37     NET_PROTO_UNIT_OBJ_PATH,
38     NET_PROTO_UNIT_ALWAYS_0,
39     NET_PROTO_UNIT_ALWAYS_EMPTY,
40     NET_PROTO_UNIT_ALWAYS_ROOT_PATH
41 };
42 
43 enum NetworkProtocolListenResponseElements
44 {
45     NET_PROTO_LISTEN_TYPE,
46     NET_PROTO_LISTEN_STREAM
47 };
48 
49 /**
50  * @brief D-Bus Unit structure returned in array from ListUnits Method
51  */
52 using UnitStruct =
53     std::tuple<std::string, std::string, std::string, std::string, std::string,
54                std::string, sdbusplus::message::object_path, uint32_t,
55                std::string, sdbusplus::message::object_path>;
56 
57 const static boost::container::flat_map<const char*, std::string>
58     protocolToDBus{{"SSH", "dropbear"},
59                    {"HTTPS", "bmcweb"},
60                    {"IPMI", "phosphor-ipmi-net"}};
61 
62 inline void
63     extractNTPServersAndDomainNamesData(const GetManagedObjects& dbus_data,
64                                         std::vector<std::string>& ntpData,
65                                         std::vector<std::string>& dnData)
66 {
67     for (const auto& obj : dbus_data)
68     {
69         for (const auto& ifacePair : obj.second)
70         {
71             if (obj.first == "/xyz/openbmc_project/network/eth0")
72             {
73                 if (ifacePair.first ==
74                     "xyz.openbmc_project.Network.EthernetInterface")
75                 {
76                     for (const auto& propertyPair : ifacePair.second)
77                     {
78                         if (propertyPair.first == "NTPServers")
79                         {
80                             const std::vector<std::string>* ntpServers =
81                                 std::get_if<std::vector<std::string>>(
82                                     &propertyPair.second);
83                             if (ntpServers != nullptr)
84                             {
85                                 ntpData = std::move(*ntpServers);
86                             }
87                         }
88                         else if (propertyPair.first == "DomainName")
89                         {
90                             const std::vector<std::string>* domainNames =
91                                 std::get_if<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(App& 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&,
147                const std::vector<std::string>&) 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                 if (error_code)
172                 {
173                     return;
174                 }
175 
176                 const std::string* s =
177                     std::get_if<std::string>(&timeSyncMethod);
178 
179                 if (*s == "xyz.openbmc_project.Time.Synchronization.Method.NTP")
180                 {
181                     asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true;
182                 }
183                 else if (*s == "xyz.openbmc_project.Time.Synchronization."
184                                "Method.Manual")
185                 {
186                     asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false;
187                 }
188             },
189             "xyz.openbmc_project.Settings",
190             "/xyz/openbmc_project/time/sync_method",
191             "org.freedesktop.DBus.Properties", "Get",
192             "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod");
193     }
194 
195     void getData(const std::shared_ptr<AsyncResp>& asyncResp)
196     {
197         asyncResp->res.jsonValue["@odata.type"] =
198             "#ManagerNetworkProtocol.v1_5_0.ManagerNetworkProtocol";
199         asyncResp->res.jsonValue["@odata.id"] =
200             "/redfish/v1/Managers/bmc/NetworkProtocol";
201         asyncResp->res.jsonValue["Id"] = "NetworkProtocol";
202         asyncResp->res.jsonValue["Name"] = "Manager Network Protocol";
203         asyncResp->res.jsonValue["Description"] = "Manager Network Service";
204         asyncResp->res.jsonValue["Status"]["Health"] = "OK";
205         asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
206         asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
207         asyncResp->res.jsonValue["SNMP"]["ProtocolEnabled"] = true;
208         asyncResp->res.jsonValue["SNMP"]["Port"] = 161;
209         asyncResp->res.jsonValue["SNMP"]["AuthenticationProtocol"] =
210             "CommunityString";
211         asyncResp->res.jsonValue["SNMP"]["CommunityAccessMode"] = "Full";
212         asyncResp->res.jsonValue["SNMP"]["HideCommunityStrings"] = true;
213         asyncResp->res
214             .jsonValue["SNMP"]["EngineId"]["EnterpriseSpecificMethod"] =
215             nullptr;
216         asyncResp->res.jsonValue["SNMP"]["EngineId"]["PrivateEnterpriseId"] =
217             nullptr;
218         asyncResp->res.jsonValue["SNMP"]["EnableSNMPv1"] = false;
219         asyncResp->res.jsonValue["SNMP"]["EnableSNMPv2c"] = true;
220         asyncResp->res.jsonValue["SNMP"]["EnableSNMPv3"] = false;
221         asyncResp->res.jsonValue["SNMP"]["EncryptionProtocol"] = "None";
222         nlohmann::json& memberArray =
223             asyncResp->res.jsonValue["SNMP"]["CommunityStrings"];
224         memberArray = nlohmann::json::array();
225         memberArray.push_back({{"AccessMode", "Full"}});
226         memberArray.push_back({{"CommunityString", ""}});
227         memberArray.push_back({{"Name", ""}});
228 
229         // HTTP is Mandatory attribute as per OCP Baseline Profile - v1.0.0,
230         // but from security perspective it is not recommended to use.
231         // Hence using protocolEnabled as false to make it OCP and security-wise
232         // compliant
233         asyncResp->res.jsonValue["HTTP"]["Port"] = 0;
234         asyncResp->res.jsonValue["HTTP"]["ProtocolEnabled"] = false;
235 
236         for (auto& protocol : protocolToDBus)
237         {
238             asyncResp->res.jsonValue[protocol.first]["Port"] =
239                 nlohmann::detail::value_t::null;
240             asyncResp->res.jsonValue[protocol.first]["ProtocolEnabled"] = false;
241         }
242 
243         std::string hostName = getHostName();
244 
245         asyncResp->res.jsonValue["HostName"] = hostName;
246 
247         getNTPProtocolEnabled(asyncResp);
248 
249         // TODO Get eth0 interface data, and call the below callback for JSON
250         // preparation
251         getEthernetIfaceData(
252             [hostName, asyncResp](const bool& success,
253                                   const std::vector<std::string>& ntpServers,
254                                   const std::vector<std::string>& domainNames) {
255                 if (!success)
256                 {
257                     messages::resourceNotFound(asyncResp->res,
258                                                "EthernetInterface", "eth0");
259                     return;
260                 }
261                 asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers;
262                 if (hostName.empty() == false)
263                 {
264                     std::string FQDN = std::move(hostName);
265                     if (domainNames.empty() == false)
266                     {
267                         FQDN += "." + domainNames[0];
268                     }
269                     asyncResp->res.jsonValue["FQDN"] = std::move(FQDN);
270                 }
271             });
272 
273         crow::connections::systemBus->async_method_call(
274             [asyncResp](const boost::system::error_code e,
275                         const std::vector<UnitStruct>& r) {
276                 if (e)
277                 {
278                     asyncResp->res.jsonValue = nlohmann::json::object();
279                     messages::internalError(asyncResp->res);
280                     return;
281                 }
282                 asyncResp->res.jsonValue["HTTPS"]["Certificates"] = {
283                     {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol/"
284                                   "HTTPS/Certificates"}};
285 
286                 for (auto& unit : r)
287                 {
288                     /* Only traverse through <xyz>.socket units */
289                     std::string unitName = std::get<NET_PROTO_UNIT_NAME>(unit);
290                     if (!boost::ends_with(unitName, ".socket"))
291                     {
292                         continue;
293                     }
294 
295                     for (auto& kv : protocolToDBus)
296                     {
297                         // We are interested in services, which starts with
298                         // mapped service name
299                         if (!boost::starts_with(unitName, kv.second))
300                         {
301                             continue;
302                         }
303                         const char* rfServiceKey = kv.first;
304                         std::string socketPath =
305                             std::get<NET_PROTO_UNIT_OBJ_PATH>(unit);
306                         std::string unitState =
307                             std::get<NET_PROTO_UNIT_SUB_STATE>(unit);
308 
309                         asyncResp->res
310                             .jsonValue[rfServiceKey]["ProtocolEnabled"] =
311                             (unitState == "running") ||
312                             (unitState == "listening");
313 
314                         crow::connections::systemBus->async_method_call(
315                             [asyncResp,
316                              rfServiceKey{std::string(rfServiceKey)}](
317                                 const boost::system::error_code ec,
318                                 const std::variant<std::vector<std::tuple<
319                                     std::string, std::string>>>& resp) {
320                                 if (ec)
321                                 {
322                                     messages::internalError(asyncResp->res);
323                                     return;
324                                 }
325                                 const std::vector<
326                                     std::tuple<std::string, std::string>>*
327                                     responsePtr = std::get_if<std::vector<
328                                         std::tuple<std::string, std::string>>>(
329                                         &resp);
330                                 if (responsePtr == nullptr ||
331                                     responsePtr->size() < 1)
332                                 {
333                                     return;
334                                 }
335 
336                                 const std::string& listenStream =
337                                     std::get<NET_PROTO_LISTEN_STREAM>(
338                                         (*responsePtr)[0]);
339                                 std::size_t lastColonPos =
340                                     listenStream.rfind(":");
341                                 if (lastColonPos == std::string::npos)
342                                 {
343                                     // Not a port
344                                     return;
345                                 }
346                                 std::string portStr =
347                                     listenStream.substr(lastColonPos + 1);
348                                 if (portStr.empty())
349                                 {
350                                     return;
351                                 }
352                                 char* endPtr = nullptr;
353                                 errno = 0;
354                                 // Use strtol instead of stroi to avoid
355                                 // exceptions
356                                 long port =
357                                     std::strtol(portStr.c_str(), &endPtr, 10);
358                                 if ((errno == 0) && (*endPtr == '\0'))
359                                 {
360                                     asyncResp->res
361                                         .jsonValue[rfServiceKey]["Port"] = port;
362                                 }
363                                 return;
364                             },
365                             "org.freedesktop.systemd1", socketPath,
366                             "org.freedesktop.DBus.Properties", "Get",
367                             "org.freedesktop.systemd1.Socket", "Listen");
368 
369                         // We found service, break the inner loop.
370                         break;
371                     }
372                 }
373             },
374             "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
375             "org.freedesktop.systemd1.Manager", "ListUnits");
376     }
377 
378     void handleHostnamePatch(const std::string& hostName,
379                              const std::shared_ptr<AsyncResp>& asyncResp)
380     {
381         crow::connections::systemBus->async_method_call(
382             [asyncResp](const boost::system::error_code ec) {
383                 if (ec)
384                 {
385                     messages::internalError(asyncResp->res);
386                     return;
387                 }
388             },
389             "xyz.openbmc_project.Network",
390             "/xyz/openbmc_project/network/config",
391             "org.freedesktop.DBus.Properties", "Set",
392             "xyz.openbmc_project.Network.SystemConfiguration", "HostName",
393             std::variant<std::string>(hostName));
394     }
395 
396     void handleNTPProtocolEnabled(const bool& ntpEnabled,
397                                   const std::shared_ptr<AsyncResp>& asyncResp)
398     {
399         std::string timeSyncMethod;
400         if (ntpEnabled)
401         {
402             timeSyncMethod =
403                 "xyz.openbmc_project.Time.Synchronization.Method.NTP";
404         }
405         else
406         {
407             timeSyncMethod =
408                 "xyz.openbmc_project.Time.Synchronization.Method.Manual";
409         }
410 
411         crow::connections::systemBus->async_method_call(
412             [asyncResp](const boost::system::error_code error_code) {
413                 if (error_code)
414                 {
415                     messages::internalError(asyncResp->res);
416                 }
417             },
418             "xyz.openbmc_project.Settings",
419             "/xyz/openbmc_project/time/sync_method",
420             "org.freedesktop.DBus.Properties", "Set",
421             "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod",
422             std::variant<std::string>{timeSyncMethod});
423     }
424 
425     void handleNTPServersPatch(const std::vector<std::string>& ntpServers,
426                                const std::shared_ptr<AsyncResp>& asyncResp)
427     {
428         crow::connections::systemBus->async_method_call(
429             [asyncResp](const boost::system::error_code ec) {
430                 if (ec)
431                 {
432                     messages::internalError(asyncResp->res);
433                     return;
434                 }
435             },
436             "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/eth0",
437             "org.freedesktop.DBus.Properties", "Set",
438             "xyz.openbmc_project.Network.EthernetInterface", "NTPServers",
439             std::variant<std::vector<std::string>>{ntpServers});
440     }
441 
442     void handleIpmiProtocolEnabled(const bool ipmiProtocolEnabled,
443                                    const std::shared_ptr<AsyncResp>& asyncResp)
444     {
445         crow::connections::systemBus->async_method_call(
446             [ipmiProtocolEnabled,
447              asyncResp](const boost::system::error_code ec,
448                         const crow::openbmc_mapper::GetSubTreeType& subtree) {
449                 if (ec)
450                 {
451                     messages::internalError(asyncResp->res);
452                     return;
453                 }
454 
455                 constexpr char const* netipmidBasePath =
456                     "/xyz/openbmc_project/control/service/"
457                     "phosphor_2dipmi_2dnet_40";
458 
459                 for (const auto& entry : subtree)
460                 {
461                     if (boost::algorithm::starts_with(entry.first,
462                                                       netipmidBasePath))
463                     {
464                         crow::connections::systemBus->async_method_call(
465                             [asyncResp](const boost::system::error_code ec2) {
466                                 if (ec2)
467                                 {
468                                     messages::internalError(asyncResp->res);
469                                     return;
470                                 }
471                             },
472                             entry.second.begin()->first, entry.first,
473                             "org.freedesktop.DBus.Properties", "Set",
474                             "xyz.openbmc_project.Control.Service.Attributes",
475                             "Running", std::variant<bool>{ipmiProtocolEnabled});
476 
477                         crow::connections::systemBus->async_method_call(
478                             [asyncResp](const boost::system::error_code ec2) {
479                                 if (ec2)
480                                 {
481                                     messages::internalError(asyncResp->res);
482                                     return;
483                                 }
484                             },
485                             entry.second.begin()->first, entry.first,
486                             "org.freedesktop.DBus.Properties", "Set",
487                             "xyz.openbmc_project.Control.Service.Attributes",
488                             "Enabled", std::variant<bool>{ipmiProtocolEnabled});
489                     }
490                 }
491             },
492             "xyz.openbmc_project.ObjectMapper",
493             "/xyz/openbmc_project/object_mapper",
494             "xyz.openbmc_project.ObjectMapper", "GetSubTree",
495             "/xyz/openbmc_project/control/service", 0,
496             std::array<const char*, 1>{
497                 "xyz.openbmc_project.Control.Service.Attributes"});
498     }
499 
500     void doPatch(crow::Response& res, const crow::Request& req,
501                  const std::vector<std::string>&) override
502     {
503         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
504         std::optional<std::string> newHostName;
505         std::optional<nlohmann::json> ntp;
506         std::optional<nlohmann::json> ipmi;
507 
508         if (!json_util::readJson(req, res, "HostName", newHostName, "NTP", ntp,
509                                  "IPMI", ipmi))
510         {
511             return;
512         }
513 
514         res.result(boost::beast::http::status::no_content);
515         if (newHostName)
516         {
517             handleHostnamePatch(*newHostName, asyncResp);
518         }
519 
520         if (ntp)
521         {
522             std::optional<std::vector<std::string>> ntpServers;
523             std::optional<bool> ntpEnabled;
524             if (!json_util::readJson(*ntp, res, "NTPServers", ntpServers,
525                                      "ProtocolEnabled", ntpEnabled))
526             {
527                 return;
528             }
529 
530             if (ntpEnabled)
531             {
532                 handleNTPProtocolEnabled(*ntpEnabled, asyncResp);
533             }
534 
535             if (ntpServers)
536             {
537                 std::sort((*ntpServers).begin(), (*ntpServers).end());
538                 (*ntpServers)
539                     .erase(
540                         std::unique((*ntpServers).begin(), (*ntpServers).end()),
541                         (*ntpServers).end());
542                 handleNTPServersPatch(*ntpServers, asyncResp);
543             }
544         }
545 
546         if (ipmi)
547         {
548             std::optional<bool> ipmiProtocolEnabled;
549             if (!json_util::readJson(*ipmi, res, "ProtocolEnabled",
550                                      ipmiProtocolEnabled))
551             {
552                 return;
553             }
554 
555             if (ipmiProtocolEnabled)
556             {
557                 handleIpmiProtocolEnabled(*ipmiProtocolEnabled, asyncResp);
558             }
559         }
560     }
561 };
562 
563 } // namespace redfish
564