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