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