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