xref: /openbmc/bmcweb/features/redfish/lib/network_protocol.hpp (revision 720487803a1890e8d4e5d91463e7ec62b4b23f74)
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                     const crow::Request& req)
133 {
134     asyncResp->res.jsonValue["@odata.type"] =
135         "#ManagerNetworkProtocol.v1_5_0.ManagerNetworkProtocol";
136     asyncResp->res.jsonValue["@odata.id"] =
137         "/redfish/v1/Managers/bmc/NetworkProtocol";
138     asyncResp->res.jsonValue["Id"] = "NetworkProtocol";
139     asyncResp->res.jsonValue["Name"] = "Manager Network Protocol";
140     asyncResp->res.jsonValue["Description"] = "Manager Network Service";
141     asyncResp->res.jsonValue["Status"]["Health"] = "OK";
142     asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK";
143     asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
144 
145     // HTTP is Mandatory attribute as per OCP Baseline Profile - v1.0.0,
146     // but from security perspective it is not recommended to use.
147     // Hence using protocolEnabled as false to make it OCP and security-wise
148     // compliant
149     asyncResp->res.jsonValue["HTTP"]["Port"] = 0;
150     asyncResp->res.jsonValue["HTTP"]["ProtocolEnabled"] = false;
151 
152     for (auto& protocol : protocolToDBus)
153     {
154         asyncResp->res.jsonValue[protocol.first]["Port"] =
155             nlohmann::detail::value_t::null;
156         asyncResp->res.jsonValue[protocol.first]["ProtocolEnabled"] = false;
157     }
158 
159     std::string hostName = getHostName();
160 
161     asyncResp->res.jsonValue["HostName"] = hostName;
162 
163     getNTPProtocolEnabled(asyncResp);
164 
165     // TODO Get eth0 interface data, and call the below callback for JSON
166     // preparation
167     getEthernetIfaceData(
168         [hostName, asyncResp](const bool& success,
169                               const std::vector<std::string>& ntpServers,
170                               const std::vector<std::string>& domainNames) {
171             if (!success)
172             {
173                 messages::resourceNotFound(asyncResp->res, "EthernetInterface",
174                                            "eth0");
175                 return;
176             }
177             asyncResp->res.jsonValue["NTP"]["NTPServers"] = ntpServers;
178             if (hostName.empty() == false)
179             {
180                 std::string fqdn = hostName;
181                 if (domainNames.empty() == false)
182                 {
183                     fqdn += ".";
184                     fqdn += domainNames[0];
185                 }
186                 asyncResp->res.jsonValue["FQDN"] = std::move(fqdn);
187             }
188         });
189 
190     Privileges effectiveUserPrivileges =
191         redfish::getUserPrivileges(req.userRole);
192 
193     crow::connections::systemBus->async_method_call(
194         [asyncResp,
195          &effectiveUserPrivileges](const boost::system::error_code e,
196                                    const std::vector<UnitStruct>& r) {
197             if (e)
198             {
199                 asyncResp->res.jsonValue = nlohmann::json::object();
200                 messages::internalError(asyncResp->res);
201                 return;
202             }
203             // /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates is
204             // something only ConfigureManager can access then only display when
205             // the user has permissions ConfigureManager
206             if (isOperationAllowedWithPrivileges({{"ConfigureManager"}},
207                                                  effectiveUserPrivileges))
208             {
209                 asyncResp->res.jsonValue["HTTPS"]["Certificates"] = {
210                     {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol/"
211                                   "HTTPS/Certificates"}};
212             }
213             for (auto& unit : r)
214             {
215                 /* Only traverse through <xyz>.socket units */
216                 const std::string& unitName =
217                     std::get<NET_PROTO_UNIT_NAME>(unit);
218                 if (!boost::ends_with(unitName, ".socket"))
219                 {
220                     continue;
221                 }
222 
223                 for (auto& kv : protocolToDBus)
224                 {
225                     // We are interested in services, which starts with
226                     // mapped service name
227                     if (!boost::starts_with(unitName, kv.second))
228                     {
229                         continue;
230                     }
231                     const char* rfServiceKey = kv.first;
232                     const std::string& socketPath =
233                         std::get<NET_PROTO_UNIT_OBJ_PATH>(unit);
234                     const std::string& unitState =
235                         std::get<NET_PROTO_UNIT_SUB_STATE>(unit);
236 
237                     asyncResp->res.jsonValue[rfServiceKey]["ProtocolEnabled"] =
238                         (unitState == "running") || (unitState == "listening");
239 
240                     crow::connections::systemBus->async_method_call(
241                         [asyncResp, rfServiceKey{std::string(rfServiceKey)}](
242                             const boost::system::error_code ec,
243                             const std::variant<std::vector<
244                                 std::tuple<std::string, std::string>>>& resp) {
245                             if (ec)
246                             {
247                                 messages::internalError(asyncResp->res);
248                                 return;
249                             }
250                             const std::vector<
251                                 std::tuple<std::string, std::string>>*
252                                 responsePtr = std::get_if<std::vector<
253                                     std::tuple<std::string, std::string>>>(
254                                     &resp);
255                             if (responsePtr == nullptr ||
256                                 responsePtr->size() < 1)
257                             {
258                                 return;
259                             }
260 
261                             const std::string& listenStream =
262                                 std::get<NET_PROTO_LISTEN_STREAM>(
263                                     (*responsePtr)[0]);
264                             std::size_t lastColonPos = listenStream.rfind(':');
265                             if (lastColonPos == std::string::npos)
266                             {
267                                 // Not a port
268                                 return;
269                             }
270                             std::string portStr =
271                                 listenStream.substr(lastColonPos + 1);
272                             if (portStr.empty())
273                             {
274                                 return;
275                             }
276                             char* endPtr = nullptr;
277                             errno = 0;
278                             // Use strtol instead of stroi to avoid
279                             // exceptions
280                             long port =
281                                 std::strtol(portStr.c_str(), &endPtr, 10);
282                             if ((errno == 0) && (*endPtr == '\0'))
283                             {
284                                 asyncResp->res.jsonValue[rfServiceKey]["Port"] =
285                                     port;
286                             }
287                             return;
288                         },
289                         "org.freedesktop.systemd1", socketPath,
290                         "org.freedesktop.DBus.Properties", "Get",
291                         "org.freedesktop.systemd1.Socket", "Listen");
292 
293                     // We found service, break the inner loop.
294                     break;
295                 }
296             }
297         },
298         "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
299         "org.freedesktop.systemd1.Manager", "ListUnits");
300 }
301 
302 #ifdef BMCWEB_ALLOW_DEPRECATED_HOSTNAME_PATCH
303 void handleHostnamePatch(const std::string& hostName,
304                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
305 {
306     crow::connections::systemBus->async_method_call(
307         [asyncResp](const boost::system::error_code ec) {
308             if (ec)
309             {
310                 messages::internalError(asyncResp->res);
311                 return;
312             }
313         },
314         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/config",
315         "org.freedesktop.DBus.Properties", "Set",
316         "xyz.openbmc_project.Network.SystemConfiguration", "HostName",
317         std::variant<std::string>(hostName));
318 }
319 #endif
320 
321 void handleNTPProtocolEnabled(
322     const bool& ntpEnabled, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
323 {
324     std::string timeSyncMethod;
325     if (ntpEnabled)
326     {
327         timeSyncMethod = "xyz.openbmc_project.Time.Synchronization.Method.NTP";
328     }
329     else
330     {
331         timeSyncMethod =
332             "xyz.openbmc_project.Time.Synchronization.Method.Manual";
333     }
334 
335     crow::connections::systemBus->async_method_call(
336         [asyncResp](const boost::system::error_code errorCode) {
337             if (errorCode)
338             {
339                 messages::internalError(asyncResp->res);
340             }
341         },
342         "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method",
343         "org.freedesktop.DBus.Properties", "Set",
344         "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod",
345         std::variant<std::string>{timeSyncMethod});
346 }
347 
348 void handleNTPServersPatch(const std::vector<std::string>& ntpServers,
349                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
350 {
351     crow::connections::systemBus->async_method_call(
352         [asyncResp](const boost::system::error_code ec) {
353             if (ec)
354             {
355                 messages::internalError(asyncResp->res);
356                 return;
357             }
358         },
359         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/eth0",
360         "org.freedesktop.DBus.Properties", "Set",
361         "xyz.openbmc_project.Network.EthernetInterface", "NTPServers",
362         std::variant<std::vector<std::string>>{ntpServers});
363 }
364 
365 void handleProtocolEnabled(const bool protocolEnabled,
366                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
367                            const std::string_view netBasePath)
368 {
369     crow::connections::systemBus->async_method_call(
370         [protocolEnabled, asyncResp,
371          netBasePath](const boost::system::error_code ec,
372                       const crow::openbmc_mapper::GetSubTreeType& subtree) {
373             if (ec)
374             {
375                 messages::internalError(asyncResp->res);
376                 return;
377             }
378 
379             for (const auto& entry : subtree)
380             {
381                 if (boost::algorithm::starts_with(entry.first, netBasePath))
382                 {
383                     crow::connections::systemBus->async_method_call(
384                         [asyncResp](const boost::system::error_code ec2) {
385                             if (ec2)
386                             {
387                                 messages::internalError(asyncResp->res);
388                                 return;
389                             }
390                         },
391                         entry.second.begin()->first, entry.first,
392                         "org.freedesktop.DBus.Properties", "Set",
393                         "xyz.openbmc_project.Control.Service.Attributes",
394                         "Running", std::variant<bool>{protocolEnabled});
395 
396                     crow::connections::systemBus->async_method_call(
397                         [asyncResp](const boost::system::error_code ec2) {
398                             if (ec2)
399                             {
400                                 messages::internalError(asyncResp->res);
401                                 return;
402                             }
403                         },
404                         entry.second.begin()->first, entry.first,
405                         "org.freedesktop.DBus.Properties", "Set",
406                         "xyz.openbmc_project.Control.Service.Attributes",
407                         "Enabled", std::variant<bool>{protocolEnabled});
408                 }
409             }
410         },
411         "xyz.openbmc_project.ObjectMapper",
412         "/xyz/openbmc_project/object_mapper",
413         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
414         "/xyz/openbmc_project/control/service", 0,
415         std::array<const char*, 1>{
416             "xyz.openbmc_project.Control.Service.Attributes"});
417 }
418 
419 std::string getHostName()
420 {
421     std::string hostName;
422 
423     std::array<char, HOST_NAME_MAX> hostNameCStr;
424     if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0)
425     {
426         hostName = hostNameCStr.data();
427     }
428     return hostName;
429 }
430 
431 void getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
432 {
433     crow::connections::systemBus->async_method_call(
434         [asyncResp](const boost::system::error_code errorCode,
435                     const std::variant<std::string>& timeSyncMethod) {
436             if (errorCode)
437             {
438                 return;
439             }
440 
441             const std::string* s = std::get_if<std::string>(&timeSyncMethod);
442 
443             if (*s == "xyz.openbmc_project.Time.Synchronization.Method.NTP")
444             {
445                 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true;
446             }
447             else if (*s == "xyz.openbmc_project.Time.Synchronization."
448                            "Method.Manual")
449             {
450                 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false;
451             }
452         },
453         "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method",
454         "org.freedesktop.DBus.Properties", "Get",
455         "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod");
456 }
457 
458 inline void requestRoutesNetworkProtocol(App& app)
459 {
460     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
461         .privileges({{"ConfigureManager"}})
462         .methods(boost::beast::http::verb::patch)(
463             [](const crow::Request& req,
464                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
465                 std::optional<std::string> newHostName;
466                 std::optional<nlohmann::json> ntp;
467                 std::optional<nlohmann::json> ipmi;
468                 std::optional<nlohmann::json> ssh;
469 
470                 if (!json_util::readJson(req, asyncResp->res, "NTP", ntp,
471                                          "HostName", newHostName, "IPMI", ipmi,
472                                          "SSH", ssh))
473                 {
474                     return;
475                 }
476 
477                 asyncResp->res.result(boost::beast::http::status::no_content);
478                 if (newHostName)
479                 {
480 #ifdef BMCWEB_ALLOW_DEPRECATED_HOSTNAME_PATCH
481                     handleHostnamePatch(*newHostName, asyncResp);
482 #else
483                     messages::propertyNotWritable(asyncResp->res, "HostName");
484 #endif
485                 }
486 
487                 if (ntp)
488                 {
489                     std::optional<std::vector<std::string>> ntpServers;
490                     std::optional<bool> ntpEnabled;
491                     if (!json_util::readJson(*ntp, asyncResp->res, "NTPServers",
492                                              ntpServers, "ProtocolEnabled",
493                                              ntpEnabled))
494                     {
495                         return;
496                     }
497 
498                     if (ntpEnabled)
499                     {
500                         handleNTPProtocolEnabled(*ntpEnabled, asyncResp);
501                     }
502 
503                     if (ntpServers)
504                     {
505                         std::sort((*ntpServers).begin(), (*ntpServers).end());
506                         (*ntpServers)
507                             .erase(std::unique((*ntpServers).begin(),
508                                                (*ntpServers).end()),
509                                    (*ntpServers).end());
510                         handleNTPServersPatch(*ntpServers, asyncResp);
511                     }
512                 }
513 
514                 if (ipmi)
515                 {
516                     std::optional<bool> ipmiProtocolEnabled;
517                     if (!json_util::readJson(*ipmi, asyncResp->res,
518                                              "ProtocolEnabled",
519                                              ipmiProtocolEnabled))
520                     {
521                         return;
522                     }
523 
524                     if (ipmiProtocolEnabled)
525                     {
526                         handleProtocolEnabled(
527                             *ipmiProtocolEnabled, asyncResp,
528                             "/xyz/openbmc_project/control/service/"
529                             "phosphor_2dipmi_2dnet_40");
530                     }
531                 }
532 
533                 if (ssh)
534                 {
535                     std::optional<bool> sshProtocolEnabled;
536                     if (!json_util::readJson(*ssh, asyncResp->res,
537                                              "ProtocolEnabled",
538                                              sshProtocolEnabled))
539                     {
540                         return;
541                     }
542 
543                     if (sshProtocolEnabled)
544                     {
545                         handleProtocolEnabled(
546                             *sshProtocolEnabled, asyncResp,
547                             "/xyz/openbmc_project/control/service/dropbear");
548                     }
549                 }
550             });
551 
552     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
553         .privileges({{"Login"}})
554         .methods(boost::beast::http::verb::get)(
555             [](const crow::Request& req,
556                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
557                 getNetworkData(asyncResp, req);
558             });
559 }
560 
561 } // namespace redfish
562