xref: /openbmc/bmcweb/features/redfish/lib/network_protocol.hpp (revision ed3982131dcef2b499da36e674d2d21b2289ef29)
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 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 void handleHostnamePatch(const std::string& hostName,
305                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
306 {
307     crow::connections::systemBus->async_method_call(
308         [asyncResp](const boost::system::error_code ec) {
309             if (ec)
310             {
311                 messages::internalError(asyncResp->res);
312                 return;
313             }
314         },
315         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/config",
316         "org.freedesktop.DBus.Properties", "Set",
317         "xyz.openbmc_project.Network.SystemConfiguration", "HostName",
318         std::variant<std::string>(hostName));
319 }
320 #endif
321 
322 void handleNTPProtocolEnabled(
323     const bool& ntpEnabled, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
324 {
325     std::string timeSyncMethod;
326     if (ntpEnabled)
327     {
328         timeSyncMethod = "xyz.openbmc_project.Time.Synchronization.Method.NTP";
329     }
330     else
331     {
332         timeSyncMethod =
333             "xyz.openbmc_project.Time.Synchronization.Method.Manual";
334     }
335 
336     crow::connections::systemBus->async_method_call(
337         [asyncResp](const boost::system::error_code errorCode) {
338             if (errorCode)
339             {
340                 messages::internalError(asyncResp->res);
341             }
342         },
343         "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method",
344         "org.freedesktop.DBus.Properties", "Set",
345         "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod",
346         std::variant<std::string>{timeSyncMethod});
347 }
348 
349 void handleNTPServersPatch(const std::vector<std::string>& ntpServers,
350                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
351 {
352     crow::connections::systemBus->async_method_call(
353         [asyncResp](const boost::system::error_code ec) {
354             if (ec)
355             {
356                 messages::internalError(asyncResp->res);
357                 return;
358             }
359         },
360         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/eth0",
361         "org.freedesktop.DBus.Properties", "Set",
362         "xyz.openbmc_project.Network.EthernetInterface", "NTPServers",
363         std::variant<std::vector<std::string>>{ntpServers});
364 }
365 
366 void handleProtocolEnabled(const bool protocolEnabled,
367                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
368                            const std::string_view netBasePath)
369 {
370     crow::connections::systemBus->async_method_call(
371         [protocolEnabled, asyncResp,
372          netBasePath](const boost::system::error_code ec,
373                       const crow::openbmc_mapper::GetSubTreeType& subtree) {
374             if (ec)
375             {
376                 messages::internalError(asyncResp->res);
377                 return;
378             }
379 
380             for (const auto& entry : subtree)
381             {
382                 if (boost::algorithm::starts_with(entry.first, netBasePath))
383                 {
384                     crow::connections::systemBus->async_method_call(
385                         [asyncResp](const boost::system::error_code ec2) {
386                             if (ec2)
387                             {
388                                 messages::internalError(asyncResp->res);
389                                 return;
390                             }
391                         },
392                         entry.second.begin()->first, entry.first,
393                         "org.freedesktop.DBus.Properties", "Set",
394                         "xyz.openbmc_project.Control.Service.Attributes",
395                         "Running", std::variant<bool>{protocolEnabled});
396 
397                     crow::connections::systemBus->async_method_call(
398                         [asyncResp](const boost::system::error_code ec2) {
399                             if (ec2)
400                             {
401                                 messages::internalError(asyncResp->res);
402                                 return;
403                             }
404                         },
405                         entry.second.begin()->first, entry.first,
406                         "org.freedesktop.DBus.Properties", "Set",
407                         "xyz.openbmc_project.Control.Service.Attributes",
408                         "Enabled", std::variant<bool>{protocolEnabled});
409                 }
410             }
411         },
412         "xyz.openbmc_project.ObjectMapper",
413         "/xyz/openbmc_project/object_mapper",
414         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
415         "/xyz/openbmc_project/control/service", 0,
416         std::array<const char*, 1>{
417             "xyz.openbmc_project.Control.Service.Attributes"});
418 }
419 
420 std::string getHostName()
421 {
422     std::string hostName;
423 
424     std::array<char, HOST_NAME_MAX> hostNameCStr;
425     if (gethostname(hostNameCStr.data(), hostNameCStr.size()) == 0)
426     {
427         hostName = hostNameCStr.data();
428     }
429     return hostName;
430 }
431 
432 void getNTPProtocolEnabled(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
433 {
434     crow::connections::systemBus->async_method_call(
435         [asyncResp](const boost::system::error_code errorCode,
436                     const std::variant<std::string>& timeSyncMethod) {
437             if (errorCode)
438             {
439                 return;
440             }
441 
442             const std::string* s = std::get_if<std::string>(&timeSyncMethod);
443 
444             if (*s == "xyz.openbmc_project.Time.Synchronization.Method.NTP")
445             {
446                 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = true;
447             }
448             else if (*s == "xyz.openbmc_project.Time.Synchronization."
449                            "Method.Manual")
450             {
451                 asyncResp->res.jsonValue["NTP"]["ProtocolEnabled"] = false;
452             }
453         },
454         "xyz.openbmc_project.Settings", "/xyz/openbmc_project/time/sync_method",
455         "org.freedesktop.DBus.Properties", "Get",
456         "xyz.openbmc_project.Time.Synchronization", "TimeSyncMethod");
457 }
458 
459 inline void requestRoutesNetworkProtocol(App& app)
460 {
461     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
462         .privileges(redfish::privileges::patchManagerNetworkProtocol)
463         .methods(boost::beast::http::verb::patch)(
464             [](const crow::Request& req,
465                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
466                 std::optional<std::string> newHostName;
467                 std::optional<nlohmann::json> ntp;
468                 std::optional<nlohmann::json> ipmi;
469                 std::optional<nlohmann::json> ssh;
470 
471                 if (!json_util::readJson(req, asyncResp->res, "NTP", ntp,
472                                          "HostName", newHostName, "IPMI", ipmi,
473                                          "SSH", ssh))
474                 {
475                     return;
476                 }
477 
478                 asyncResp->res.result(boost::beast::http::status::no_content);
479                 if (newHostName)
480                 {
481 #ifdef BMCWEB_ALLOW_DEPRECATED_HOSTNAME_PATCH
482                     handleHostnamePatch(*newHostName, asyncResp);
483 #else
484                     messages::propertyNotWritable(asyncResp->res, "HostName");
485 #endif
486                 }
487 
488                 if (ntp)
489                 {
490                     std::optional<std::vector<std::string>> ntpServers;
491                     std::optional<bool> ntpEnabled;
492                     if (!json_util::readJson(*ntp, asyncResp->res, "NTPServers",
493                                              ntpServers, "ProtocolEnabled",
494                                              ntpEnabled))
495                     {
496                         return;
497                     }
498 
499                     if (ntpEnabled)
500                     {
501                         handleNTPProtocolEnabled(*ntpEnabled, asyncResp);
502                     }
503 
504                     if (ntpServers)
505                     {
506                         std::sort((*ntpServers).begin(), (*ntpServers).end());
507                         (*ntpServers)
508                             .erase(std::unique((*ntpServers).begin(),
509                                                (*ntpServers).end()),
510                                    (*ntpServers).end());
511                         handleNTPServersPatch(*ntpServers, asyncResp);
512                     }
513                 }
514 
515                 if (ipmi)
516                 {
517                     std::optional<bool> ipmiProtocolEnabled;
518                     if (!json_util::readJson(*ipmi, asyncResp->res,
519                                              "ProtocolEnabled",
520                                              ipmiProtocolEnabled))
521                     {
522                         return;
523                     }
524 
525                     if (ipmiProtocolEnabled)
526                     {
527                         handleProtocolEnabled(
528                             *ipmiProtocolEnabled, asyncResp,
529                             "/xyz/openbmc_project/control/service/"
530                             "phosphor_2dipmi_2dnet_40");
531                     }
532                 }
533 
534                 if (ssh)
535                 {
536                     std::optional<bool> sshProtocolEnabled;
537                     if (!json_util::readJson(*ssh, asyncResp->res,
538                                              "ProtocolEnabled",
539                                              sshProtocolEnabled))
540                     {
541                         return;
542                     }
543 
544                     if (sshProtocolEnabled)
545                     {
546                         handleProtocolEnabled(
547                             *sshProtocolEnabled, asyncResp,
548                             "/xyz/openbmc_project/control/service/dropbear");
549                     }
550                 }
551             });
552 
553     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/NetworkProtocol/")
554         .privileges(redfish::privileges::getManagerNetworkProtocol)
555         .methods(boost::beast::http::verb::get)(
556             [](const crow::Request& req,
557                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
558                 getNetworkData(asyncResp, req);
559             });
560 }
561 
562 } // namespace redfish
563